|
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,152 @@ time_zone::__get_info(sys_seconds __time) const {
|
903 | 904 | std::__throw_runtime_error("tzdb: corrupt db");
|
904 | 905 | }
|
905 | 906 |
|
| 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 | + |
906 | 1053 | } // namespace chrono
|
907 | 1054 |
|
908 | 1055 | _LIBCPP_END_NAMESPACE_STD
|
0 commit comments