Skip to content

Codable conformance to some Foundation types #1148

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
merged 6 commits into from
Aug 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions Foundation/Calendar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1189,3 +1189,36 @@ extension Calendar: _ObjectTypeBridgeable {
return result!
}
}

extension Calendar : Codable {
private enum CodingKeys : Int, CodingKey {
case identifier
case locale
case timeZone
case firstWeekday
case minimumDaysInFirstWeek
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let identifierString = try container.decode(String.self, forKey: .identifier)
let identifier = Calendar._fromNSCalendarIdentifier(NSCalendar.Identifier(rawValue: identifierString))
self.init(identifier: identifier)

self.locale = try container.decodeIfPresent(Locale.self, forKey: .locale)
self.timeZone = try container.decode(TimeZone.self, forKey: .timeZone)
self.firstWeekday = try container.decode(Int.self, forKey: .firstWeekday)
self.minimumDaysInFirstWeek = try container.decode(Int.self, forKey: .minimumDaysInFirstWeek)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

let identifier = Calendar._toNSCalendarIdentifier(self.identifier).rawValue
try container.encode(identifier, forKey: .identifier)
try container.encode(self.locale, forKey: .locale)
try container.encode(self.timeZone, forKey: .timeZone)
try container.encode(self.firstWeekday, forKey: .firstWeekday)
try container.encode(self.minimumDaysInFirstWeek, forKey: .minimumDaysInFirstWeek)
}
}
79 changes: 79 additions & 0 deletions Foundation/DateComponents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,82 @@ extension DateComponents : _ObjectTypeBridgeable {
}
}

extension DateComponents : Codable {
private enum CodingKeys : Int, CodingKey {
case calendar
case timeZone
case era
case year
case month
case day
case hour
case minute
case second
case nanosecond
case weekday
case weekdayOrdinal
case quarter
case weekOfMonth
case weekOfYear
case yearForWeekOfYear
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let calendar = try container.decodeIfPresent(Calendar.self, forKey: .calendar)
let timeZone = try container.decodeIfPresent(TimeZone.self, forKey: .timeZone)
let era = try container.decodeIfPresent(Int.self, forKey: .era)
let year = try container.decodeIfPresent(Int.self, forKey: .year)
let month = try container.decodeIfPresent(Int.self, forKey: .month)
let day = try container.decodeIfPresent(Int.self, forKey: .day)
let hour = try container.decodeIfPresent(Int.self, forKey: .hour)
let minute = try container.decodeIfPresent(Int.self, forKey: .minute)
let second = try container.decodeIfPresent(Int.self, forKey: .second)
let nanosecond = try container.decodeIfPresent(Int.self, forKey: .nanosecond)

let weekday = try container.decodeIfPresent(Int.self, forKey: .weekday)
let weekdayOrdinal = try container.decodeIfPresent(Int.self, forKey: .weekdayOrdinal)
let quarter = try container.decodeIfPresent(Int.self, forKey: .quarter)
let weekOfMonth = try container.decodeIfPresent(Int.self, forKey: .weekOfMonth)
let weekOfYear = try container.decodeIfPresent(Int.self, forKey: .weekOfYear)
let yearForWeekOfYear = try container.decodeIfPresent(Int.self, forKey: .yearForWeekOfYear)

self.init(calendar: calendar,
timeZone: timeZone,
era: era,
year: year,
month: month,
day: day,
hour: hour,
minute: minute,
second: second,
nanosecond: nanosecond,
weekday: weekday,
weekdayOrdinal: weekdayOrdinal,
quarter: quarter,
weekOfMonth: weekOfMonth,
weekOfYear: weekOfYear,
yearForWeekOfYear: yearForWeekOfYear)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(self.calendar, forKey: .calendar)
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 applying this, BTW. I need to make a similar fix in the Foundation overlay itself.

try container.encodeIfPresent(self.timeZone, forKey: .timeZone)
try container.encodeIfPresent(self.era, forKey: .era)
try container.encodeIfPresent(self.year, forKey: .year)
try container.encodeIfPresent(self.month, forKey: .month)
try container.encodeIfPresent(self.day, forKey: .day)
try container.encodeIfPresent(self.hour, forKey: .hour)
try container.encodeIfPresent(self.minute, forKey: .minute)
try container.encodeIfPresent(self.second, forKey: .second)
try container.encodeIfPresent(self.nanosecond, forKey: .nanosecond)

try container.encodeIfPresent(self.weekday, forKey: .weekday)
try container.encodeIfPresent(self.weekdayOrdinal, forKey: .weekdayOrdinal)
try container.encodeIfPresent(self.quarter, forKey: .quarter)
try container.encodeIfPresent(self.weekOfMonth, forKey: .weekOfMonth)
try container.encodeIfPresent(self.weekOfYear, forKey: .weekOfYear)
try container.encodeIfPresent(self.yearForWeekOfYear, forKey: .yearForWeekOfYear)
}
}
23 changes: 23 additions & 0 deletions Foundation/TimeZone.swift
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,26 @@ extension TimeZone {
return result!
}
}

extension TimeZone : Codable {
private enum CodingKeys : Int, CodingKey {
case identifier
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let identifier = try container.decode(String.self, forKey: .identifier)

guard let timeZone = TimeZone(identifier: identifier) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
debugDescription: "Invalid TimeZone identifier."))
}

self = timeZone
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.identifier, forKey: .identifier)
}
}
97 changes: 97 additions & 0 deletions TestFoundation/TestCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,100 @@ class TestCodable : XCTestCase {
}
}

// MARK: - TimeZone
lazy var timeZoneValues: [TimeZone] = {
var values = [
TimeZone(identifier: "America/Los_Angeles")!,
TimeZone(identifier: "UTC")!,
]

#if !os(Linux)
// Disabled due to [SR-5598] bug, which occurs on Linux, and breaks
// TimeZone.current == TimeZone(identifier: TimeZone.current.identifier) equality,
// causing encode -> decode -> compare test to fail.
values.append(TimeZone.current)
#endif

return values
}()

func test_TimeZone_JSON() {
for timeZone in timeZoneValues {
expectRoundTripEqualityThroughJSON(for: timeZone)
}
}

// MARK: - Calendar
lazy var calendarValues: [Calendar] = {
var values = [
Calendar(identifier: .gregorian),
Calendar(identifier: .buddhist),
Calendar(identifier: .chinese),
Calendar(identifier: .coptic),
Calendar(identifier: .ethiopicAmeteMihret),
Calendar(identifier: .ethiopicAmeteAlem),
Calendar(identifier: .hebrew),
Calendar(identifier: .iso8601),
Calendar(identifier: .indian),
Calendar(identifier: .islamic),
Calendar(identifier: .islamicCivil),
Calendar(identifier: .japanese),
Calendar(identifier: .persian),
Calendar(identifier: .republicOfChina),
]

#if os(Linux)
// Custom timeZone set to work around [SR-5598] bug, which occurs on Linux, and breaks equality after
// serializing and deserializing TimeZone.current
for index in values.indices {
values[index].timeZone = TimeZone(identifier: "UTC")!
}
#endif

return values
}()

func test_Calendar_JSON() {
for calendar in calendarValues {
expectRoundTripEqualityThroughJSON(for: calendar)
}
}

// MARK: - DateComponents
lazy var dateComponents: Set<Calendar.Component> = [
.era,
.year,
.month,
.day,
.hour,
.minute,
.second,
.weekday,
.weekdayOrdinal,
.weekOfMonth,
.weekOfYear,
.yearForWeekOfYear,
.timeZone,
.calendar,
// [SR-5576] Disabled due to a bug in Calendar.dateComponents(_:from:) which crashes on Darwin and returns
// invalid values on Linux if components include .nanosecond or .quarter.
// .nanosecond,
// .quarter,
]

func test_DateComponents_JSON() {
#if os(Linux)
var calendar = Calendar(identifier: .gregorian)
// Custom timeZone set to work around [SR-5598] bug, which occurs on Linux, and breaks equality after
// serializing and deserializing TimeZone.current
calendar.timeZone = TimeZone(identifier: "UTC")!
#else
let calendar = Calendar(identifier: .gregorian)
#endif

let components = calendar.dateComponents(dateComponents, from: Date(timeIntervalSince1970: 1501283776))
expectRoundTripEqualityThroughJSON(for: components)
}
}

extension TestCodable {
Expand All @@ -323,6 +417,9 @@ extension TestCodable {
("test_CGSize_JSON", test_CGSize_JSON),
("test_CGRect_JSON", test_CGRect_JSON),
("test_CharacterSet_JSON", test_CharacterSet_JSON),
("test_TimeZone_JSON", test_TimeZone_JSON),
("test_Calendar_JSON", test_Calendar_JSON),
("test_DateComponents_JSON", test_DateComponents_JSON),
]
}
}