@@ -8,6 +8,7 @@ package kotlinx.datetime.internal
8
8
import kotlinx.datetime.*
9
9
import kotlinx.cinterop.*
10
10
import platform.windows.*
11
+ import kotlin.experimental.*
11
12
12
13
internal class TzdbInRegistry : TimeZoneDatabase {
13
14
@@ -78,7 +79,7 @@ internal class TzdbInRegistry: TimeZoneDatabase {
78
79
private const val MAX_KEY_LENGTH = 128
79
80
private const val KEY_BUFFER_SIZE = MAX_KEY_LENGTH + 1
80
81
81
- internal fun processTimeZonesInRegistry (onTimeZone : (String , PerYearZoneRulesData , List <Pair <Int , PerYearZoneRulesData >>) -> Unit ) {
82
+ private fun processTimeZonesInRegistry (onTimeZone : (String , PerYearZoneRulesData , List <Pair <Int , PerYearZoneRulesData >>) -> Unit ) {
82
83
memScoped {
83
84
alloc<HKEYVar >().withRegistryKey(HKEY_LOCAL_MACHINE !! , " SOFTWARE\\ Microsoft\\ Windows NT\\ CurrentVersion\\ Time Zones" , { err ->
84
85
throw IllegalStateException (" Error while opening the registry to fetch the time zones (err = $err ): ${getLastWindowsError()} " )
@@ -167,23 +168,62 @@ private class RegistryTimeZoneInfoBuffer private constructor(
167
168
private val buffer : CPointer <BYTEVar >,
168
169
private val cbData : DWORDVar ,
169
170
) {
171
+ /* *
172
+ * The data structure is described at
173
+ * https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-time_zone_information, as
174
+ * `_REG_TZI_FORMAT`:
175
+ *
176
+ * ```
177
+ * // 16 bytes
178
+ * typedef struct {
179
+ * uint16_t wYear;
180
+ * uint16_t wMonth;
181
+ * uint16_t wDayOfWeek;
182
+ * uint16_t wDay;
183
+ * uint16_t wHour;
184
+ * uint16_t wMinute;
185
+ * uint16_t wSecond;
186
+ * uint16_t wMilliseconds;
187
+ * } SYSTEMTIME;
188
+ *
189
+ * // 44 bytes
190
+ * typedef struct _REG_TZI_FORMAT
191
+ * {
192
+ * int32_t Bias;
193
+ * int32_t StandardBias;
194
+ * int32_t DaylightBias;
195
+ * SYSTEMTIME StandardDate;
196
+ * SYSTEMTIME DaylightDate;
197
+ * } REG_TZI_FORMAT;
198
+ * ```
199
+ */
170
200
companion object {
171
201
fun allocate (scope : MemScope , cbData : DWORDVar ): RegistryTimeZoneInfoBuffer =
172
- RegistryTimeZoneInfoBuffer (scope.allocArray<BYTEVar >(sizeOf<REG_TZI_FORMAT >().convert()), cbData)
202
+ RegistryTimeZoneInfoBuffer (scope.allocArray<BYTEVar >(SIZE_BYTES ), cbData)
203
+
204
+ private val SIZE_BYTES = 44
173
205
}
174
206
207
+ @OptIn(ExperimentalNativeApi ::class )
175
208
fun readZoneRules (tzHKey : HKEY , name : String ): PerYearZoneRulesData {
176
- getRegistryValue(cbData, buffer, sizeOf<REG_TZI_FORMAT >().convert(), tzHKey, name)
177
- return with (buffer.reinterpret<REG_TZI_FORMAT >().pointed) {
178
- val standardOffset = UtcOffset (minutes = - (StandardBias + Bias ))
179
- val daylightOffset = UtcOffset (minutes = - (DaylightBias + Bias ))
180
- if (DaylightDate .wMonth == 0 .convert<WORD >()) {
181
- return PerYearZoneRulesData (standardOffset, null )
182
- }
183
- val changeToDst = RecurringZoneRules .Rule (DaylightDate .toMonthDayTime(), standardOffset, daylightOffset)
184
- val changeToStd = RecurringZoneRules .Rule (StandardDate .toMonthDayTime(), daylightOffset, standardOffset)
185
- PerYearZoneRulesData (standardOffset, changeToDst to changeToStd)
209
+ getRegistryValue(cbData, buffer, SIZE_BYTES .convert(), tzHKey, name)
210
+ // convert the buffer to a byte array
211
+ val byteArray = buffer.readBytes(SIZE_BYTES )
212
+ // obtaining raw data
213
+ val bias = byteArray.getIntAt(0 )
214
+ val standardBias = byteArray.getIntAt(4 )
215
+ val daylightBias = byteArray.getIntAt(8 )
216
+ val standardDate = (buffer + 12 )!! .reinterpret<SYSTEMTIME >().pointed
217
+ val daylightDate = (buffer + 28 )!! .reinterpret<SYSTEMTIME >().pointed
218
+ // calculating the things we're interested in
219
+ val standardOffset = UtcOffset (minutes = - (standardBias + bias))
220
+ val daylightOffset = UtcOffset (minutes = - (daylightBias + bias))
221
+ if (daylightDate.wMonth == 0 .convert<WORD >()) {
222
+ return PerYearZoneRulesData (standardOffset, null )
186
223
}
224
+ val changeToDst = RecurringZoneRules .Rule (daylightDate.toMonthDayTime(), standardOffset, daylightOffset)
225
+ val changeToStd = RecurringZoneRules .Rule (standardDate.toMonthDayTime(), daylightOffset, standardOffset)
226
+ return PerYearZoneRulesData (standardOffset, changeToDst to changeToStd)
187
227
}
188
228
}
189
229
@@ -230,9 +270,9 @@ private fun SYSTEMTIME.toMonthDayTime(): MonthDayTime {
230
270
return MonthDayTime (MonthDayOfYear (month, transitionDay), localTime, MonthDayTime .OffsetResolver .WallClockOffset )
231
271
}
232
272
233
- internal class PerYearZoneRulesData (
234
- val standardOffset : UtcOffset ,
235
- val transitions : Pair <RecurringZoneRules .Rule <MonthDayTime >, RecurringZoneRules .Rule <MonthDayTime >>? ,
273
+ private class PerYearZoneRulesData (
274
+ val standardOffset : UtcOffset ,
275
+ val transitions : Pair <RecurringZoneRules .Rule <MonthDayTime >, RecurringZoneRules .Rule <MonthDayTime >>? ,
236
276
) {
237
277
override fun toString (): String = " standard offset is $standardOffset " + (transitions?.let {
238
278
" , the transitions: ${it.first} , ${it.second} "
0 commit comments