@@ -7,7 +7,6 @@ package kotlinx.datetime.internal
7
7
8
8
import kotlinx.datetime.*
9
9
import kotlinx.cinterop.*
10
- import platform.posix.*
11
10
import platform.windows.*
12
11
13
12
internal class TzdbInRegistry : TimeZoneDatabase {
@@ -77,31 +76,38 @@ internal class TzdbInRegistry: TimeZoneDatabase {
77
76
https://docs.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-dynamic_time_zone_information
78
77
*/
79
78
private const val MAX_KEY_LENGTH = 128
79
+ private const val KEY_BUFFER_SIZE = MAX_KEY_LENGTH + 1
80
80
81
81
internal fun processTimeZonesInRegistry (onTimeZone : (String , PerYearZoneRulesData , List <Pair <Int , PerYearZoneRulesData >>) -> Unit ) {
82
82
memScoped {
83
- withRegistryKey(HKEY_LOCAL_MACHINE !! , " SOFTWARE\\ Microsoft\\ Windows NT\\ CurrentVersion\\ Time Zones" , { err ->
83
+ alloc< HKEYVar >(). withRegistryKey(HKEY_LOCAL_MACHINE !! , " SOFTWARE\\ Microsoft\\ Windows NT\\ CurrentVersion\\ Time Zones" , { err ->
84
84
throw IllegalStateException (" Error while opening the registry to fetch the time zones (err = $err ): ${getLastWindowsError()} " )
85
85
}) { hKey ->
86
86
var index = 0u
87
+ val cbDataBuffer = alloc<DWORDVar >()
88
+ val zoneInfoBuffer = RegistryTimeZoneInfoBuffer .allocate(this , cbDataBuffer)
89
+ val dwordBuffer = RegistryDwordBuffer .allocate(this , cbDataBuffer)
90
+ val tzHkeyBuffer = alloc<HKEYVar >()
91
+ val dynamicDstHkeyBuffer = alloc<HKEYVar >()
92
+ val windowsTzNameBuffer = allocArray<WCHARVar >(KEY_BUFFER_SIZE )
93
+ val windowsTzNameSizeBuffer = alloc<UIntVar >()
87
94
while (true ) {
88
- val windowsTzName = allocArray<WCHARVar >(MAX_KEY_LENGTH + 1 )
89
- val bufSize = alloc<UIntVar >().apply { value = MAX_KEY_LENGTH .toUInt() + 1u }
90
- when (RegEnumKeyExW (hKey, index++ , windowsTzName, bufSize.ptr, null , null , null , null )) {
95
+ windowsTzNameSizeBuffer.value = KEY_BUFFER_SIZE .convert()
96
+ when (RegEnumKeyExW (hKey, index++ , windowsTzNameBuffer, windowsTzNameSizeBuffer.ptr, null , null , null , null )) {
91
97
ERROR_SUCCESS -> {
92
- withRegistryKey(hKey, windowsTzName .toKString(), { err ->
98
+ tzHkeyBuffer. withRegistryKey(hKey, windowsTzNameBuffer .toKString(), { err ->
93
99
throw IllegalStateException (
94
- " Error while opening the registry to fetch the time zone '${windowsTzName .toKString()} (err = $err )': ${getLastWindowsError()} "
100
+ " Error while opening the registry to fetch the time zone '${windowsTzNameBuffer .toKString()} (err = $err )': ${getLastWindowsError()} "
95
101
)
96
102
}) { tzHKey ->
97
103
// first, read the current data from the TZI value
98
- val tziRecord = getRegistryValue< REG_TZI_FORMAT > (tzHKey, " TZI" )
104
+ val tziRecord = zoneInfoBuffer.readZoneRules (tzHKey, " TZI" )
99
105
val historicData = try {
100
- readHistoricDataFromRegistry(tzHKey)
106
+ dynamicDstHkeyBuffer. readHistoricDataFromRegistry(tzHKey, dwordBuffer, zoneInfoBuffer )
101
107
} catch (e: IllegalStateException ) {
102
108
emptyList()
103
109
}
104
- onTimeZone(windowsTzName .toKString(), tziRecord.toZoneRules() , historicData)
110
+ onTimeZone(windowsTzNameBuffer .toKString(), tziRecord, historicData)
105
111
}
106
112
}
107
113
ERROR_MORE_DATA -> throw IllegalStateException (" The name of a time zone in the registry was too long" )
@@ -122,46 +128,78 @@ internal fun processTimeZonesInRegistry(onTimeZone: (String, PerYearZoneRulesDat
122
128
*
123
129
* @throws IllegalStateException if the 'Dynamic DST' key is present but malformed.
124
130
*/
125
- private fun MemScope.readHistoricDataFromRegistry (tzHKey : HKEY ): List <Pair <Int , PerYearZoneRulesData >> {
126
- return withRegistryKey(tzHKey, " Dynamic DST" , { emptyList() }) { dynDstHKey ->
127
- val firstEntry = getRegistryValue<DWORDVar >(dynDstHKey, " FirstEntry" ).value.toInt()
128
- val lastEntry = getRegistryValue<DWORDVar >(dynDstHKey, " LastEntry" ).value.toInt()
131
+ private fun HKEYVar.readHistoricDataFromRegistry (
132
+ tzHKey : HKEY , dwordBuffer : RegistryDwordBuffer , zoneRulesBuffer : RegistryTimeZoneInfoBuffer
133
+ ): List <Pair <Int , PerYearZoneRulesData >> =
134
+ withRegistryKey(tzHKey, " Dynamic DST" , { emptyList() }) { dynDstHKey ->
135
+ val firstEntry = dwordBuffer.readValue(dynDstHKey, " FirstEntry" )
136
+ val lastEntry = dwordBuffer.readValue(dynDstHKey, " LastEntry" )
129
137
(firstEntry.. lastEntry).map { year ->
130
- year to getRegistryValue< REG_TZI_FORMAT > (dynDstHKey, year.toString()).toZoneRules( )
138
+ year to zoneRulesBuffer.readZoneRules (dynDstHKey, year.toString())
131
139
}
132
140
}
133
- }
134
141
135
- private inline fun <T > MemScope.withRegistryKey (hKey : HKEY , subKeyName : String , onError : (Int ) -> T , block : (HKEY ) -> T ): T {
136
- val subHKey: HKEYVar = alloc()
137
- val err = RegOpenKeyExW (hKey, subKeyName, 0u , KEY_READ .toUInt(), subHKey.ptr)
142
+ private inline fun <T > HKEYVar.withRegistryKey (hKey : HKEY , subKeyName : String , onError : (Int ) -> T , block : (HKEY ) -> T ): T {
143
+ val err = RegOpenKeyExW (hKey, subKeyName, 0u , KEY_READ .toUInt(), ptr)
138
144
return if (err != ERROR_SUCCESS ) { onError(err) } else {
139
145
try {
140
- block(subHKey. value!! )
146
+ block(value!! )
141
147
} finally {
142
- RegCloseKey (subHKey. value)
148
+ RegCloseKey (value)
143
149
}
144
150
}
145
151
}
146
152
147
- private inline fun <reified T : CVariable > MemScope.getRegistryValue (hKey : HKEY , valueName : String ): T {
148
- val buffer = alloc<T >()
149
- val cbData = alloc<DWORDVar >().apply { value = sizeOf<T >().convert() }
150
- val err = RegQueryValueExW (hKey, valueName, null , null , buffer.reinterpret<BYTEVar >().ptr, cbData.ptr)
151
- check(err == ERROR_SUCCESS ) { " The expected Windows registry value '$valueName ' could not be accessed (err = $err )': ${getLastWindowsError()} " }
152
- check(cbData.value.toLong() == sizeOf<T >()) { " Expected '$valueName ' to have size ${sizeOf<T >()} , but got ${cbData.value} " }
153
- return buffer
153
+ private fun getRegistryValue (
154
+ cbDataBuffer : DWORDVar , buffer : CPointer <BYTEVar >, bufferSize : UInt , hKey : HKEY , valueName : String
155
+ ) {
156
+ cbDataBuffer.value = bufferSize
157
+ val err = RegQueryValueExW (hKey, valueName, null , null , buffer, cbDataBuffer.ptr)
158
+ check(err == ERROR_SUCCESS ) {
159
+ " The expected Windows registry value '$valueName ' could not be accessed (err = $err )': ${getLastWindowsError()} "
160
+ }
161
+ check(cbDataBuffer.value.convert<UInt >() == bufferSize) {
162
+ " Expected '$valueName ' to have size $bufferSize , but got ${cbDataBuffer.value} "
163
+ }
154
164
}
155
165
156
- private fun _REG_TZI_FORMAT.toZoneRules (): PerYearZoneRulesData {
157
- val standardOffset = UtcOffset (minutes = - (StandardBias + Bias ))
158
- val daylightOffset = UtcOffset (minutes = - (DaylightBias + Bias ))
159
- if (DaylightDate .wMonth == 0 .convert<WORD >()) {
160
- return PerYearZoneRulesData (standardOffset, null )
166
+ private class RegistryTimeZoneInfoBuffer private constructor(
167
+ private val buffer : CPointer <BYTEVar >,
168
+ private val cbData : DWORDVar ,
169
+ ) {
170
+ companion object {
171
+ fun allocate (scope : MemScope , cbData : DWORDVar ): RegistryTimeZoneInfoBuffer =
172
+ RegistryTimeZoneInfoBuffer (scope.allocArray<BYTEVar >(sizeOf<REG_TZI_FORMAT >().convert()), cbData)
173
+ }
174
+
175
+ 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)
186
+ }
187
+ }
188
+ }
189
+
190
+ private class RegistryDwordBuffer private constructor(
191
+ private val buffer : CPointer <DWORDVar >,
192
+ private val cbData : DWORDVar ,
193
+ ) {
194
+ companion object {
195
+ fun allocate (scope : MemScope , cbData : DWORDVar ): RegistryDwordBuffer =
196
+ RegistryDwordBuffer (scope.alloc<DWORDVar >().ptr, cbData)
197
+ }
198
+
199
+ fun readValue (hKey : HKEY , valueName : String ): Int {
200
+ getRegistryValue(cbData, buffer.reinterpret(), sizeOf<DWORDVar >().convert(), hKey, valueName)
201
+ return buffer.pointed.value.toInt()
161
202
}
162
- val changeToDst = RecurringZoneRules .Rule (DaylightDate .toMonthDayTime(), standardOffset, daylightOffset)
163
- val changeToStd = RecurringZoneRules .Rule (StandardDate .toMonthDayTime(), daylightOffset, standardOffset)
164
- return PerYearZoneRulesData (standardOffset, changeToDst to changeToStd)
165
203
}
166
204
167
205
/* this code is explained at
0 commit comments