Skip to content

Commit 7a742c0

Browse files
committed
[libc++][chrono] Completes the tzdb class.
It adds the missing member functions of the tzdb class and adds the free functions that use these member functions. Implements parts of: - P0355 Extending <chrono> to Calendars and Time Zones
1 parent b9094b8 commit 7a742c0

File tree

11 files changed

+409
-2
lines changed

11 files changed

+409
-2
lines changed

libcxx/include/__chrono/tzdb.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// Enable the contents of the header only when libc++ was built with experimental features enabled.
1717
#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB)
1818

19+
# include <__algorithm/ranges_lower_bound.h>
1920
# include <__chrono/leap_second.h>
2021
# include <__chrono/time_zone.h>
2122
# include <__chrono/time_zone_link.h>
@@ -43,6 +44,40 @@ struct tzdb {
4344
vector<time_zone_link> links;
4445

4546
vector<leap_second> leap_seconds;
47+
48+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI const time_zone* __locate_zone(string_view __name) const {
49+
if (const time_zone* __result = __find_in_zone(__name); __result)
50+
return __result;
51+
52+
if (auto __it = ranges::lower_bound(links, __name, {}, &time_zone_link::name);
53+
__it != links.end() && __it->name() == __name)
54+
if (const time_zone* __result = __find_in_zone(__it->target()); __result)
55+
return __result;
56+
57+
return nullptr;
58+
}
59+
60+
_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI const time_zone* locate_zone(string_view __name) const {
61+
if (const time_zone* __result = __locate_zone(__name))
62+
return __result;
63+
64+
std::__throw_runtime_error("tzdb: requested time zone not found");
65+
}
66+
67+
_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI const time_zone* current_zone() const {
68+
return __current_zone();
69+
}
70+
71+
private:
72+
_LIBCPP_HIDE_FROM_ABI const time_zone* __find_in_zone(string_view __name) const noexcept {
73+
if (auto __it = ranges::lower_bound(zones, __name, {}, &time_zone::name);
74+
__it != zones.end() && __it->name() == __name)
75+
return std::addressof(*__it);
76+
77+
return nullptr;
78+
}
79+
80+
[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const time_zone* __current_zone() const;
4681
};
4782

4883
} // namespace chrono

libcxx/include/__chrono/tzdb_list.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#if !defined(_LIBCPP_HAS_NO_INCOMPLETE_TZDB)
1818

1919
# include <__availability>
20+
# include <__chrono/time_zone.h>
2021
# include <__chrono/tzdb.h>
2122
# include <__config>
2223
# include <__fwd/string.h>
@@ -74,6 +75,15 @@ _LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI inline con
7475
return get_tzdb_list().front();
7576
}
7677

78+
_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI inline const time_zone*
79+
locate_zone(string_view __name) {
80+
return get_tzdb().locate_zone(__name);
81+
}
82+
83+
_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_HIDE_FROM_ABI inline const time_zone* current_zone() {
84+
return get_tzdb().current_zone();
85+
}
86+
7787
_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb();
7888

7989
_LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI string remote_version();

libcxx/include/chrono

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,9 @@ struct tzdb {
689689
vector<time_zone> zones;
690690
vector<time_zone_link> links;
691691
vector<leap_second> leap_seconds;
692+
693+
const time_zone* locate_zone(string_view tz_name) const;
694+
const time_zone* current_zone() const;
692695
};
693696
694697
class tzdb_list { // C++20
@@ -714,6 +717,8 @@ public:
714717
// [time.zone.db.access], time zone database access
715718
const tzdb& get_tzdb(); // C++20
716719
tzdb_list& get_tzdb_list(); // C++20
720+
const time_zone* locate_zone(string_view tz_name); // C++20
721+
const time_zone* current_zone() // C++20
717722
718723
// [time.zone.db.remote], remote time zone database support
719724
const tzdb& reload_tzdb(); // C++20

libcxx/modules/std/chrono.inc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,10 @@ export namespace std {
199199
using std::chrono::tzdb_list;
200200

201201
// [time.zone.db.access], time zone database access
202-
// using std::chrono::current_zone;
202+
using std::chrono::current_zone;
203203
using std::chrono::get_tzdb;
204204
using std::chrono::get_tzdb_list;
205-
// using std::chrono::locate_zone;
205+
using std::chrono::locate_zone;
206206

207207
// [time.zone.db.remote], remote time zone database support
208208
using std::chrono::reload_tzdb;

libcxx/src/tzdb.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,57 @@ void __init_tzdb(tzdb& __tzdb, __tz::__rules_storage_type& __rules) {
675675
std::ranges::sort(__tzdb.leap_seconds);
676676
}
677677

678+
#ifdef _WIN32
679+
[[nodiscard]] static const time_zone* __current_zone_windows(const tzdb& tzdb) {
680+
// TODO TZDB Implement this on Windows.
681+
std::__throw_runtime_error("unknown time zone");
682+
}
683+
#else // ifdef _WIN32
684+
[[nodiscard]] static const time_zone* __current_zone_posix(const tzdb& tzdb) {
685+
// On POSIX systems there are several ways to configure the time zone.
686+
// In order of priority they are:
687+
// - TZ environment variable
688+
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08
689+
// The documentation is unclear whether or not it's allowed to
690+
// change time zone information. For example the TZ string
691+
// MST7MDT
692+
// this is an entry in tzdata.zi. The value
693+
// MST
694+
// is also an entry. Is it allowed to use the following?
695+
// MST-3
696+
// Even when this is valid there is no time_zone record in the
697+
// database. Since the library would need to return a valid pointer,
698+
// this means the library needs to allocate and leak a pointer.
699+
//
700+
// - The time zone name is the target of the symlink /etc/localtime
701+
// relative to /usr/share/zoneinfo/
702+
703+
// The algorithm is like this:
704+
// - If the environment variable TZ is set and points to a valid
705+
// record use this value.
706+
// - Else use the timezone name.
707+
708+
if (const char* __tz = getenv("TZ"))
709+
if (const time_zone* __result = tzdb.__locate_zone(__tz))
710+
return __result;
711+
712+
filesystem::path __path = "/etc/localtime";
713+
if (!std::filesystem::exists(__path))
714+
std::__throw_runtime_error("the symlink '/etc/localtime' does not exist");
715+
716+
if (!std::filesystem::is_symlink(__path))
717+
std::__throw_runtime_error("the path '/etc/localtime' is not a symlink");
718+
719+
filesystem::path __tz = filesystem::read_symlink(__path);
720+
string __name = filesystem::relative(__tz, "/usr/share/zoneinfo/");
721+
722+
if (const time_zone* __result = tzdb.__locate_zone(__name))
723+
return __result;
724+
725+
std::__throw_runtime_error(("the time zone '" + __name + "' is not found in the database").c_str());
726+
}
727+
#endif // _WIN32
728+
678729
//===----------------------------------------------------------------------===//
679730
// Public API
680731
//===----------------------------------------------------------------------===//
@@ -684,6 +735,14 @@ _LIBCPP_NODISCARD_EXT _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI tzdb_l
684735
return __result;
685736
}
686737

738+
[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const time_zone* tzdb::__current_zone() const {
739+
#ifdef _WIN32
740+
return chrono::__current_zone_windows(*this);
741+
#else
742+
return chrono::__current_zone_posix(*this);
743+
#endif
744+
}
745+
687746
_LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI const tzdb& reload_tzdb() {
688747
if (chrono::remote_version() == chrono::get_tzdb().version)
689748
return chrono::get_tzdb();

libcxx/test/libcxx/diagnostics/chrono.nodiscard_extensions.compile.pass.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,16 @@ void test() {
3838

3939
std::chrono::get_tzdb_list();
4040
std::chrono::get_tzdb();
41+
std::chrono::locate_zone("name");
42+
std::chrono::current_zone();
4143
std::chrono::remote_version();
4244

45+
{
46+
const std::chrono::tzdb& t = list.front();
47+
t.locate_zone("name");
48+
t.current_zone();
49+
}
50+
4351
{
4452
tz.name();
4553
operator==(tz, tz);

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,17 @@ void test() {
3333
list.cbegin(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
3434
list.cend(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
3535

36+
{
37+
const std::chrono::tzdb& t = list.front();
38+
t.locate_zone("name"); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
39+
t.current_zone(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
40+
}
41+
3642
namespace crno = std::chrono;
3743
crno::get_tzdb_list(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
3844
crno::get_tzdb(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
45+
crno::locate_zone("n"); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
46+
crno::current_zone(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
3947
crno::remote_version(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
4048

4149
{
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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-incomplete-tzdb
13+
// XFAIL: availability-tzdb-missing
14+
15+
// <chrono>
16+
17+
// const time_zone* current_zone();
18+
19+
#include <cassert>
20+
#include <chrono>
21+
#include <string_view>
22+
#include <stdlib.h>
23+
24+
#include "test_macros.h"
25+
#include "assert_macros.h"
26+
#include "concat_macros.h"
27+
28+
#ifdef _WIN32
29+
static void set_tz(std::string zone) {
30+
// Note Windows does not have setenv, only putenv
31+
// https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/putenv-s-wputenv-s?view=msvc-170
32+
// Unlike POSIX it does not mention the string of putenv becomes part
33+
// of the environment.
34+
35+
int status = _putenv_s("TZ", zone.c_str(), 1);
36+
assert(status == 0);
37+
}
38+
39+
#else
40+
static void set_tz(const std::string& zone) {
41+
int status = setenv("TZ", zone.c_str(), 1);
42+
assert(status == 0);
43+
}
44+
#endif
45+
46+
static void test_zone(const std::string& zone) {
47+
set_tz(zone);
48+
const std::chrono::time_zone* tz = std::chrono::current_zone();
49+
assert(tz);
50+
assert(tz->name() == zone);
51+
}
52+
53+
static void test_link(const std::string& link, std::string_view zone) {
54+
set_tz(link);
55+
const std::chrono::time_zone* tz = std::chrono::current_zone();
56+
assert(tz);
57+
assert(tz->name() == zone);
58+
}
59+
60+
int main(int, const char**) {
61+
const std::chrono::time_zone* tz = std::chrono::current_zone();
62+
// Returns a valid time zone, the value depends on the OS settings.
63+
assert(tz);
64+
// setting the environment to an invalid value returns the value of
65+
// the OS setting.
66+
set_tz("This is not a time zone");
67+
assert(tz == std::chrono::current_zone());
68+
69+
const std::chrono::tzdb& db = std::chrono::get_tzdb();
70+
for (const auto& zone : db.zones)
71+
test_zone(std::string{zone.name()});
72+
73+
for (const auto& link : db.links)
74+
test_link(std::string{link.name()}, link.target());
75+
76+
return 0;
77+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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-incomplete-tzdb
13+
// XFAIL: availability-tzdb-missing
14+
15+
// <chrono>
16+
17+
// const time_zone* locate_zone(string_view tz_name);
18+
19+
#include <cassert>
20+
#include <chrono>
21+
#include <string_view>
22+
23+
#include "test_macros.h"
24+
#include "assert_macros.h"
25+
#include "concat_macros.h"
26+
27+
static void test_zone(std::string_view zone) {
28+
const std::chrono::time_zone* tz = std::chrono::locate_zone(zone);
29+
assert(tz);
30+
assert(tz->name() == zone);
31+
}
32+
33+
static void test_link(std::string_view link, std::string_view zone) {
34+
const std::chrono::time_zone* tz = std::chrono::locate_zone(link);
35+
assert(tz);
36+
assert(tz->name() == zone);
37+
}
38+
39+
static void test_exception([[maybe_unused]] std::string_view zone) {
40+
TEST_VALIDATE_EXCEPTION(
41+
std::runtime_error,
42+
[&]([[maybe_unused]] const std::runtime_error& e) {
43+
std::string_view what{"tzdb: requested time zone not found"};
44+
TEST_LIBCPP_REQUIRE(
45+
e.what() == what,
46+
TEST_WRITE_CONCATENATED("\nExpected exception ", what, "\nActual exception ", e.what(), '\n'));
47+
},
48+
TEST_IGNORE_NODISCARD std::chrono::locate_zone(zone));
49+
}
50+
51+
int main(int, const char**) {
52+
const std::chrono::tzdb& db = std::chrono::get_tzdb();
53+
for (const auto& zone : db.zones)
54+
test_zone(zone.name());
55+
56+
for (const auto& link : db.links)
57+
test_link(link.name(), link.target());
58+
59+
test_exception("This is not a time zone");
60+
61+
return 0;
62+
}

0 commit comments

Comments
 (0)