@@ -37,65 +37,29 @@ internal actual class RegionTimeZone(private val tzid: TimeZoneRules, actual ove
37
37
}
38
38
39
39
actual override fun atZone (dateTime : LocalDateTime , preferred : UtcOffset ? ): ZonedDateTime =
40
- when (val info = tzid.infoAtDatetime(dateTime)) {
41
- is Regular -> ZonedDateTime (dateTime, this , info.offset)
42
- is Gap -> {
43
- try {
44
- ZonedDateTime (dateTime.plusSeconds(info.transitionDurationSeconds), this , info.offsetAfter)
45
- } catch (e: IllegalArgumentException ) {
46
- throw DateTimeArithmeticException (
47
- " Overflow whet correcting the date-time to not be in the transition gap" ,
48
- e
49
- )
50
- }
40
+ when (val info = tzid.infoAtDatetime(dateTime)) {
41
+ is Regular -> ZonedDateTime (dateTime, this , info.offset)
42
+ is Gap -> {
43
+ try {
44
+ ZonedDateTime (dateTime.plusSeconds(info.transitionDurationSeconds), this , info.offsetAfter)
45
+ } catch (e: IllegalArgumentException ) {
46
+ throw DateTimeArithmeticException (
47
+ " Overflow whet correcting the date-time to not be in the transition gap" ,
48
+ e
49
+ )
51
50
}
52
-
53
- is Overlap -> ZonedDateTime (dateTime, this ,
54
- if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore)
55
51
}
56
52
53
+ is Overlap -> ZonedDateTime (dateTime, this ,
54
+ if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore)
55
+ }
56
+
57
57
actual override fun offsetAtImpl (instant : Instant ): UtcOffset = tzid.infoAtInstant(instant)
58
58
}
59
59
60
60
@SharedImmutable
61
61
private val tzdbInRegistry = TzdbInRegistry ()
62
62
63
- // The timezone cache.
64
- @ThreadLocal
65
- private val cache: MutableMap <String , DYNAMIC_TIME_ZONE_INFORMATION > = mutableMapOf ()
66
-
67
- internal fun getLastWindowsError (): String = memScoped {
68
- val buf = alloc<CArrayPointerVar <WCHARVar >>()
69
- FormatMessage !! (
70
- (FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS ).toUInt(),
71
- null ,
72
- GetLastError (),
73
- 0u ,
74
- buf.ptr.reinterpret(),
75
- 0u ,
76
- null ,
77
- )
78
- buf.value!! .toKStringFromUtf16().also { LocalFree (buf.ptr) }
79
- }
80
-
81
-
82
- private fun offsetAtSystime (time : SYSTEMTIME , zone : DYNAMIC_TIME_ZONE_INFORMATION ): UtcOffset = memScoped {
83
- val yearSpecificRules = alloc<TIME_ZONE_INFORMATION >()
84
- val result = GetTimeZoneInformationForYear (time.wYear, zone.ptr, yearSpecificRules.ptr)
85
- check(result != 0 ) { " Could not query the offset at the given time (err: $result ): ${getLastWindowsError()} " }
86
- offsetAtSystime(time, yearSpecificRules)
87
- }
88
-
89
- private fun offsetAtSystime (time : SYSTEMTIME , rules : TIME_ZONE_INFORMATION ): UtcOffset {
90
- var bias = rules.Bias
91
- bias + = if (isDaylightTime(rules, time)) {
92
- rules.DaylightBias
93
- } else {
94
- rules.StandardBias
95
- }
96
- return UtcOffset (seconds = - bias * 60 )
97
- }
98
-
99
63
internal actual fun currentTime (): Instant = memScoped {
100
64
val tm = alloc< timespec> ()
101
65
val error = clock_gettime(CLOCK_REALTIME , tm.ptr)
@@ -107,124 +71,3 @@ internal actual fun currentTime(): Instant = memScoped {
107
71
throw IllegalStateException (" The readings from the system clock (${tm.tv_sec} seconds, ${tm.tv_nsec} nanoseconds) are not representable as an Instant" )
108
72
}
109
73
}
110
-
111
- private fun LocalDateTime.intoSystemTime (target : SYSTEMTIME ) = with (target) {
112
- wYear = year.toUShort()
113
- wMonth = monthNumber.toUShort()
114
- wDay = dayOfMonth.toUShort()
115
- wDayOfWeek = (if (dayOfWeek == DayOfWeek .SUNDAY ) 0 else dayOfWeek.isoDayNumber).toUShort()
116
- wHour = hour.toUShort()
117
- wMinute = minute.toUShort()
118
- wSecond = second.toUShort()
119
- wMilliseconds = ((nanosecond + NANOS_PER_MILLI / 2 ) / NANOS_PER_MILLI ).toUShort()
120
- }
121
-
122
- /* this code is explained at
123
- https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-time_zone_information
124
- in the section about `StandardDate`.
125
- In short, the `StandardDate` structure uses `SYSTEMTIME` in a...
126
- non-conventional way. This function translates that representation to one
127
- representing a proper date at a given year.
128
- */
129
- private fun decodeTransitionDate (year : Int , src : SYSTEMTIME ): LocalDateTime {
130
- // we set seconds and nanoseconds to 0 because they are 0 in practice,
131
- // but the Windows registry contains some broken data where these values are invalid.
132
- val localTime = with (src) { LocalTime (wHour.toInt(), wMinute.toInt(), 0 , 0 ) }
133
- val localDate = if (src.wYear != 0 .toUShort()) {
134
- // if the year is not 0, this is the absolute time.
135
- with (src) { LocalDate (wYear.toInt(), wMonth.toInt(), wDay.toInt()) }
136
- } else {
137
- /* otherwise, the transition happens yearly at the specified month, hour,
138
- and minute at the specified day of the week. */
139
- // The number of the occurrence of the specified day of week in the month,
140
- // or the special value "5" to denote the last such occurrence.
141
- val dowOccurrenceNumber = src.wDay.toInt()
142
- val month = Month (src.wMonth.toInt())
143
- val dayOfWeek = if (src.wDayOfWeek == 0 .toUShort()) DayOfWeek .SUNDAY else DayOfWeek (src.wDayOfWeek.toInt())
144
- val initialDate = LocalDate (year, month, 1 )
145
- val newDate = initialDate.nextDateWithDay(dayOfWeek).plus(dowOccurrenceNumber - 1 , DateTimeUnit .WEEK )
146
- if (newDate.month == month) newDate else newDate.minus(1 , DateTimeUnit .WEEK )
147
- }
148
- return localDate.atTime(localTime)
149
- }
150
-
151
- private fun LocalDate.nextDateWithDay (newDayOfWeek : DayOfWeek ) =
152
- plus((newDayOfWeek.isoDayNumber - this .dayOfWeek.isoDayNumber).mod(7 ), DateTimeUnit .DAY )
153
-
154
- private fun isDaylightTime (out : TIME_ZONE_INFORMATION , systime : SYSTEMTIME ): Boolean {
155
- // it means that daylight saving time is not supported at all
156
- if (out .StandardDate .wMonth == 0 .toUShort())
157
- return false
158
- /* translate the "date" values stored in `tzi` into real dates of
159
- transitions to and from the daylight saving time. */
160
- val standardLocal = decodeTransitionDate(systime.wYear.toInt(), out .StandardDate )
161
- val daylightLocal = decodeTransitionDate(systime.wYear.toInt(), out .DaylightDate )
162
- /* Two things happen here:
163
- * All the relevant dates are converted to a number of ticks at some
164
- unified scale, counted in seconds. This is done so that we are able
165
- to easily add to and compare between dates.
166
- * `standard_local` and `daylight_local` are represented as dates in the
167
- local time that was active *just before* the transition. For example,
168
- `standard_local` contains the date of the transition to the standard
169
- time, as seen by a person that is currently on the daylight saving
170
- time. So, in order for the dates to be on the same scale, the biases
171
- that are assumed to be currently active are negated. */
172
- val standard = standardLocal.toInstant(UtcOffset (minutes = - (out .Bias + out .DaylightBias )))
173
- val daylight = daylightLocal.toInstant(UtcOffset (minutes = - (out .Bias + out .StandardBias )))
174
- val currentTime = systime.toInstant()
175
- /* Maybe `else` is never hit, but the documentation doesn't say so. */
176
- return if (daylight < standard) {
177
- // The year is |STANDARD|DAYLIGHT|STANDARD|, as it should be
178
- currentTime < standard && currentTime >= daylight
179
- } else {
180
- // The year is |DAYLIGHT|STANDARD|DAYLIGHT|
181
- currentTime < standard || currentTime >= daylight
182
- }
183
- }
184
-
185
- private fun SYSTEMTIME.toInstant (): Instant = decodeTransitionDate(1601 , this ).toInstant(UtcOffset .ZERO )
186
-
187
- private fun offsetAtDatetime (
188
- zone : DYNAMIC_TIME_ZONE_INFORMATION , dateTime : LocalDateTime , preferred : UtcOffset ? , gapHandling : GapHandling
189
- ): Pair <LocalDateTime , UtcOffset > = when (val offsetInfo = offsetsAtDatetime(zone, dateTime)) {
190
- is Regular -> Pair (dateTime, offsetInfo.offset)
191
- is Overlap ->
192
- Pair (dateTime, if (offsetInfo.offsetBefore == preferred) offsetInfo.offsetBefore else offsetInfo.offsetAfter)
193
-
194
- is Gap -> when (gapHandling) {
195
- GapHandling .MOVE_FORWARD -> {
196
- Pair (dateTime.plusSeconds(offsetInfo.offsetAfter.totalSeconds - offsetInfo.offsetBefore.totalSeconds), offsetInfo.offsetAfter)
197
- }
198
-
199
- GapHandling .NEXT_CORRECT -> Pair (offsetInfo.start.toLocalDateTime(offsetInfo.offsetBefore), offsetInfo.offsetAfter)
200
- }
201
- }
202
-
203
- private fun offsetsAtDatetime (zone : DYNAMIC_TIME_ZONE_INFORMATION , dateTime : LocalDateTime ): OffsetInfo = memScoped {
204
- val localTime = alloc<SYSTEMTIME >().also { dateTime.intoSystemTime(it) }
205
- val yearSpecificRules = alloc<TIME_ZONE_INFORMATION >()
206
- GetTimeZoneInformationForYear (localTime.wYear, zone.ptr, yearSpecificRules.ptr)
207
- if (yearSpecificRules.DaylightDate .wMonth == 0 .toUShort()) {
208
- return Regular (UtcOffset (minutes = - yearSpecificRules.Bias ))
209
- }
210
- val dstStart = decodeTransitionDate(dateTime.year, yearSpecificRules.DaylightDate )
211
- val stdStart = decodeTransitionDate(dateTime.year, yearSpecificRules.StandardDate )
212
- check(dstStart < stdStart) { " Invalid timezone info readings: DST transition $dstStart occurred before the transition back $stdStart " }
213
- val standardOffset = UtcOffset (minutes = - (yearSpecificRules.Bias + yearSpecificRules.StandardBias ))
214
- val daylightOffset = UtcOffset (minutes = - (yearSpecificRules.Bias + yearSpecificRules.DaylightBias ))
215
- when {
216
- dateTime >= dstStart && dateTime < dstStart.plusSeconds(daylightOffset.totalSeconds - standardOffset.totalSeconds) ->
217
- Gap (dstStart.toInstant(standardOffset), standardOffset, daylightOffset)
218
-
219
- dateTime >= stdStart.plusSeconds(daylightOffset.totalSeconds - standardOffset.totalSeconds) && dateTime < stdStart ->
220
- Overlap (stdStart.toInstant(daylightOffset), daylightOffset, standardOffset)
221
-
222
- dateTime >= dstStart && dateTime < stdStart -> Regular (daylightOffset)
223
- else -> Regular (standardOffset)
224
- }
225
- }
226
-
227
- private enum class GapHandling {
228
- MOVE_FORWARD ,
229
- NEXT_CORRECT ,
230
- }
0 commit comments