Skip to content

Commit 5ba9e6e

Browse files
committed
[libc++][TZDB] Implements time zone get_info(local_time).
Implements parts of: - P0355 Extending to Calendars and Time Zones
1 parent 4e9decf commit 5ba9e6e

File tree

5 files changed

+1489
-0
lines changed

5 files changed

+1489
-0
lines changed

libcxx/include/__chrono/time_zone.h

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

1919
# include <__chrono/duration.h>
20+
# include <__chrono/local_info.h>
2021
# include <__chrono/sys_info.h>
2122
# include <__chrono/system_clock.h>
2223
# include <__compare/strong_order.h>
@@ -63,12 +64,18 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone {
6364
return __get_info(chrono::time_point_cast<seconds>(__time));
6465
}
6566

67+
template <class _Duration>
68+
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI local_info get_info(const local_time<_Duration>& __time) const {
69+
return __get_info(chrono::time_point_cast<seconds>(__time));
70+
}
71+
6672
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI const __impl& __implementation() const noexcept { return *__impl_; }
6773

6874
private:
6975
[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string_view __name() const noexcept;
7076

7177
[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI sys_info __get_info(sys_seconds __time) const;
78+
[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI local_info __get_info(local_seconds __time) const;
7279

7380
unique_ptr<__impl> __impl_;
7481
};

libcxx/include/chrono

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,9 @@ class time_zone {
763763
764764
template<class Duration>
765765
sys_info get_info(const sys_time<Duration>& st) const;
766+
767+
template<class Duration>
768+
local_info get_info(const local_time<Duration>& tp) const;
766769
};
767770
bool operator==(const time_zone& x, const time_zone& y) noexcept; // C++20
768771
strong_ordering operator<=>(const time_zone& x, const time_zone& y) noexcept; // C++20

libcxx/src/time_zone.cpp

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include <chrono>
3535
#include <expected>
3636
#include <map>
37+
#include <numeric>
3738
#include <ranges>
3839

3940
#include "include/tzdb/time_zone_private.h"
@@ -903,6 +904,180 @@ time_zone::__get_info(sys_seconds __time) const {
903904
std::__throw_runtime_error("tzdb: corrupt db");
904905
}
905906

907+
enum class __position {
908+
__beginning,
909+
__middle,
910+
__end,
911+
};
912+
913+
// Determines the position of "__time" inside "__info".
914+
//
915+
// The code picks an arbitrary value to determine the "middle"
916+
// - Every time that is more than the threshold from a boundary, or
917+
// - Every value that is at the boundary sys_seconds::min() or
918+
// sys_seconds::max().
919+
//
920+
// If not in the middle, it returns __beginning or __end.
921+
[[nodiscard]] static __position __get_position(sys_seconds __time, const sys_info __info) {
922+
_LIBCPP_ASSERT_ARGUMENT_WITHIN_DOMAIN(
923+
__time >= __info.begin && __time < __info.end, "A value outside the range's position can't be determined.");
924+
925+
using _Tp = sys_seconds::rep;
926+
// Africa/Freetown has a 4 day "zone"
927+
// Africa/Freetown Fri Sep 1 00:59:59 1939 UT = Thu Aug 31 23:59:59 1939 -01 isdst=0 gmtoff=-3600
928+
// Africa/Freetown Fri Sep 1 01:00:00 1939 UT = Fri Sep 1 00:20:00 1939 -0040 isdst=1 gmtoff=-2400
929+
// Africa/Freetown Tue Sep 5 00:39:59 1939 UT = Mon Sep 4 23:59:59 1939 -0040 isdst=1 gmtoff=-2400
930+
// Africa/Freetown Tue Sep 5 00:40:00 1939 UT = Mon Sep 4 23:40:00 1939 -01 isdst=0 gmtoff=-3600
931+
//
932+
// Originally used a one week threshold, but due to this switched to 1 day.
933+
// This seems to work in practice.
934+
//
935+
// TODO TZDB Evaluate the proper threshold.
936+
constexpr _Tp __threshold = 24 * 3600;
937+
938+
_Tp __upper = std::__add_sat(__info.begin.time_since_epoch().count(), __threshold);
939+
if (__time >= __info.begin && __time.time_since_epoch().count() < __upper)
940+
return __info.begin != sys_seconds::min() ? __position::__beginning : __position::__middle;
941+
942+
_Tp __lower = std::__sub_sat(__info.end.time_since_epoch().count(), __threshold);
943+
if (__time < __info.end && __time.time_since_epoch().count() >= __lower)
944+
return __info.end != sys_seconds::max() ? __position::__end : __position::__middle;
945+
946+
return __position::__middle;
947+
}
948+
949+
[[nodiscard]] static local_info
950+
__get_info(local_seconds __local_time, const sys_info& __first, const sys_info& __second) {
951+
std::chrono::local_seconds __end_first{__first.end.time_since_epoch() + __first.offset};
952+
std::chrono::local_seconds __begin_second{__second.begin.time_since_epoch() + __second.offset};
953+
954+
if (__local_time < __end_first) {
955+
if (__local_time >= __begin_second)
956+
// |--------|
957+
// |------|
958+
// ^
959+
return {local_info::ambiguous, __first, __second};
960+
961+
// |--------|
962+
// |------|
963+
// ^
964+
return {local_info::unique, __first, sys_info{}};
965+
}
966+
967+
if (__local_time < __begin_second)
968+
// |--------|
969+
// |------|
970+
// ^
971+
return {local_info::nonexistent, __first, __second};
972+
973+
// |--------|
974+
// |------|
975+
// ^
976+
return {local_info::unique, __second, sys_info{}};
977+
}
978+
979+
[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI local_info
980+
time_zone::__get_info(local_seconds __local_time) const {
981+
seconds __local_seconds = __local_time.time_since_epoch();
982+
983+
/* An example of a typical year with a DST switch displayed in local time.
984+
*
985+
* At the first of April the time goes forward one hour. This means the
986+
* time marked with ~~ is not a valid local time. This is represented by the
987+
* nonexistent value in local_info.result.
988+
*
989+
* At the first of November the time goes backward one hour. This means the
990+
* time marked with ^^ happens twice. This is represented by the ambiguous
991+
* value in local_info.result.
992+
*
993+
* 2020.11.01 2021.04.01 2021.11.01
994+
* offset +05 offset +05 offset +05
995+
* save 0s save 1h save 0s
996+
* |-------------W----------|
997+
* |----------W--------------|
998+
* |-------------
999+
* ~~ ^^
1000+
*
1001+
* These shifts can happen due to changes in the current time zone for a
1002+
* location. For example, Indian/Kerguelen switched only once. In 1950 from an
1003+
* offset of 0 hours to an offset of +05 hours.
1004+
*
1005+
* During all these shifts the UTC time will have not gaps.
1006+
*/
1007+
1008+
// The code needs to determine the system time for the local time. There is no
1009+
// information available. Assume the offset between system time and local time
1010+
// is 0s. This gives an initial estimate.
1011+
sys_seconds __guess{__local_seconds};
1012+
sys_info __info = __get_info(__guess);
1013+
1014+
// At this point the offset can be used to determine an estimate for the local
1015+
// time. Before doing the determine the offset validate whether the local time
1016+
// is the range [chrono::local_seconds::min(), chrono::local_seconds::max()).
1017+
if (__local_seconds < 0s && __info.offset > 0s)
1018+
if (__local_seconds - chrono::local_seconds::min().time_since_epoch() < __info.offset)
1019+
return {-1, __info, {}};
1020+
1021+
if (__local_seconds > 0s && __info.offset < 0s)
1022+
if (chrono::local_seconds::max().time_since_epoch() - __local_seconds < -__info.offset)
1023+
return {-2, __info, {}};
1024+
1025+
// Based on the information in the sys_info found the local time can be
1026+
// converted to a system time. This resulting time can be in the following
1027+
// locations of the sys_info found:
1028+
//
1029+
// |----------W--------------|
1030+
// 1 2 3 4 5
1031+
//
1032+
// 1. The estimate is before the returned sys_info object.
1033+
// The result is either non-existent or unique in the previous sys_info.
1034+
// 2. The estimate is in the beginning of the returned sys_info object.
1035+
// The result is either unique or ambiguous with the previous sys_info.
1036+
// 3. The estimate is in the "middle" of the returned sys_info.
1037+
// The result is unique.
1038+
// 4. The result is at the end of the returned sys_info object.
1039+
// The result is either unique or ambiguous with the next sys_info.
1040+
// 5. The estimate is after the returned sys_info object.
1041+
// The result is either non-existent or unique in the next sys_info.
1042+
//
1043+
// There is no specification where the "middle" starts. Similar issues can
1044+
// happen when sys_info objects are "short", then "unique in the next" could
1045+
// become "ambiguous in the next and the one following". Theoretically there
1046+
// is the option of the following time-line
1047+
//
1048+
// |------------|
1049+
// |----|
1050+
// |-----------------|
1051+
//
1052+
// However the local_info object only has 2 sys_info objects, so this option
1053+
// is not tested.
1054+
//
1055+
// The positions 2, 3, or 4 are determined by __get_position. This function
1056+
// also contains the definition of "middle".
1057+
1058+
sys_seconds __sys_time{__local_seconds - __info.offset};
1059+
if (__sys_time < __info.begin)
1060+
// Case 1 before __info
1061+
return chrono::__get_info(__local_time, __get_info(__info.begin - 1s), __info);
1062+
1063+
if (__sys_time >= __info.end)
1064+
// Case 5 after __info
1065+
return chrono::__get_info(__local_time, __info, __get_info(__info.end));
1066+
1067+
switch (__get_position(__sys_time, __info)) {
1068+
case __position::__beginning: // Case 2
1069+
return chrono::__get_info(__local_time, __get_info(__info.begin - 1s), __info);
1070+
1071+
case __position::__middle: // Case 3
1072+
return {local_info::unique, __info, {}};
1073+
1074+
case __position::__end: // Case 4
1075+
return chrono::__get_info(__local_time, __info, __get_info(__info.end));
1076+
}
1077+
1078+
std::__libcpp_unreachable();
1079+
}
1080+
9061081
} // namespace chrono
9071082

9081083
_LIBCPP_END_NAMESPACE_STD

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,10 @@ void test() {
4848

4949
{
5050
std::chrono::sys_seconds s{};
51+
std::chrono::local_seconds l{};
5152
tz.name(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
5253
tz.get_info(s); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
54+
tz.get_info(l); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
5355
operator==(tz, tz); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
5456
operator<=>(tz, tz); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
5557
}

0 commit comments

Comments
 (0)