Skip to content

Commit 3dcb187

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 3dcb187

File tree

8 files changed

+857
-2
lines changed

8 files changed

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

0 commit comments

Comments
 (0)