Skip to content

Commit a6b280e

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

File tree

5 files changed

+1462
-0
lines changed

5 files changed

+1462
-0
lines changed

libcxx/include/__chrono/time_zone.h

+8
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
// Enable the contents of the header only when libc++ was built with experimental features enabled.
1717
#if !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_TZDB)
1818

19+
# include <__chrono/calendar.h>
1920
# include <__chrono/duration.h>
21+
# include <__chrono/local_info.h>
2022
# include <__chrono/sys_info.h>
2123
# include <__chrono/system_clock.h>
2224
# include <__compare/strong_order.h>
@@ -63,12 +65,18 @@ class _LIBCPP_AVAILABILITY_TZDB time_zone {
6365
return __get_info(chrono::time_point_cast<seconds>(__time));
6466
}
6567

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

6875
private:
6976
[[nodiscard]] _LIBCPP_EXPORTED_FROM_ABI string_view __name() const noexcept;
7077

7178
[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI sys_info __get_info(sys_seconds __time) const;
79+
[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI local_info __get_info(local_seconds __time) const;
7280

7381
unique_ptr<__impl> __impl_;
7482
};

libcxx/include/chrono

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

+147
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,152 @@ time_zone::__get_info(sys_seconds __time) const {
903904
std::__throw_runtime_error("tzdb: corrupt db");
904905
}
905906

907+
// Is the "__local_time" present in "__first" and "__second". If so the
908+
// local_info has an ambiguous result.
909+
[[nodiscard]] static bool
910+
__is_ambiguous(local_seconds __local_time, const sys_info& __first, const sys_info& __second) {
911+
std::chrono::local_seconds __end_first{__first.end.time_since_epoch() + __first.offset};
912+
std::chrono::local_seconds __begin_second{__second.begin.time_since_epoch() + __second.offset};
913+
914+
return __local_time < __end_first && __local_time >= __begin_second;
915+
}
916+
917+
// Determines the result of the "__local_time". This expects the object
918+
// "__first" to be earlier in time than "__second".
919+
[[nodiscard]] static local_info
920+
__get_info(local_seconds __local_time, const sys_info& __first, const sys_info& __second) {
921+
std::chrono::local_seconds __end_first{__first.end.time_since_epoch() + __first.offset};
922+
std::chrono::local_seconds __begin_second{__second.begin.time_since_epoch() + __second.offset};
923+
924+
if (__local_time < __end_first) {
925+
if (__local_time >= __begin_second)
926+
// |--------|
927+
// |------|
928+
// ^
929+
return {local_info::ambiguous, __first, __second};
930+
931+
// |--------|
932+
// |------|
933+
// ^
934+
return {local_info::unique, __first, sys_info{}};
935+
}
936+
937+
if (__local_time < __begin_second)
938+
// |--------|
939+
// |------|
940+
// ^
941+
return {local_info::nonexistent, __first, __second};
942+
943+
// |--------|
944+
// |------|
945+
// ^
946+
return {local_info::unique, __second, sys_info{}};
947+
}
948+
949+
[[nodiscard]] _LIBCPP_AVAILABILITY_TZDB _LIBCPP_EXPORTED_FROM_ABI local_info
950+
time_zone::__get_info(local_seconds __local_time) const {
951+
seconds __local_seconds = __local_time.time_since_epoch();
952+
953+
/* An example of a typical year with a DST switch displayed in local time.
954+
*
955+
* At the first of April the time goes forward one hour. This means the
956+
* time marked with ~~ is not a valid local time. This is represented by the
957+
* nonexistent value in local_info.result.
958+
*
959+
* At the first of November the time goes backward one hour. This means the
960+
* time marked with ^^ happens twice. This is represented by the ambiguous
961+
* value in local_info.result.
962+
*
963+
* 2020.11.01 2021.04.01 2021.11.01
964+
* offset +05 offset +05 offset +05
965+
* save 0s save 1h save 0s
966+
* |------------//----------|
967+
* |---------//--------------|
968+
* |-------------
969+
* ~~ ^^
970+
*
971+
* These shifts can happen due to changes in the current time zone for a
972+
* location. For example, Indian/Kerguelen switched only once. In 1950 from an
973+
* offset of 0 hours to an offset of +05 hours.
974+
*
975+
* During all these shifts the UTC time will not have gaps.
976+
*/
977+
978+
// The code needs to determine the system time for the local time. There is no
979+
// information available. Assume the offset between system time and local time
980+
// is 0s. This gives an initial estimate.
981+
sys_seconds __guess{__local_seconds};
982+
sys_info __info = __get_info(__guess);
983+
984+
// At this point the offset can be used to determine an estimate for the local
985+
// time. Before doing that, determine the offset and validate whether the
986+
// local time is the range [chrono::local_seconds::min(),
987+
// chrono::local_seconds::max()).
988+
if (__local_seconds < 0s && __info.offset > 0s)
989+
if (__local_seconds - chrono::local_seconds::min().time_since_epoch() < __info.offset)
990+
return {-1, __info, {}};
991+
992+
if (__local_seconds > 0s && __info.offset < 0s)
993+
if (chrono::local_seconds::max().time_since_epoch() - __local_seconds < -__info.offset)
994+
return {-2, __info, {}};
995+
996+
// Based on the information found in the sys_info, the local time can be
997+
// converted to a system time. This resulting time can be in the following
998+
// locations of the sys_info:
999+
//
1000+
// |---------//--------------|
1001+
// 1 2.1 2.2 2.3 3
1002+
//
1003+
// 1. The estimate is before the returned sys_info object.
1004+
// The result is either non-existent or unique in the previous sys_info.
1005+
// 2. The estimate is in the sys_info object
1006+
// - If the sys_info begin is not sys_seconds::min(), then it might be at
1007+
// 2.1 and could be ambiguous with the previous or unique.
1008+
// - If sys_info end is not sys_seconds::max(), then it might be at 2.3
1009+
// and could be ambiguous with the next or unique.
1010+
// - Else it is at 2.2 and always unique. This case happens when a
1011+
// time zone has no transitions. For example, UTC or GMT+1.
1012+
// 3. The estimate is after the returned sys_info object.
1013+
// The result is either non-existent or unique in the next sys_info.
1014+
//
1015+
// There is no specification where the "middle" starts. Similar issues can
1016+
// happen when sys_info objects are "short", then "unique in the next" could
1017+
// become "ambiguous in the next and the one following". Theoretically there
1018+
// is the option of the following time-line
1019+
//
1020+
// |------------|
1021+
// |----|
1022+
// |-----------------|
1023+
//
1024+
// However the local_info object only has 2 sys_info objects, so this option
1025+
// is not tested.
1026+
1027+
sys_seconds __sys_time{__local_seconds - __info.offset};
1028+
if (__sys_time < __info.begin)
1029+
// Case 1 before __info
1030+
return chrono::__get_info(__local_time, __get_info(__info.begin - 1s), __info);
1031+
1032+
if (__sys_time >= __info.end)
1033+
// Case 3 after __info
1034+
return chrono::__get_info(__local_time, __info, __get_info(__info.end));
1035+
1036+
// Case 2 in __info
1037+
if (__info.begin != sys_seconds::min()) {
1038+
// Case 2.1 Not at the beginning, when not ambiguous the result should test
1039+
// case 2.3.
1040+
sys_info __prev = __get_info(__info.begin - 1s);
1041+
if (__is_ambiguous(__local_time, __prev, __info))
1042+
return {local_info::ambiguous, __prev, __info};
1043+
}
1044+
1045+
if (__info.end == sys_seconds::max())
1046+
// At the end so it's case 2.2
1047+
return {local_info::unique, __info, sys_info{}};
1048+
1049+
// This tests case 2.2 or case 2.3.
1050+
return chrono::__get_info(__local_time, __info, __get_info(__info.end));
1051+
}
1052+
9061053
} // namespace chrono
9071054

9081055
_LIBCPP_END_NAMESPACE_STD

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

+2
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)