Skip to content

Commit 368f0d4

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 e374d90 commit 368f0d4

File tree

8 files changed

+846
-2
lines changed

8 files changed

+846
-2
lines changed

libcxx/include/__chrono/zoned_time.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,29 @@ class zoned_time {
146146
} && is_convertible_v<sys_time<_Duration2>, sys_time<_Duration>>)
147147
: zoned_time{__traits::locate_zone(__name), __zt, __c} {}
148148

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

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