Skip to content

Conversation

jmschonfeld
Copy link
Contributor

There's been a handful of changes over the years in this area, but the situation we have today is that TimeZone, Calendar, and Locale all have different behaviors for how they encode .current. All 3 mostly agree that .autoupdatingCurrent is always encoded as as sentinel value becoming the .autoupdatingCurrent at decode time, and that fixed values are decoded simply as fixed values (Calendar doesn't do the latter sometimes). However:

  • TimeZone always encodes .current as a fixed value. When decoded it is the same time zone that was encoded regardless of .current at decode time
  • Locale always encodes .current as a sentinel value. When decoded, it takes the value of whatever .current is at decode time regardless of the locale that was encoded
  • Calendar encodes a sentinel current value if the calendar to be encoded is equivalent to the current locale (i.e. if it has the same identifier/properties). This means that .current is always encoded as a sentinel value like Locale, but some fixed values are also encoded as a .current sentinel

The desired behavior is what TimeZone does today - .current represents a snapshot in time and we should encode that fixed snapshot unless .autoupdatingCurrent is used (in which case it should update at decode time). This is trickier for Locale because Locale contains extra user preferences that are persisted neither in the identifier nor other archive keys (which is why it is not its behavior today). This means encoding Locale.current as a fixed locale with just an identifier results in lost information due to lost preferences.

To fix this we do two things:

  • Locale now persists preferences in the archive (when present) alongside the identifier (to prevent data loss) and .current is no longer decoded as a sentinel to match TimeZone
  • Calendar no longer decodes .current as a sentinel value to match TimeZone (and now Locale)

With this change, we always decode a Calendar that was encoded as .current as a fixed Calendar based on the encoded values. We still encode the .current key to preserve the preexisting behavior when decoding on an older runtime, but newer runtimes will ignore the .current sentinel and decode the various keys.

This also adds unit tests to each type that validates consistent behavior for all 3 types.

@jmschonfeld
Copy link
Contributor Author

@swift-ci please test

return result as CFDictionary
}
var icuSymbolsAndStrings = try container.decodeIfPresent(ICUSymbolsAndStrings.self, forKey: .icuSymbolsAndStrings)
if (icuDateFormats != nil || icuNumberSymbols != nil) && icuSymbolsAndStrings == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It looks like we can always create a icuSymbolsAndStrings since we're going to always need one below at init?

self.icuTimeFormatStrings = try container.decodeIfPresent([String : String].self, forKey: .icuTimeFormatStrings).map { $0 as CFDictionary }
self.icuNumberFormatStrings = try container.decodeIfPresent([String : String].self, forKey: .icuNumberFormatStrings).map { $0 as CFDictionary }

// Will be filled in by LocalePreferences serialized value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this comment. I was indeed confused above why these two are handled separately from the others.

@jmschonfeld
Copy link
Contributor Author

@swift-ci please test

@jmschonfeld jmschonfeld merged commit b9c791c into swiftlang:main Sep 4, 2025
19 checks passed
@jmschonfeld jmschonfeld deleted the i18n-serialization branch September 4, 2025 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants