|
34 | 34 | #include <chrono>
|
35 | 35 | #include <expected>
|
36 | 36 | #include <map>
|
| 37 | +#include <numeric> |
37 | 38 | #include <ranges>
|
38 | 39 |
|
39 | 40 | #include "include/tzdb/time_zone_private.h"
|
@@ -903,6 +904,180 @@ time_zone::__get_info(sys_seconds __time) const {
|
903 | 904 | std::__throw_runtime_error("tzdb: corrupt db");
|
904 | 905 | }
|
905 | 906 |
|
| 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 | + |
906 | 1081 | } // namespace chrono
|
907 | 1082 |
|
908 | 1083 | _LIBCPP_END_NAMESPACE_STD
|
0 commit comments