//
|
// SwiftDate
|
// Parse, validate, manipulate, and display dates, time and timezones in Swift
|
//
|
// Created by Daniele Margutti
|
// - Web: https://www.danielemargutti.com
|
// - Twitter: https://twitter.com/danielemargutti
|
// - Mail: hello@danielemargutti.com
|
//
|
// Copyright © 2019 Daniele Margutti. Licensed under MIT License.
|
//
|
|
import Foundation
|
|
/// Region define a context both for `Date` and `DateInRegion`.
|
/// Each `Date` is assigned to the currently set `SwiftDate.default
|
public struct Region: Decodable, Encodable, Equatable, Hashable, CustomStringConvertible {
|
|
// MARK: - Properties
|
|
/// Calendar associated with region
|
public let calendar: Calendar
|
|
/// Locale associated with region
|
public var locale: Locale { return calendar.locale! }
|
|
/// Timezone associated with region
|
public var timeZone: TimeZone { return calendar.timeZone }
|
|
/// Description of the object
|
public var description: String {
|
return "{calendar='\(calendar.identifier)', timezone='\(timeZone.identifier)', locale='\(locale.identifier)'}"
|
}
|
|
public func hash(into hasher: inout Hasher) {
|
hasher.combine(calendar)
|
}
|
|
// MARK: Initialization
|
|
/// Initialize a new region with given parameters.
|
///
|
/// - Parameters:
|
/// - calendar: calendar for region, if not specified `defaultRegions`'s calendar is used instead.
|
/// - timezone: timezone for region, if not specified `defaultRegions`'s timezone is used instead.
|
/// - locale: locale for region, if not specified `defaultRegions`'s locale is used instead.
|
public init(calendar: CalendarConvertible = SwiftDate.defaultRegion.calendar,
|
zone: ZoneConvertible = SwiftDate.defaultRegion.timeZone,
|
locale: LocaleConvertible = SwiftDate.defaultRegion.locale) {
|
self.calendar = Calendar.newCalendar(calendar, configure: {
|
$0.timeZone = zone.toTimezone()
|
$0.locale = locale.toLocale()
|
})
|
}
|
|
/// Initialize a new Region by reading the `timeZone`,`calendar` and `locale`
|
/// parameters from the passed `DateComponents` instance.
|
/// For any `nil` parameter the correspondent `SwiftDate.defaultRegion` is used instead.
|
///
|
/// - Parameter fromDateComponents: date components
|
public init(fromDateComponents components: DateComponents) {
|
let tz = (components.timeZone ?? Zones.current.toTimezone())
|
let cal = (components.calendar ?? Calendars.gregorian.toCalendar())
|
let loc = (cal.locale ?? Locales.current.toLocale())
|
self.init(calendar: cal, zone: tz, locale: loc)
|
}
|
|
public static var UTC: Region {
|
return Region(calendar: Calendar.autoupdatingCurrent,
|
zone: Zones.gmt.toTimezone(),
|
locale: Locale.autoupdatingCurrent)
|
}
|
|
/// Return the current local device's region where all attributes are set to the device's values.
|
///
|
/// - Returns: Region
|
public static var local: Region {
|
return Region(calendar: Calendar.autoupdatingCurrent,
|
zone: TimeZone.autoupdatingCurrent,
|
locale: Locale.autoupdatingCurrent)
|
}
|
|
/// ISO Region is defined by the gregorian calendar, gmt timezone and english posix locale
|
public static var ISO: Region {
|
return Region(calendar: Calendars.gregorian.toCalendar(),
|
zone: Zones.gmt.toTimezone(),
|
locale: Locales.englishUnitedStatesComputer)
|
}
|
|
/// Return an auto updating region where all settings are obtained from the current's device settings.
|
///
|
/// - Returns: Region
|
public static var current: Region {
|
return Region(calendar: Calendar.autoupdatingCurrent,
|
zone: TimeZone.autoupdatingCurrent,
|
locale: Locale.autoupdatingCurrent)
|
}
|
|
/// Return a new region in current's device timezone with optional adjust of the calendar and locale.
|
///
|
/// - Parameters:
|
/// - locale: locale to set
|
/// - calendar: calendar to set
|
/// - Returns: region
|
public static func currentIn(locale: LocaleConvertible? = nil, calendar: CalendarConvertible? = nil) -> Region {
|
return Region(calendar: (calendar ?? SwiftDate.defaultRegion.calendar),
|
zone: SwiftDate.defaultRegion.timeZone,
|
locale: (locale ?? SwiftDate.defaultRegion.locale))
|
}
|
|
/// Return the current date expressed into the receiver region.
|
///
|
/// - Returns: `DateInRegion` instance
|
public func nowInThisRegion() -> DateInRegion {
|
return DateInRegion(Date(), region: self)
|
}
|
|
// MARK: - Codable Support
|
|
enum CodingKeys: String, CodingKey {
|
case calendar
|
case locale
|
case timezone
|
}
|
|
public func encode(to encoder: Encoder) throws {
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
try container.encode(calendar.timeZone.identifier, forKey: .timezone)
|
try container.encode(calendar.locale!.identifier, forKey: .locale)
|
try container.encode(calendar.identifier.description, forKey: .calendar)
|
}
|
|
public init(from decoder: Decoder) throws {
|
let values = try decoder.container(keyedBy: CodingKeys.self)
|
let calId = Calendar.Identifier( try values.decode(String.self, forKey: .calendar))
|
let tz = (TimeZone(identifier: try values.decode(String.self, forKey: .timezone)) ?? SwiftDate.defaultRegion.timeZone)
|
let lc = Locale(identifier: try values.decode(String.self, forKey: .locale))
|
calendar = Calendar.newCalendar(calId, configure: {
|
$0.timeZone = tz
|
$0.locale = lc
|
})
|
}
|
|
// MARK: - Comparable
|
|
public static func == (lhs: Region, rhs: Region) -> Bool {
|
// Note: equality does not consider other parameters than the identifier of the major
|
// attributes (calendar, timezone and locale). Deeper comparisor must be made directly
|
// between Calendar (it may fail when you encode/decode autoUpdating calendars).
|
return
|
(lhs.calendar.identifier == rhs.calendar.identifier) &&
|
(lhs.timeZone.identifier == rhs.timeZone.identifier) &&
|
(lhs.locale.identifier == rhs.locale.identifier)
|
}
|
}
|