Skip to content

Commit 775f545

Browse files
committed
[libc++][TZDB] Implements time_zone::to_sys.
This implements the overload with the choose argument and adds this enum. Implements parts of: - P0355 Extending chrono to Calendars and Time Zones
1 parent d7b4271 commit 775f545

File tree

7 files changed

+264
-3
lines changed

7 files changed

+264
-3
lines changed

libcxx/include/__chrono/time_zone.h

+32
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ _LIBCPP_BEGIN_NAMESPACE_STD
4242

4343
namespace chrono {
4444

45+
enum class choose { earliest, latest };
46+
4547
class _LIBCPP_AVAILABILITY_TZDB time_zone {
4648
_LIBCPP_HIDE_FROM_ABI time_zone() = default;
4749

@@ -96,6 +98,36 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone {
9698
return {};
9799
}
98100

101+
template <class _Duration>
102+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI sys_time<common_type_t<_Duration, seconds>>
103+
to_sys(const local_time<_Duration>& __time, choose __z) const {
104+
local_info __info = get_info(__time);
105+
switch (__info.result) {
106+
case local_info::unique:
107+
case local_info::nonexistent: // first and second are the same
108+
return sys_time<common_type_t<_Duration, seconds>>{__time.time_since_epoch() - __info.first.offset};
109+
110+
case local_info::ambiguous:
111+
switch (__z) {
112+
case choose::earliest:
113+
return sys_time<common_type_t<_Duration, seconds>>{__time.time_since_epoch() - __info.first.offset};
114+
115+
case choose::latest:
116+
return sys_time<common_type_t<_Duration, seconds>>{__time.time_since_epoch() - __info.second.offset};
117+
118+
// Note a value out of bounds is not specified.
119+
}
120+
}
121+
122+
// TODO TZDB The standard does not specify anything in these cases.
123+
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
124+
__info.result != -1, "cannot convert the local time; it would be before the minimum system clock value");
125+
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
126+
__info.result != -2, "cannot convert the local time; it would be after the maximum system clock value");
127+
128+
return {};
129+
}
130+
99131
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI const __impl& __implementation() const noexcept { return *__impl_; }
100132

101133
private:

libcxx/include/chrono

+4
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,10 @@ class time_zone {
774774
template<class Duration>
775775
sys_time<common_type_t<Duration, seconds>>
776776
to_sys(const local_time<Duration>& tp) const;
777+
778+
template<class Duration>
779+
sys_time<common_type_t<Duration, seconds>>
780+
to_sys(const local_time<Duration>& tp, choose z) const;
777781
};
778782
bool operator==(const time_zone& x, const time_zone& y) noexcept; // C++20
779783
strong_ordering operator<=>(const time_zone& x, const time_zone& y) noexcept; // C++20

libcxx/modules/std/chrono.inc

-3
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,8 @@ export namespace std {
216216
using std::chrono::local_info;
217217
using std::chrono::sys_info;
218218

219-
# if 0
220219
// [time.zone.timezone], class time_zone
221220
using std::chrono::choose;
222-
# endif // if 0
223-
224221
using std::chrono::time_zone;
225222

226223
# if 0

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

+3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,12 @@ void test() {
4949
{
5050
std::chrono::sys_seconds s{};
5151
std::chrono::local_seconds l{};
52+
std::chrono::choose z = std::chrono::choose::earliest;
5253
tz.name(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
5354
tz.get_info(s); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
5455
tz.get_info(l); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
56+
tz.to_sys(l); // not nodiscard
57+
tz.to_sys(l, z); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
5558
operator==(tz, tz); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
5659
operator<=>(tz, tz); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
5760
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
// enaum class choose;
18+
19+
#include <chrono>
20+
#include <type_traits>
21+
#include <cassert>
22+
23+
#include "test_macros.h"
24+
25+
int main(int, char**) {
26+
using E = std::chrono::choose;
27+
static_assert(std::is_enum_v<E>);
28+
29+
// Check that E is a scoped enum by checking for conversions.
30+
using UT = std::underlying_type_t<E>;
31+
static_assert(!std::is_convertible_v<E, UT>);
32+
33+
[[maybe_unused]] const E& early = E::earliest;
34+
[[maybe_unused]] const E& late = E::latest;
35+
36+
return 0;
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
11+
// REQUIRES: has-unix-headers
12+
// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
13+
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
14+
15+
// XFAIL: libcpp-has-no-experimental-tzdb
16+
17+
// <chrono>
18+
19+
// template <class _Duration>
20+
// sys_time<common_type_t<Duration, seconds>>
21+
// to_sys(const local_time<Duration>& tp, choose z) const;
22+
23+
#include <chrono>
24+
25+
#include "check_assertion.h"
26+
27+
// Tests values that cannot be converted. To make sure the test is does not depend on changes
28+
// in the database it uses a time zone with a fixed offset.
29+
int main(int, char**) {
30+
TEST_LIBCPP_ASSERT_FAILURE(
31+
std::chrono::locate_zone("Etc/GMT-1")->to_sys(std::chrono::local_seconds::min(), std::chrono::choose::earliest),
32+
"cannot convert the local time; it would be before the minimum system clock value");
33+
34+
// TODO TZDB look why std::chrono::local_seconds::max() fails
35+
TEST_LIBCPP_ASSERT_FAILURE(
36+
std::chrono::locate_zone("Etc/GMT+1")
37+
->to_sys(std::chrono::local_seconds::max() - std::chrono::seconds(1), std::chrono::choose::latest),
38+
"cannot convert the local time; it would be after the maximum system clock value");
39+
40+
return 0;
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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+
// class time_zone;
18+
19+
// template <class _Duration>
20+
// sys_time<common_type_t<Duration, seconds>>
21+
// to_sys(const local_time<Duration>& tp, choose z) const;
22+
23+
#include <chrono>
24+
#include <format>
25+
#include <cassert>
26+
#include <string_view>
27+
28+
#include "test_macros.h"
29+
30+
// Tests unique conversions. To make sure the test is does not depend on changes
31+
// in the database it uses a time zone with a fixed offset.
32+
static void test_unique() {
33+
using namespace std::literals::chrono_literals;
34+
35+
const std::chrono::time_zone* tz = std::chrono::locate_zone("Etc/GMT+1");
36+
37+
assert(tz->to_sys(std::chrono::local_time<std::chrono::nanoseconds>{-1ns}, std::chrono::choose::earliest) ==
38+
std::chrono::sys_time<std::chrono::nanoseconds>{-1ns + 1h});
39+
40+
assert(tz->to_sys(std::chrono::local_time<std::chrono::microseconds>{0us}, std::chrono::choose::latest) ==
41+
std::chrono::sys_time<std::chrono::microseconds>{1h});
42+
43+
assert(tz->to_sys(
44+
std::chrono::local_time<std::chrono::seconds>{
45+
(std::chrono::sys_days{std::chrono::January / 1 / -21970}).time_since_epoch()},
46+
std::chrono::choose::earliest) ==
47+
std::chrono::sys_time<std::chrono::seconds>{
48+
(std::chrono::sys_days{std::chrono::January / 1 / -21970}).time_since_epoch() + 1h});
49+
50+
// sys_time<common_type_t<Duration, seconds>> is seconds for the larger types
51+
assert(tz->to_sys(
52+
std::chrono::local_time<std::chrono::days>{
53+
(std::chrono::sys_days{std::chrono::January / 1 / 21970}).time_since_epoch()},
54+
std::chrono::choose::latest) ==
55+
std::chrono::sys_time<std::chrono::seconds>{
56+
(std::chrono::sys_days{std::chrono::January / 1 / 21970}).time_since_epoch() + 1h});
57+
58+
assert(tz->to_sys(std::chrono::local_time<std::chrono::weeks>{}, std::chrono::choose::earliest) ==
59+
std::chrono::sys_time<std::chrono::seconds>{
60+
(std::chrono::sys_days{std::chrono::January / 1 / 1970}).time_since_epoch() + 1h});
61+
62+
// Note months and years cannot be streamed; however these functions don't
63+
// throw an exception and thus can be used.
64+
assert(tz->to_sys(std::chrono::local_time<std::chrono::months>{}, std::chrono::choose::latest) ==
65+
std::chrono::sys_time<std::chrono::seconds>{
66+
(std::chrono::sys_days{std::chrono::January / 1 / 1970}).time_since_epoch() + 1h});
67+
68+
assert(tz->to_sys(std::chrono::local_time<std::chrono::years>{}, std::chrono::choose::earliest) ==
69+
std::chrono::sys_time<std::chrono::seconds>{
70+
(std::chrono::sys_days{std::chrono::January / 1 / 1970}).time_since_epoch() + 1h});
71+
}
72+
73+
// Tests non-existant conversions.
74+
static void test_nonexistent() {
75+
using namespace std::literals::chrono_literals;
76+
77+
const std::chrono::time_zone* tz = std::chrono::locate_zone("Europe/Berlin");
78+
79+
// Z Europe/Berlin 0:53:28 - LMT 1893 Ap
80+
// ...
81+
// 1 DE CE%sT 1980
82+
// 1 E CE%sT
83+
//
84+
// ...
85+
// R E 1981 ma - Mar lastSu 1u 1 S
86+
// R E 1996 ma - O lastSu 1u 0 -
87+
88+
// Pick an historic date where it's well known what the time zone rules were.
89+
// This makes it unlikely updates to the database change these rules.
90+
std::chrono::local_time<std::chrono::seconds> time{
91+
(std::chrono::sys_days{std::chrono::March / 30 / 1986} + 2h + 30min).time_since_epoch()};
92+
93+
std::chrono::sys_seconds expected{time.time_since_epoch() - 1h};
94+
95+
// Validates whether the database did not change.
96+
std::chrono::local_info info = tz->get_info(time);
97+
assert(info.result == std::chrono::local_info::nonexistent);
98+
99+
assert(tz->to_sys(time + 0ns, std::chrono::choose::earliest) == expected);
100+
assert(tz->to_sys(time + 0us, std::chrono::choose::latest) == expected);
101+
assert(tz->to_sys(time + 0ms, std::chrono::choose::earliest) == expected);
102+
assert(tz->to_sys(time + 0s, std::chrono::choose::latest) == expected);
103+
}
104+
105+
// Tests ambiguous conversions.
106+
static void test_ambiguous() {
107+
using namespace std::literals::chrono_literals;
108+
109+
const std::chrono::time_zone* tz = std::chrono::locate_zone("Europe/Berlin");
110+
111+
// Z Europe/Berlin 0:53:28 - LMT 1893 Ap
112+
// ...
113+
// 1 DE CE%sT 1980
114+
// 1 E CE%sT
115+
//
116+
// ...
117+
// R E 1981 ma - Mar lastSu 1u 1 S
118+
// R E 1996 ma - O lastSu 1u 0 -
119+
120+
// Pick an historic date where it's well known what the time zone rules were.
121+
// This makes it unlikely updates to the database change these rules.
122+
std::chrono::local_time<std::chrono::seconds> time{
123+
(std::chrono::sys_days{std::chrono::September / 28 / 1986} + 2h + 30min).time_since_epoch()};
124+
125+
std::chrono::sys_seconds earlier{time.time_since_epoch() - 2h};
126+
std::chrono::sys_seconds later{time.time_since_epoch() - 1h};
127+
128+
// Validates whether the database did not change.
129+
std::chrono::local_info info = tz->get_info(time);
130+
assert(info.result == std::chrono::local_info::ambiguous);
131+
132+
assert(tz->to_sys(time + 0ns, std::chrono::choose::earliest) == earlier);
133+
assert(tz->to_sys(time + 0us, std::chrono::choose::latest) == later);
134+
assert(tz->to_sys(time + 0ms, std::chrono::choose::earliest) == earlier);
135+
assert(tz->to_sys(time + 0s, std::chrono::choose::latest) == later);
136+
}
137+
138+
// This test does the basic validations of this function. The library function
139+
// uses `local_info get_info(const local_time<Duration>& tp)` as implementation
140+
// detail. The get_info function does extensive testing of the data.
141+
int main(int, char**) {
142+
test_unique();
143+
test_nonexistent();
144+
test_ambiguous();
145+
146+
return 0;
147+
}

0 commit comments

Comments
 (0)