-
Notifications
You must be signed in to change notification settings - Fork 113
Implement Foundation-based time zone support for Darwin systems with comprehensive DST handling #539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+553
−25
Merged
Implement Foundation-based time zone support for Darwin systems with comprehensive DST handling #539
Changes from all commits
Commits
Show all changes
95 commits
Select commit
Hold shift + click to select a range
868f81f
#485: Add tests and fallback logic for retrieving available time zone…
DmitryNekrasov cef3d62
#485: Rename TimeZoneRules to TimeZoneRulesCommon across the codebase
DmitryNekrasov 2c30c6e
#485: Implement TimeZoneRules interface and update TimeZoneRulesCommo…
DmitryNekrasov c4c4916
#485: Update RegionTimeZone to use TimeZoneRules instead of TimeZoneR…
DmitryNekrasov 8f9ffba
#485: Add TimeZoneRulesFoundation implementation skeleton to core
DmitryNekrasov 67d1e1f
#485: Add fallback to Foundation-based time zone retrieval on Darwin …
DmitryNekrasov c55680f
#485: Refactor timezone validation in tests to use a shared list of v…
DmitryNekrasov e545b06
#485: Add tests for Foundation-based time zone retrieval consistency.…
DmitryNekrasov 9f8af79
#485: Remove debug println from TimeZoneNativeTest during validation …
DmitryNekrasov b392db9
#485: Pass zoneId to TimeZoneRulesFoundation constructor to ensure pr…
DmitryNekrasov 00e135f
#485: Update TODO messages in TimeZoneRulesFoundation with method-spe…
DmitryNekrasov 1b36cad
#485: Add test to validate offsetAt consistency between Foundation an…
DmitryNekrasov c252bc1
#485: Implement infoAtInstant in TimeZoneRulesFoundation and initiali…
DmitryNekrasov 7856184
#485: Add test to verify DST transition handling consistency between …
DmitryNekrasov 64128d9
#485: Add tests for DST transitions and stable periods to validate ti…
DmitryNekrasov 687c8a6
#485: Add test to verify edge cases for DST gaps and overlaps in Time…
DmitryNekrasov 8d75cc4
#485: Add comments for time zone offsets in DST gap and overlap tests
DmitryNekrasov 37f33f6
#485: Add debug logging for infoAtDatetime in RegionTimeZone during z…
DmitryNekrasov 56b05c6
#485: Add a debug helper for offsets in TimeZoneNativeTest and implem…
DmitryNekrasov 0f1ba7a
#485: Add validation for LocalDateTime components and debug logging i…
DmitryNekrasov c096cb8
#485: Refactor infoAtInstant and infoAtDatetime in TimeZoneRulesFound…
DmitryNekrasov 7d447e6
#485: Enhance infoAtDatetime to handle DST gaps and overlaps, improve…
DmitryNekrasov bfaa938
#485: Simplify infoAtDatetime logic, enhance null-safety, and refine …
DmitryNekrasov e1e9de3
#485: Remove unused @OptIn annotation in TimeZoneRulesFoundation
DmitryNekrasov e566643
#485: Add setup for time zone rules test cases and validate offset co…
DmitryNekrasov b0b9660
#485: Refactor TimeZoneNativeTest to reorganize assertions, reintrodu…
DmitryNekrasov 3665682
#485: Remove debug logging from infoAtDatetime in RegionTimeZone
DmitryNekrasov fc2ba4b
#485: Remove unused TimeZone import in TimeZoneNativeTest
DmitryNekrasov 6542d8f
#485: Add UTC test cases in TimeZoneNativeTest to validate no DST tra…
DmitryNekrasov b43e725
#485: Refactor DST gap/overlap handling in TimeZoneRulesFoundation an…
DmitryNekrasov 96a2cb8
#485: Add detailed test cases for DST gap/overlap transitions in Time…
DmitryNekrasov e3e0710
#485: Consolidate and reorder UTC test cases in TimeZoneNativeTest
DmitryNekrasov 317c4a2
#485: Simplify tzdb initialization and update rulesForId usage in Tim…
DmitryNekrasov a7db951
#485: Add Australia/Sydney test cases for DST gap/overlap transitions…
DmitryNekrasov d7ffdf6
#485: Fix incorrect overlap edge case times in TimeZoneNativeTest
DmitryNekrasov 6ca929e
#485: Remove redundant blank lines in TimeZoneRulesFoundation infoAtD…
DmitryNekrasov 16d1d1e
#485: Add Asia/Kolkata test cases to TimeZoneNativeTest for no DST va…
DmitryNekrasov 3d65a79
#485: Add America/Sao_Paulo test cases to TimeZoneNativeTest for hist…
DmitryNekrasov 6168ed6
#485: Add Pacific/Chatham test cases to TimeZoneNativeTest for unusua…
DmitryNekrasov c8fb77a
#485: Update copyright year range in TimeZoneRulesCommon header
DmitryNekrasov c780902
#485: Remove unused imports from TimeZoneNativeTest to clean up depen…
DmitryNekrasov 5b235b9
#485: Rename variable in TimeZoneRulesFoundation for clarity and cons…
DmitryNekrasov 3665fc8
#485: Replace forEach with for loop in TimeZoneNativeTest for improve…
DmitryNekrasov 7c247af
#485: Add Asia/Pyongyang test cases to TimeZoneNativeTest for histori…
DmitryNekrasov 855a591
#485: Add Pacific/Kwajalein test cases to TimeZoneNativeTest for skip…
DmitryNekrasov 64ba33a
#485: Add Pacific/Apia test cases to TimeZoneNativeTest and adjust ga…
DmitryNekrasov a4d797f
#485: Add additional skipped date test case for Pacific/Kwajalein in …
DmitryNekrasov 7e230ed
#485: Add America/Caracas test cases to TimeZoneNativeTest for UTC of…
DmitryNekrasov 9aab509
#485: Extend separator line in TimeZoneNativeTest for improved log re…
DmitryNekrasov 3d4fc91
#485: Remove redundant setup method and replace mutable test data wit…
DmitryNekrasov 732c693
#485: Update return type of rulesForId in TimeZoneDatabase to TimeZon…
DmitryNekrasov 7b4343c
#485: Update TimeZoneRulesCommon references to TimeZoneRules across i…
DmitryNekrasov 31d87a3
#485: Cast TimeZoneRules to TimeZoneRulesCommon in TimeZoneRulesTest …
DmitryNekrasov b05fe2d
#485: Cast rulesForId result to TimeZoneRulesCommon in JsJodaTimezone…
DmitryNekrasov 9cef46f
#485: Cast rulesForId result to TimeZoneRulesCommon in TimeZoneRulesC…
DmitryNekrasov 78e09c6
#485: Add America/Caracas test cases to TimeZoneNativeTest for UTC of…
DmitryNekrasov 9887af3
#485: Fix daylight saving overlap logic in TimeZoneRulesFoundation by…
DmitryNekrasov 48236df
#485: Add Asia/Magadan test cases to TimeZoneNativeTest for UTC offse…
DmitryNekrasov 278d070
#485: Add test case for one day before gap transition in TimeZoneNati…
DmitryNekrasov 21513f6
#485: Replace static header with dynamic formatHeader method in TimeZ…
DmitryNekrasov b8f4f1b
#485: Iterate over multiple LocalDateTimes in testTimeZoneByIdFoundat…
DmitryNekrasov ffd5c16
#485: Rename test to reflect consistent ZonedDateTime production acro…
DmitryNekrasov f4c4411
#485: Add test for consistent atStartOfDay instance production betwee…
DmitryNekrasov 7a8265e
#485: Add test for consistent UTC offset production between regular a…
DmitryNekrasov dacfdf7
#485: Replace kotlinx.datetime.Instant with kotlin.time.Instant in Ti…
DmitryNekrasov f155332
#485: Update Asia/Pyongyang test case in TimeZoneNativeTest to reflec…
DmitryNekrasov ad39080
#485: Extend TimeZoneNativeTest to classify LocalDateTimes with Offse…
DmitryNekrasov 5f61ce4
#485: Refactor TimeZoneNativeTest for clarity by extracting OffsetInf…
DmitryNekrasov 2ee058d
#485: Adjust LocalDateTime-OffsetInfo mappings in TimeZoneNativeTest …
DmitryNekrasov 8036cee
#485: Refactor TimeZoneNativeTest for improved readability and consis…
DmitryNekrasov 568d567
#485: Make OffsetInfoType enum private in TimeZoneNativeTest to restr…
DmitryNekrasov 1ad7c4f
#485: Refactor consistent ZonedDateTime production test for improved …
DmitryNekrasov b3845ba
#485: Refactor consistent atStartOfDay instance production test for i…
DmitryNekrasov 0c79ea5
#485: Move TimeZoneRulesCommon and RecurringZoneRules implementations…
DmitryNekrasov 1dfec8e
#485: Replace specific import with wildcard in TimeZoneRules for math…
DmitryNekrasov 21ca771
#485: Add ExperimentalForeignApi opt-in and use `convert` for safer U…
DmitryNekrasov 16b2a1d
#485: Update TimeZoneRulesFoundation to use ISO8601 calendar
DmitryNekrasov 052c98f
#485: Refactor TimeZoneRulesFoundation to improve null checks and enh…
DmitryNekrasov e4b423d
#485: Refactor infoAtDatetime based on nextDaylightSavingTimeTransiti…
DmitryNekrasov 3f10cc0
#485: Remove unused `addTimeInterval` import from TimeZoneRulesFounda…
DmitryNekrasov 4c43b3f
#485: Remove debug print statement from TimeZoneRulesFoundation
DmitryNekrasov 2091ff2
#485: Remove unused SECS_PER_DAY constant from TimeZoneRulesFoundation
DmitryNekrasov 20e5504
#485: Reuse `transitionDateTimeInstant` for clarity and to avoid redu…
DmitryNekrasov 2bfeefb
#485: Update `rulesForId` and related methods to use `TimeZoneRulesCo…
DmitryNekrasov 7323da4
#485: Replace `runCatching` with explicit `if` checks for improved cl…
DmitryNekrasov 569c485
#485: Update `rulesForId` and `readRules` to return `TimeZoneRulesCom…
DmitryNekrasov b7fbc2c
#485: Remove unused `TimeZoneRulesCommon` import from `JsJodaTimezone…
DmitryNekrasov 7624587
Refactor `assertOffsetInfoType` to directly compare `OffsetInfo` type…
DmitryNekrasov 8fa63ae
Remove debug print statements from `TimeZoneNativeTest`.
DmitryNekrasov 59234f5
Refine assertions in `TimeZoneNativeTest` to check for timezone prefi…
DmitryNekrasov 4dd205f
Refactor `TimeZoneNativeTest` to use `DateTimePeriod` in offset calcu…
DmitryNekrasov 1f9154d
Refactor `TimeZoneNativeTest` to rename `transitionTime` to `timeJust…
DmitryNekrasov 400e246
Refactor `TimeZoneNativeTest` to parameterize `zoneId` in DST transit…
DmitryNekrasov a3ff28a
Refine `TimeZoneNativeTest` assertions to require both "UTC" and "GMT…
DmitryNekrasov 6294845
Add synchronization comments for `infoAtLocalDateTime` and `infoAtDat…
DmitryNekrasov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
* Copyright 2019-2025 JetBrains s.r.o. and contributors. | ||
* Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file. | ||
*/ | ||
|
||
package kotlinx.datetime.internal | ||
|
||
import kotlinx.cinterop.ExperimentalForeignApi | ||
import kotlinx.cinterop.UnsafeNumber | ||
import kotlinx.cinterop.convert | ||
import kotlinx.datetime.LocalDateTime | ||
import kotlinx.datetime.UtcOffset | ||
import kotlinx.datetime.toKotlinInstant | ||
import kotlinx.datetime.toLocalDateTime | ||
import kotlinx.datetime.toNSDate | ||
import kotlinx.datetime.toNSDateComponents | ||
import platform.Foundation.NSCalendar | ||
import platform.Foundation.NSCalendarIdentifierISO8601 | ||
import platform.Foundation.NSCalendarUnitYear | ||
import platform.Foundation.NSDate | ||
import platform.Foundation.NSTimeZone | ||
import platform.Foundation.timeZoneWithName | ||
import kotlin.time.Instant | ||
|
||
internal class TimeZoneRulesFoundation(private val zoneId: String) : TimeZoneRules { | ||
private val nsTimeZone: NSTimeZone = NSTimeZone.timeZoneWithName(zoneId) | ||
?: throw IllegalArgumentException("Unknown timezone: $zoneId") | ||
|
||
override fun infoAtInstant(instant: Instant): UtcOffset = | ||
infoAtNsDate(instant.toNSDate()) | ||
|
||
/** | ||
* IMPORTANT: mirrors the logic in [RecurringZoneRules.infoAtLocalDateTime]. | ||
* Any update to offset calculations, transition handling, or edge cases | ||
* must be duplicated there to maintain consistent behavior across | ||
* all platforms. | ||
*/ | ||
@OptIn(UnsafeNumber::class, ExperimentalForeignApi::class) | ||
override fun infoAtDatetime(localDateTime: LocalDateTime): OffsetInfo { | ||
val calendar = NSCalendar.calendarWithIdentifier(NSCalendarIdentifierISO8601) | ||
?.apply { timeZone = nsTimeZone } | ||
|
||
val year = localDateTime.year | ||
val startOfTheYear = calendar?.dateFromComponents(LocalDateTime(year, 1, 1, 0, 0).toNSDateComponents()) | ||
check(startOfTheYear != null) { "Failed to get the start of the year for $localDateTime, timezone: $zoneId" } | ||
|
||
var currentDate: NSDate = startOfTheYear | ||
var offset = infoAtNsDate(startOfTheYear) | ||
do { | ||
val transitionDateTime = nsTimeZone.nextDaylightSavingTimeTransitionAfterDate(currentDate) | ||
if (transitionDateTime == null) break | ||
|
||
val yearOfNextDate = calendar.component(NSCalendarUnitYear.convert(), fromDate = transitionDateTime) | ||
val transitionDateTimeInstant = transitionDateTime.toKotlinInstant() | ||
|
||
val offsetBefore = infoAtNsDate(currentDate) | ||
val ldtBefore = transitionDateTimeInstant.toLocalDateTime(offsetBefore) | ||
val offsetAfter = infoAtNsDate(transitionDateTime) | ||
val ldtAfter = transitionDateTimeInstant.toLocalDateTime(offsetAfter) | ||
|
||
return if (localDateTime < ldtBefore && localDateTime < ldtAfter) { | ||
DmitryNekrasov marked this conversation as resolved.
Show resolved
Hide resolved
|
||
OffsetInfo.Regular(offsetBefore) | ||
} else if (localDateTime >= ldtBefore && localDateTime >= ldtAfter) { | ||
offset = offsetAfter | ||
currentDate = transitionDateTime | ||
continue | ||
} else if (ldtAfter < ldtBefore) { | ||
OffsetInfo.Overlap(transitionDateTimeInstant, offsetBefore, offsetAfter) | ||
} else { | ||
OffsetInfo.Gap(transitionDateTimeInstant, offsetBefore, offsetAfter) | ||
} | ||
} while (yearOfNextDate <= year) | ||
|
||
return OffsetInfo.Regular(offset) | ||
} | ||
|
||
@OptIn(UnsafeNumber::class, ExperimentalForeignApi::class) | ||
private fun infoAtNsDate(nsDate: NSDate): UtcOffset { | ||
val offsetSeconds = nsTimeZone.secondsFromGMTForDate(nsDate) | ||
return UtcOffset(seconds = offsetSeconds.convert()) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.