Skip to content

Commit 5195d39

Browse files
committed
[libc++][TZDB] Finishes zoned_time member functions.
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 6d6dade commit 5195d39

File tree

8 files changed

+855
-2
lines changed

8 files changed

+855
-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-existant or abiguous 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: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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+
[[maybe_unused]] std::same_as<zoned_time&> decltype(auto) _ = zt = local_time_point{duration{99}};
56+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
57+
assert(zt.get_sys_time() == sys_time_point{duration{99} + cr::hours{1}});
58+
assert(zt.get_local_time() == local_time_point{duration{99}});
59+
}
60+
{
61+
using duration = cr::microseconds;
62+
using sys_time_point = cr::sys_time<duration>;
63+
using local_time_point = cr::local_time<duration>;
64+
using zoned_time = cr::zoned_time<duration>;
65+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
66+
67+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
68+
assert(zt.get_sys_time() == sys_time_point{duration{42}});
69+
assert(zt.get_local_time() == local_time_point{duration{42} - cr::hours{1}});
70+
71+
[[maybe_unused]] std::same_as<zoned_time&> decltype(auto) _ = zt = local_time_point{duration{99}};
72+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
73+
assert(zt.get_sys_time() == sys_time_point{duration{99} + cr::hours{1}});
74+
assert(zt.get_local_time() == local_time_point{duration{99}});
75+
}
76+
{
77+
using duration = cr::milliseconds;
78+
using sys_time_point = cr::sys_time<duration>;
79+
using local_time_point = cr::local_time<duration>;
80+
using zoned_time = cr::zoned_time<duration>;
81+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
82+
83+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
84+
assert(zt.get_sys_time() == sys_time_point{duration{42}});
85+
assert(zt.get_local_time() == local_time_point{duration{42} - cr::hours{1}});
86+
87+
[[maybe_unused]] std::same_as<zoned_time&> decltype(auto) _ = zt = local_time_point{duration{99}};
88+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
89+
assert(zt.get_sys_time() == sys_time_point{duration{99} + cr::hours{1}});
90+
assert(zt.get_local_time() == local_time_point{duration{99}});
91+
}
92+
// common_type_t<seconds, seconds> -> seconds
93+
{
94+
using duration = cr::seconds;
95+
using sys_time_point = cr::sys_time<duration>;
96+
using local_time_point = cr::local_time<duration>;
97+
using zoned_time = cr::zoned_time<duration>;
98+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
99+
100+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
101+
assert(zt.get_sys_time() == sys_time_point{duration{42}});
102+
assert(zt.get_local_time() == local_time_point{duration{42} - cr::hours{1}});
103+
104+
[[maybe_unused]] std::same_as<zoned_time&> decltype(auto) _ = zt = local_time_point{duration{99}};
105+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
106+
assert(zt.get_sys_time() == sys_time_point{duration{99} + cr::hours{1}});
107+
assert(zt.get_local_time() == local_time_point{duration{99}});
108+
}
109+
// common_type_t<duration, seconds> -> seconds
110+
{
111+
using duration = cr::days;
112+
using sys_time_point = cr::sys_time<duration>;
113+
using local_time_point = cr::local_time<duration>;
114+
using zoned_time = cr::zoned_time<duration>;
115+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
116+
117+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
118+
assert(zt.get_sys_time() == cr::sys_seconds{duration{42}});
119+
assert(zt.get_local_time() == cr::local_seconds{duration{42} - cr::hours{1}});
120+
121+
[[maybe_unused]] std::same_as<zoned_time&> decltype(auto) _ = zt = local_time_point{duration{99}};
122+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
123+
assert(zt.get_sys_time() == cr::sys_seconds{duration{99} + cr::hours{1}});
124+
assert(zt.get_local_time() == cr::local_seconds{duration{99}});
125+
}
126+
{
127+
using duration = cr::weeks;
128+
using sys_time_point = cr::sys_time<duration>;
129+
using local_time_point = cr::local_time<duration>;
130+
using zoned_time = cr::zoned_time<duration>;
131+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
132+
133+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
134+
assert(zt.get_sys_time() == cr::sys_seconds{duration{42}});
135+
assert(zt.get_local_time() == cr::local_seconds{duration{42} - cr::hours{1}});
136+
137+
[[maybe_unused]] std::same_as<zoned_time&> decltype(auto) _ = zt = local_time_point{duration{99}};
138+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
139+
assert(zt.get_sys_time() == cr::sys_seconds{duration{99} + cr::hours{1}});
140+
assert(zt.get_local_time() == cr::local_seconds{duration{99}});
141+
}
142+
/* This does not work; due to using __tp_ = __zone_->to_sys(__tp);
143+
* Here the ambiguous/non-existent exception can't stream months and years,
144+
* leading to a compilation error.
145+
{
146+
using duration = cr::months;
147+
using sys_time_point = cr::sys_time<duration>;
148+
using local_time_point = cr::local_time<duration>;
149+
using zoned_time = cr::zoned_time<duration>;
150+
zoned_time zt{"Etc/GMT+1", sys_time_point{duration{42}}};
151+
152+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
153+
assert(zt.get_sys_time() == cr::sys_seconds{duration{42}});
154+
assert(zt.get_local_time() == cr::local_seconds{duration{42} - cr::hours{1}});
155+
156+
[[maybe_unused]] std::same_as<zoned_time&> decltype(auto) _ = zt = local_time_point{duration{99}};
157+
assert(zt.get_time_zone() == cr::locate_zone("Etc/GMT+1"));
158+
assert(zt.get_sys_time() == cr::sys_seconds{duration{99} + cr::hours{1}});
159+
assert(zt.get_local_time() == cr::local_seconds{duration{99}});
160+
} */
161+
}
162+
163+
// Tests non-existant conversions.
164+
static void test_nonexistent() {
165+
#ifndef TEST_HAS_NO_EXCEPTIONS
166+
using namespace std::literals::chrono_literals;
167+
168+
const cr::time_zone* tz = cr::locate_zone("Europe/Berlin");
169+
170+
// Z Europe/Berlin 0:53:28 - LMT 1893 Ap
171+
// ...
172+
// 1 DE CE%sT 1980
173+
// 1 E CE%sT
174+
//
175+
// ...
176+
// R E 1981 ma - Mar lastSu 1u 1 S
177+
// R E 1996 ma - O lastSu 1u 0 -
178+
179+
// Pick an historic date where it's well known what the time zone rules were.
180+
// This makes it unlikely updates to the database change these rules.
181+
cr::local_time<cr::seconds> time{(cr::sys_days{cr::March / 30 / 1986} + 2h + 30min).time_since_epoch()};
182+
183+
using duration = cr::seconds;
184+
using zoned_time = cr::zoned_time<duration>;
185+
zoned_time zt{tz};
186+
187+
bool thrown = false;
188+
try {
189+
[[maybe_unused]] std::same_as<zoned_time&> decltype(auto) _ = zt = time;
190+
} catch (const cr::nonexistent_local_time&) {
191+
thrown = true;
192+
}
193+
// There is no system type that can represent the current local time. So the
194+
// assertion passes. The current implementation throws an exception too.
195+
assert(zt.get_local_time() != time);
196+
assert(thrown);
197+
#endif // TEST_HAS_NO_EXCEPTIONS
198+
}
199+
200+
// Tests ambiguous conversions.
201+
static void test_ambiguous() {
202+
#ifndef TEST_HAS_NO_EXCEPTIONS
203+
using namespace std::literals::chrono_literals;
204+
205+
const cr::time_zone* tz = cr::locate_zone("Europe/Berlin");
206+
207+
// Z Europe/Berlin 0:53:28 - LMT 1893 Ap
208+
// ...
209+
// 1 DE CE%sT 1980
210+
// 1 E CE%sT
211+
//
212+
// ...
213+
// R E 1981 ma - Mar lastSu 1u 1 S
214+
// R E 1996 ma - O lastSu 1u 0 -
215+
216+
// Pick an historic date where it's well known what the time zone rules were.
217+
// This makes it unlikely updates to the database change these rules.
218+
cr::local_time<cr::seconds> time{(cr::sys_days{cr::September / 28 / 1986} + 2h + 30min).time_since_epoch()};
219+
220+
using duration = cr::seconds;
221+
using zoned_time = cr::zoned_time<duration>;
222+
zoned_time zt{tz};
223+
224+
bool thrown = false;
225+
try {
226+
[[maybe_unused]] std::same_as<zoned_time&> decltype(auto) _ = zt = time;
227+
} catch (const cr::ambiguous_local_time&) {
228+
thrown = true;
229+
}
230+
// There is no system type that can represent the current local time. So the
231+
// assertion passes. The current implementation throws an exception too.
232+
assert(zt.get_local_time() != time);
233+
assert(thrown);
234+
#endif // TEST_HAS_NO_EXCEPTIONS
235+
}
236+
237+
int main(int, char**) {
238+
test_unique();
239+
test_nonexistent();
240+
test_ambiguous();
241+
242+
return 0;
243+
}

0 commit comments

Comments
 (0)