Skip to content

Commit 0a95f2f

Browse files
authored
[libc++][TZDB] Finishes zoned_time member functions. (llvm#95026)
Note the implementation of zoned_time& operator=(const local_time<Duration>& lt); is not correct; however the wording cannot be easily implemented. It could be if the object caches the local_time assigned. However this does not seem to intended. The current implementation matches MSVC STL and libstdc++. Implements parts of: - P0355 Extending to chrono Calendars and Time Zones
1 parent 14ba847 commit 0a95f2f

File tree

8 files changed

+872
-2
lines changed

8 files changed

+872
-2
lines changed

libcxx/include/__chrono/zoned_time.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
# include <__chrono/calendar.h>
2020
# include <__chrono/duration.h>
21+
# include <__chrono/sys_info.h>
2122
# include <__chrono/system_clock.h>
2223
# include <__chrono/time_zone.h>
2324
# include <__chrono/tzdb_list.h>
@@ -147,8 +148,29 @@ class zoned_time {
147148
} && is_convertible_v<sys_time<_Duration2>, sys_time<_Duration>>)
148149
: zoned_time{__traits::locate_zone(__name), __zt, __c} {}
149150

151+
_LIBCPP_HIDE_FROM_ABI zoned_time& operator=(const sys_time<_Duration>& __tp) {
152+
__tp_ = __tp;
153+
return *this;
154+
}
155+
156+
_LIBCPP_HIDE_FROM_ABI zoned_time& operator=(const local_time<_Duration>& __tp) {
157+
// TODO TZDB This seems wrong.
158+
// Assigning a non-existent or ambiguous time will throw and not satisfy
159+
// the post condition. This seems quite odd; I constructed an object with
160+
// choose::earliest and that choice is not respected.
161+
// what did LEWG do with this.
162+
// MSVC STL and libstdc++ behave the same
163+
__tp_ = __zone_->to_sys(__tp);
164+
return *this;
165+
}
166+
167+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI operator sys_time<duration>() const { return get_sys_time(); }
168+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI explicit operator local_time<duration>() const { return get_local_time(); }
169+
150170
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI _TimeZonePtr get_time_zone() const { return __zone_; }
171+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI local_time<duration> get_local_time() const { return __zone_->to_local(__tp_); }
151172
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI sys_time<duration> get_sys_time() const { return __tp_; }
173+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI sys_info get_info() const { return __zone_->get_info(__tp_); }
152174

153175
private:
154176
_TimeZonePtr __zone_;

libcxx/test/libcxx/diagnostics/chrono.nodiscard.verify.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,15 @@ void test() {
8181

8282
{
8383
std::chrono::zoned_time<std::chrono::seconds> zt;
84-
zt.get_time_zone(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
85-
zt.get_sys_time(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
84+
85+
// expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
86+
static_cast<std::chrono::sys_seconds>(zt);
87+
// expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}}
88+
static_cast<std::chrono::local_seconds>(zt);
89+
90+
zt.get_time_zone(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
91+
zt.get_local_time(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
92+
zt.get_sys_time(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
93+
zt.get_info(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
8694
}
8795
}
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17
10+
// UNSUPPORTED: no-filesystem, no-localization, no-tzdb
11+
12+
// XFAIL: libcpp-has-no-experimental-tzdb
13+
// XFAIL: availability-tzdb-missing
14+
15+
// <chrono>
16+
17+
// template<class Duration, class TimeZonePtr = const time_zone*>
18+
// class zoned_time;
19+
//
20+
// zoned_time& operator=(const local_time<Duration>& st);
21+
22+
// TODO TZDB Investigate the issues in this test, this seems like
23+
// a design issue of the class.
24+
//
25+
// [time.zone.zonedtime.members]/3
26+
// Effects: After assignment, get_local_time() == lt.
27+
// This assignment has no effect on the return value of get_time_zone().
28+
//
29+
// The test cases describe the issues.
30+
31+
#include <cassert>
32+
#include <chrono>
33+
#include <concepts>
34+
#include <type_traits>
35+
36+
#include "test_macros.h"
37+
38+
namespace cr = std::chrono;
39+
40+
// Tests unique conversions. To make sure the test is does not depend on changes
41+
// in the database it uses a time zone with a fixed offset.
42+
static void test_unique() {
43+
// common_type_t<duration, seconds> -> duration
44+
{
45+
using duration = cr::nanoseconds;
46+
using sys_time_point = cr::sys_time<duration>;
47+
using local_time_point = cr::local_time<duration>;
48+
using zoned_time = cr::zoned_time<duration>;
49+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
50+
51+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
52+
assert(zt.get_sys_time() == sys_time_point{duration{42}});
53+
assert(zt.get_local_time() == local_time_point{duration{42} - cr::hours{1}});
54+
55+
std::same_as<zoned_time&> decltype(auto) result = zt = local_time_point{duration{99}};
56+
assert(&result == &zt);
57+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
58+
assert(zt.get_sys_time() == sys_time_point{duration{99} + cr::hours{1}});
59+
assert(zt.get_local_time() == local_time_point{duration{99}});
60+
}
61+
{
62+
using duration = cr::microseconds;
63+
using sys_time_point = cr::sys_time<duration>;
64+
using local_time_point = cr::local_time<duration>;
65+
using zoned_time = cr::zoned_time<duration>;
66+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
67+
68+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
69+
assert(zt.get_sys_time() == sys_time_point{duration{42}});
70+
assert(zt.get_local_time() == local_time_point{duration{42} - cr::hours{1}});
71+
72+
std::same_as<zoned_time&> decltype(auto) result = zt = local_time_point{duration{99}};
73+
assert(&result == &zt);
74+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
75+
assert(zt.get_sys_time() == sys_time_point{duration{99} + cr::hours{1}});
76+
assert(zt.get_local_time() == local_time_point{duration{99}});
77+
}
78+
{
79+
using duration = cr::milliseconds;
80+
using sys_time_point = cr::sys_time<duration>;
81+
using local_time_point = cr::local_time<duration>;
82+
using zoned_time = cr::zoned_time<duration>;
83+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
84+
85+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
86+
assert(zt.get_sys_time() == sys_time_point{duration{42}});
87+
assert(zt.get_local_time() == local_time_point{duration{42} - cr::hours{1}});
88+
89+
std::same_as<zoned_time&> decltype(auto) result = zt = local_time_point{duration{99}};
90+
assert(&result == &zt);
91+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
92+
assert(zt.get_sys_time() == sys_time_point{duration{99} + cr::hours{1}});
93+
assert(zt.get_local_time() == local_time_point{duration{99}});
94+
}
95+
// common_type_t<seconds, seconds> -> seconds
96+
{
97+
using duration = cr::seconds;
98+
using sys_time_point = cr::sys_time<duration>;
99+
using local_time_point = cr::local_time<duration>;
100+
using zoned_time = cr::zoned_time<duration>;
101+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
102+
103+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
104+
assert(zt.get_sys_time() == sys_time_point{duration{42}});
105+
assert(zt.get_local_time() == local_time_point{duration{42} - cr::hours{1}});
106+
107+
std::same_as<zoned_time&> decltype(auto) result = zt = local_time_point{duration{99}};
108+
assert(&result == &zt);
109+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
110+
assert(zt.get_sys_time() == sys_time_point{duration{99} + cr::hours{1}});
111+
assert(zt.get_local_time() == local_time_point{duration{99}});
112+
}
113+
// common_type_t<duration, seconds> -> seconds
114+
{
115+
using duration = cr::days;
116+
using sys_time_point = cr::sys_time<duration>;
117+
using local_time_point = cr::local_time<duration>;
118+
using zoned_time = cr::zoned_time<duration>;
119+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
120+
121+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
122+
assert(zt.get_sys_time() == cr::sys_seconds{duration{42}});
123+
assert(zt.get_local_time() == cr::local_seconds{duration{42} - cr::hours{1}});
124+
125+
std::same_as<zoned_time&> decltype(auto) result = zt = local_time_point{duration{99}};
126+
assert(&result == &zt);
127+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
128+
assert(zt.get_sys_time() == cr::sys_seconds{duration{99} + cr::hours{1}});
129+
assert(zt.get_local_time() == cr::local_seconds{duration{99}});
130+
}
131+
{
132+
using duration = cr::weeks;
133+
using sys_time_point = cr::sys_time<duration>;
134+
using local_time_point = cr::local_time<duration>;
135+
using zoned_time = cr::zoned_time<duration>;
136+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
137+
138+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
139+
assert(zt.get_sys_time() == cr::sys_seconds{duration{42}});
140+
assert(zt.get_local_time() == cr::local_seconds{duration{42} - cr::hours{1}});
141+
142+
std::same_as<zoned_time&> decltype(auto) result = zt = local_time_point{duration{99}};
143+
assert(&result == &zt);
144+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
145+
assert(zt.get_sys_time() == cr::sys_seconds{duration{99} + cr::hours{1}});
146+
assert(zt.get_local_time() == cr::local_seconds{duration{99}});
147+
}
148+
/* This does not work; due to using __tp_ = __zone_->to_sys(__tp);
149+
* Here the ambiguous/non-existent exception can't stream months and years,
150+
* leading to a compilation error.
151+
{
152+
using duration = cr::months;
153+
using sys_time_point = cr::sys_time<duration>;
154+
using local_time_point = cr::local_time<duration>;
155+
using zoned_time = cr::zoned_time<duration>;
156+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
157+
158+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
159+
assert(zt.get_sys_time() == cr::sys_seconds{duration{42}});
160+
assert(zt.get_local_time() == cr::local_seconds{duration{42} - cr::hours{1}});
161+
162+
std::same_as<zoned_time&> decltype(auto) result= zt = local_time_point{duration{99}};
163+
assert(&result == &zt);
164+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
165+
assert(zt.get_sys_time() == cr::sys_seconds{duration{99} + cr::hours{1}});
166+
assert(zt.get_local_time() == cr::local_seconds{duration{99}});
167+
} */
168+
}
169+
170+
// Tests non-existent conversions.
171+
static void test_nonexistent() {
172+
#ifndef TEST_HAS_NO_EXCEPTIONS
173+
using namespace std::literals::chrono_literals;
174+
175+
const cr::time_zone* tz = cr::locate_zone("Europe/Berlin");
176+
177+
// Z Europe/Berlin 0:53:28 - LMT 1893 Ap
178+
// ...
179+
// 1 DE CE%sT 1980
180+
// 1 E CE%sT
181+
//
182+
// ...
183+
// R E 1981 ma - Mar lastSu 1u 1 S
184+
// R E 1996 ma - O lastSu 1u 0 -
185+
186+
// Pick an historic date where it's well known what the time zone rules were.
187+
// This makes it unlikely updates to the database change these rules.
188+
cr::local_time<cr::seconds> time{(cr::sys_days{cr::March / 30 / 1986} + 2h + 30min).time_since_epoch()};
189+
190+
using duration = cr::seconds;
191+
using zoned_time = cr::zoned_time<duration>;
192+
zoned_time zt{tz};
193+
194+
bool thrown = false;
195+
try {
196+
std::same_as<zoned_time&> decltype(auto) result = zt = time;
197+
assert(&result == &zt);
198+
} catch (const cr::nonexistent_local_time&) {
199+
thrown = true;
200+
}
201+
// There is no system type that can represent the current local time. So the
202+
// assertion passes. The current implementation throws an exception too.
203+
assert(zt.get_local_time() != time);
204+
assert(thrown);
205+
#endif // TEST_HAS_NO_EXCEPTIONS
206+
}
207+
208+
// Tests ambiguous conversions.
209+
static void test_ambiguous() {
210+
#ifndef TEST_HAS_NO_EXCEPTIONS
211+
using namespace std::literals::chrono_literals;
212+
213+
const cr::time_zone* tz = cr::locate_zone("Europe/Berlin");
214+
215+
// Z Europe/Berlin 0:53:28 - LMT 1893 Ap
216+
// ...
217+
// 1 DE CE%sT 1980
218+
// 1 E CE%sT
219+
//
220+
// ...
221+
// R E 1981 ma - Mar lastSu 1u 1 S
222+
// R E 1996 ma - O lastSu 1u 0 -
223+
224+
// Pick an historic date where it's well known what the time zone rules were.
225+
// This makes it unlikely updates to the database change these rules.
226+
cr::local_time<cr::seconds> time{(cr::sys_days{cr::September / 28 / 1986} + 2h + 30min).time_since_epoch()};
227+
228+
using duration = cr::seconds;
229+
using zoned_time = cr::zoned_time<duration>;
230+
zoned_time zt{tz};
231+
232+
bool thrown = false;
233+
try {
234+
std::same_as<zoned_time&> decltype(auto) result = zt = time;
235+
assert(&result == &zt);
236+
} catch (const cr::ambiguous_local_time&) {
237+
thrown = true;
238+
}
239+
// There is no system type that can represent the current local time. So the
240+
// assertion passes. The current implementation throws an exception too.
241+
assert(zt.get_local_time() != time);
242+
assert(thrown);
243+
#endif // TEST_HAS_NO_EXCEPTIONS
244+
}
245+
246+
int main(int, char**) {
247+
test_unique();
248+
test_nonexistent();
249+
test_ambiguous();
250+
251+
return 0;
252+
}

0 commit comments

Comments
 (0)