Skip to content

Commit

Permalink
There are more and more flaky timezones
Browse files Browse the repository at this point in the history
... so let's give up and stop trying to predict the future:
limit the checks to the next couple of years instead of checking
2030 and similar. All flaky failures happen when accessing the
timezone database several years into the future.
  • Loading branch information
dkhalanskyjb committed Jan 9, 2025
1 parent f5c81f7 commit 95f6b02
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 32 deletions.
2 changes: 1 addition & 1 deletion core/windows/src/internal/TzdbInRegistry.kt
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ private class PerYearZoneRulesDataWithTransitions(
", the transitions: $daylightTransitionTime, $standardTransitionTime"
}

private fun getLastWindowsError(): String = memScoped {
internal fun getLastWindowsError(): String = memScoped {
val buf = alloc<CArrayPointerVar<WCHARVar>>()
FormatMessage!!(
(FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_IGNORE_INSERTS).toUInt(),
Expand Down
43 changes: 12 additions & 31 deletions core/windows/test/TimeZoneRulesCompleteTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class TimeZoneRulesCompleteTest {
}
val issues = mutableListOf<IncompatibilityWithWindowsRegistry>()
var i: DWORD = 0u
val currentYear = Clock.System.todayIn(TimeZone.UTC).year
while (true) {
when (val dwResult: Int = EnumDynamicTimeZoneInformation(i++, dtzi.ptr).toInt()) {
ERROR_NO_MORE_ITEMS -> break
Expand Down Expand Up @@ -95,30 +96,15 @@ class TimeZoneRulesCompleteTest {
}
}
// check recurring rules
if (windowsName !in strangeTimeZones) {
// we skip checking these time zones because Windows does something arbitrary with them
// after 2030. For example, Morocco DST transitions are linked to the month of Ramadan,
// and after 2030, Windows doesn't seem to calculate Ramadan properly, but also, it doesn't
// follow the rules stored in the registry. Odd, but it doesn't seem worth it trying to
// reverse engineer results that aren't even correct.
val lastTransitionYear = Instant.fromEpochSeconds(
rules.transitionEpochSeconds.lastOrNull() ?: 1715000000 // arbitrary time
).toLocalDateTime(TimeZone.UTC).year
val firstTransitionYear = Instant.fromEpochSeconds(
rules.transitionEpochSeconds.firstOrNull() ?: 0 // arbitrary time
).toLocalDateTime(TimeZone.UTC).year
val yearsToCheck = (maxOf(firstTransitionYear - 15, 1970)..<firstTransitionYear) +
((lastTransitionYear + 1)..(lastTransitionYear + 15))
for (year in yearsToCheck) {
val rulesForYear = rules.recurringZoneRules!!.rulesForYear(year)
if (rulesForYear.isEmpty()) {
checkAtInstant(
LocalDate(year, 6, 1).atStartOfDayIn(TimeZone.UTC)
)
} else {
for (rule in rulesForYear) {
checkTransition(rule.transitionDateTime)
}
for (year in 1970..currentYear + 1) {
val rulesForYear = rules.recurringZoneRules!!.rulesForYear(year)
if (rulesForYear.isEmpty()) {
checkAtInstant(
LocalDate(year, 6, 1).atStartOfDayIn(TimeZone.UTC)
)
} else {
for (rule in rulesForYear) {
checkTransition(rule.transitionDateTime)
}
}
}
Expand Down Expand Up @@ -166,7 +152,8 @@ private fun Instant.toLocalDateTime(
outputBuffer: CPointer<SYSTEMTIME>
): LocalDateTime {
toLocalDateTime(TimeZone.UTC).toSystemTime(inputBuffer)
SystemTimeToTzSpecificLocalTimeEx(tzinfo.ptr, inputBuffer, outputBuffer)
val result = SystemTimeToTzSpecificLocalTimeEx(tzinfo.ptr, inputBuffer, outputBuffer)
check(result != 0) { "SystemTimeToTzSpecificLocalTimeEx failed: ${getLastWindowsError()}" }
return outputBuffer.pointed.toLocalDateTime()
}

Expand Down Expand Up @@ -195,12 +182,6 @@ private fun SYSTEMTIME.toLocalDateTime(): LocalDateTime =
nanosecond = wMilliseconds.convert<Int>() * (NANOS_PER_ONE / MILLIS_PER_ONE)
)

private val strangeTimeZones = listOf(
// These report transitions in the future that are not in the registry:
"Morocco Standard Time", "West Bank Standard Time", "Iran Standard Time", "Syria Standard Time",
"Paraguay Standard Time",
)

private fun binarySearchInstant(instant1: Instant, instant2: Instant, predicate: (Instant) -> Boolean): Instant {
var low = instant1
var high = instant2
Expand Down

0 comments on commit 95f6b02

Please sign in to comment.