Skip to content

Commit

Permalink
Simplify coding if it's using standard values
Browse files Browse the repository at this point in the history
If the region's values are equivalent to the standard values, then we only really to encode the identifiers and not the full object
  • Loading branch information
davedelong committed Feb 11, 2024
1 parent e3fe2d9 commit 2e959fe
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 7 deletions.
95 changes: 89 additions & 6 deletions Sources/Time/4-Fixed Values/Fixed+Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,17 @@ extension Region: Codable {
case locale
case calendar
}

#warning("TODO: if the c/l/tz are unchanged from default, only encode the identifiers")

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let calendar = try container.decode(Calendar.self, forKey: .calendar)
let timeZone = try container.decode(TimeZone.self, forKey: .timeZone)
let locale = try container.decode(Locale.self, forKey: .locale)
self.init(calendar: calendar, timeZone: timeZone, locale: locale)

let calendarContainer = try container.decode(CodableCalendar.self, forKey: .calendar)
let timeZoneContainer = try container.decode(CodableTimeZone.self, forKey: .timeZone)
let localeContainer = try container.decode(CodableLocale.self, forKey: .locale)

self.init(calendar: calendarContainer.calendar,
timeZone: timeZoneContainer.timeZone,
locale: localeContainer.locale)
}

public func encode(to encoder: Encoder) throws {
Expand Down Expand Up @@ -62,3 +64,84 @@ extension Fixed: Codable {
try container.encode(instant, forKey: .value)
}
}

private struct CodableLocale: Codable {

let locale: Locale

init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
let identifier = try container.decode(String.self)
self.locale = Locale.standard(identifier)
} catch {
self.locale = try Locale(from: decoder)
}
}

func encode(to encoder: Encoder) throws {
let standard = Locale.standard(locale.identifier)

if standard.isEquivalent(to: locale) {
var single = encoder.singleValueContainer()
try single.encode(locale.identifier)
} else {
try locale.encode(to: encoder)
}
}

}

private struct CodableTimeZone: Codable {

let timeZone: TimeZone

init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
let identifier = try container.decode(String.self)
self.timeZone = TimeZone.standard(identifier)
} catch {
self.timeZone = try TimeZone(from: decoder)
}
}

func encode(to encoder: Encoder) throws {
let standard = TimeZone.standard(timeZone.identifier)

if standard.isEquivalent(to: timeZone) {
var single = encoder.singleValueContainer()
try single.encode(timeZone.identifier)
} else {
try timeZone.encode(to: encoder)
}
}

}

private struct CodableCalendar: Codable {

let calendar: Calendar

init(from decoder: Decoder) throws {
do {
let container = try decoder.singleValueContainer()
let identifier = try container.decode(Calendar.Identifier.self)
self.calendar = Calendar(identifier: identifier)
} catch {
self.calendar = try Calendar(from: decoder)
}
}

func encode(to encoder: Encoder) throws {
let standard = Calendar.standard(calendar.identifier)

if standard.isEquivalent(to: calendar) {
var single = encoder.singleValueContainer()
try single.encode(calendar.identifier)
} else {
try calendar.encode(to: encoder)
}
}

}
2 changes: 2 additions & 0 deletions Sources/Time/Internals/DateFormatterCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extension DateFormatter {

internal static func formatter(for templates: Array<Format?>, region: Region) -> DateFormatter {
let template = templates.compactMap { $0?.template }.joined()
if template.isEmpty { fatalError("Somehow have an empty template? this should not happen") }
return self.formatter(for: .init(configuration: .template(template), region: region))
}
}
Expand All @@ -38,6 +39,7 @@ private class DateFormatterCache {

static let shared = DateFormatterCache()

#warning("TODO: better locking primitive?")
private let queue = DispatchQueue(label: "DateFormatterCache")

private var formatters = Dictionary<DateFormatter.Key, DateFormatter>()
Expand Down
53 changes: 53 additions & 0 deletions Sources/Time/Internals/Region+Equivalence.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// File.swift
//
//
// Created by Dave DeLong on 2/11/24.
//

import Foundation

extension Calendar {

func isEquivalent(to other: Calendar) -> Bool {
guard identifier == other.identifier else { return false }
guard timeZone.isEquivalent(to: other.timeZone) else { return false }
guard firstWeekday == other.firstWeekday else { return false }
guard minimumDaysInFirstWeek == other.minimumDaysInFirstWeek else { return false }

return true
}

}

extension TimeZone {

func isEquivalent(to other: TimeZone) -> Bool {
guard identifier == other.identifier else { return false }

return true
}

}

extension Locale {

func isEquivalent(to other: Locale) -> Bool {
guard calendar.identifier == other.calendar.identifier else { return false }
guard collation == other.collation else { return false }
guard currency == other.currency else { return false }
guard firstDayOfWeek == other.firstDayOfWeek else { return false }
guard hourCycle == other.hourCycle else { return false }

guard measurementSystem == other.measurementSystem else { return false }
guard numberingSystem == other.numberingSystem else { return false }
guard region == other.region else { return false }
guard subdivision == other.subdivision else { return false }
guard variant == other.variant else { return false }

guard language == other.language else { return false }

return true
}

}
57 changes: 57 additions & 0 deletions Sources/Time/Internals/SimpleCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// File.swift
//
//
// Created by Dave DeLong on 2/11/24.
//

import Foundation

extension Locale {
private static let cache = SimpleCache<String, Locale>()

static func standard(_ id: String) -> Locale {
return cache.get(id, create: { Locale(identifier: id) })
}
}

extension Calendar {
private static let cache = SimpleCache<Calendar.Identifier, Calendar>()

static func standard(_ id: Calendar.Identifier) -> Calendar {
return cache.get(id, create: { Calendar(identifier: id) })
}
}

extension TimeZone {
private static let cache = SimpleCache<String, TimeZone>()

static func standard(_ id: String) -> TimeZone {
return cache.get(id, create: { TimeZone(identifier: id)! })
}
}

private class SimpleCache<Key: Hashable, T> {

private var storage = Dictionary<Key, T>()

#warning("TODO: better synchronization primitive?")
private let lock = NSLock()

init() { }

func get(_ id: Key, create: () -> T) -> T {
lock.lock()

let returnValue: T
if let existing = storage[id] {
returnValue = existing
} else {
returnValue = create()
storage[id] = returnValue
}

lock.unlock()
return returnValue
}
}
11 changes: 10 additions & 1 deletion Sources/Time/Internals/Snapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ extension Locale {

private static let currentSnapshot: Snapshot<Locale> = Snapshot(notification: NSLocale.currentLocaleDidChangeNotification, createSnapshot: {
let auto = Locale.autoupdatingCurrent

let standard = Locale.standard(auto.identifier)
if auto.isEquivalent(to: standard) { return standard }

var components = Locale.Components()

components.calendar = auto.calendar.identifier
Expand Down Expand Up @@ -41,7 +45,7 @@ extension Locale {
extension TimeZone {

private static let currentSnapshot: Snapshot<TimeZone> = Snapshot(notification: .NSSystemTimeZoneDidChange, createSnapshot: {
return TimeZone(identifier: TimeZone.autoupdatingCurrent.identifier)!
return TimeZone.standard(TimeZone.autoupdatingCurrent.identifier)
})

func snapshot() -> Self {
Expand All @@ -55,6 +59,10 @@ extension Calendar {

private static let currentSnapshot: Snapshot<Calendar> = Snapshot(notification: NSLocale.currentLocaleDidChangeNotification, createSnapshot: {
let auto = Calendar.autoupdatingCurrent

let standard = Calendar.standard(auto.identifier)
if auto.isEquivalent(to: standard) { return standard }

var snapshot = Calendar(identifier: auto.identifier)

// don't bother snapshotting the timezone and locale,
Expand Down Expand Up @@ -82,6 +90,7 @@ private class Snapshot<T> {
private var _snapshot: T?
private var observationToken: NSObjectProtocol?

#warning("TODO: better synchronization primitive?")
private let lock = NSLock()

var snapshot: T {
Expand Down

0 comments on commit 2e959fe

Please sign in to comment.