Skip to content

Commit ce537e2

Browse files
committed
Improve DateCoding experience
1 parent 2eaa753 commit ce537e2

File tree

3 files changed

+284
-205
lines changed

3 files changed

+284
-205
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
//===------------------------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftTencentSCFRuntime open source project
4+
//
5+
// Copyright (c) 2020 stevapple and the SwiftTencentSCFRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftTencentSCFRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===------------------------------------------------------------------------------------===//
14+
15+
import struct Foundation.Date
16+
import class Foundation.DateFormatter
17+
import class Foundation.ISO8601DateFormatter
18+
import struct Foundation.Locale
19+
import struct Foundation.TimeInterval
20+
21+
protocol DateCodingHelper {
22+
static func decode(from decoder: Decoder) throws -> Date
23+
static func encode(_ date: Date, to encoder: Encoder) throws
24+
}
25+
26+
extension DateCodingHelper {
27+
static func decodeIfPresent(from decoder: Decoder) throws -> Date? {
28+
if try decoder.singleValueContainer().decodeNil() {
29+
return nil
30+
} else {
31+
return try decode(from: decoder)
32+
}
33+
}
34+
35+
static func encodeIfPresent(_ date: Date?, to encoder: Encoder) throws {
36+
if let rawDate = date {
37+
try encode(rawDate, to: encoder)
38+
}
39+
}
40+
}
41+
42+
enum DateCoding {
43+
enum ISO8601: DateCodingHelper {
44+
static func decode(from decoder: Decoder) throws -> Date {
45+
let container = try decoder.singleValueContainer()
46+
let string = try container.decode(String.self)
47+
guard let date = Self.dateFormatter.date(from: string) else {
48+
throw DecodingError.dataCorruptedError(in: container, debugDescription:
49+
"Expected date to be in ISO 8601 date format, but `\(string)` does not forfill format")
50+
}
51+
return date
52+
}
53+
54+
static func encode(_ date: Date, to encoder: Encoder) throws {
55+
var container = encoder.singleValueContainer()
56+
let string = Self.dateFormatter.string(from: date)
57+
try container.encode(string)
58+
}
59+
60+
static let dateFormatter = ISO8601DateFormatter()
61+
}
62+
63+
enum ISO8601WithFractionalSeconds: DateCodingHelper {
64+
static func decode(from decoder: Decoder) throws -> Date {
65+
let container = try decoder.singleValueContainer()
66+
let string = try container.decode(String.self)
67+
guard let date = Self.dateFormatter.date(from: string) else {
68+
throw DecodingError.dataCorruptedError(in: container, debugDescription:
69+
"Expected date to be in ISO 8601 date format with fractional seconds, but `\(string)` does not forfill format")
70+
}
71+
return date
72+
}
73+
74+
static func encode(_ date: Date, to encoder: Encoder) throws {
75+
var container = encoder.singleValueContainer()
76+
let string = Self.dateFormatter.string(from: date)
77+
try container.encode(string)
78+
}
79+
80+
static let dateFormatter: ISO8601DateFormatter = {
81+
let formatter = ISO8601DateFormatter()
82+
formatter.formatOptions = [
83+
.withInternetDateTime,
84+
.withDashSeparatorInDate,
85+
.withColonSeparatorInTime,
86+
.withColonSeparatorInTimeZone,
87+
.withFractionalSeconds,
88+
]
89+
return formatter
90+
}()
91+
}
92+
93+
enum RFC5322: DateCodingHelper {
94+
static func decode(from decoder: Decoder) throws -> Date {
95+
let container = try decoder.singleValueContainer()
96+
var string = try container.decode(String.self)
97+
// RFC5322 dates sometimes have the alphabetic version of the timezone in brackets after the numeric version. The date formatter
98+
// fails to parse this so we need to remove this before parsing.
99+
if let bracket = string.firstIndex(of: "(") {
100+
string = String(string[string.startIndex ..< bracket].trimmingCharacters(in: .whitespaces))
101+
}
102+
guard let date = Self.dateFormatter.date(from: string) else {
103+
throw DecodingError.dataCorruptedError(in: container, debugDescription:
104+
"Expected date to be in RFC 5322 date-time format with fractional seconds, but `\(string)` does not forfill format")
105+
}
106+
return date
107+
}
108+
109+
static func encode(_ date: Date, to encoder: Encoder) throws {
110+
var container = encoder.singleValueContainer()
111+
let string = Self.dateFormatter.string(from: date)
112+
try container.encode(string)
113+
}
114+
115+
static let dateFormatter: DateFormatter = {
116+
let formatter = DateFormatter()
117+
formatter.dateFormat = "EEE, d MMM yyy HH:mm:ss z"
118+
formatter.locale = Locale(identifier: "en_US_POSIX")
119+
return formatter
120+
}()
121+
}
122+
123+
enum UnixTimestamp: DateCodingHelper {
124+
static func decode(from decoder: Decoder) throws -> Date {
125+
let container = try decoder.singleValueContainer()
126+
let timestamp = try container.decode(UInt.self)
127+
let date = Date(timeIntervalSince1970: .init(timestamp))
128+
return date
129+
}
130+
131+
static func encode(_ date: Date, to encoder: Encoder) throws {
132+
var container = encoder.singleValueContainer()
133+
let timestamp = UInt(date.timeIntervalSince1970)
134+
try container.encode(timestamp)
135+
}
136+
}
137+
138+
enum UnixTimestampWithFractionalSeconds: DateCodingHelper {
139+
static func decode(from decoder: Decoder) throws -> Date {
140+
let container = try decoder.singleValueContainer()
141+
let timestamp = try container.decode(TimeInterval.self)
142+
let date = Date(timeIntervalSince1970: timestamp)
143+
return date
144+
}
145+
146+
static func encode(_ date: Date, to encoder: Encoder) throws {
147+
var container = encoder.singleValueContainer()
148+
let timestamp = date.timeIntervalSince1970
149+
try container.encode(timestamp)
150+
}
151+
}
152+
}
153+
154+
extension KeyedEncodingContainerProtocol {
155+
mutating func encode(_ date: Date, forKey key: Self.Key, using coding: DateCodingHelper.Type) throws {
156+
try coding.encode(date, to: self.superEncoder(forKey: key))
157+
}
158+
159+
mutating func encode(_ date: Date?, forKey key: Self.Key, using coding: DateCodingHelper.Type) throws {
160+
if let rawDate = date {
161+
try coding.encode(rawDate, to: self.superEncoder(forKey: key))
162+
} else {
163+
try self.encodeNil(forKey: key)
164+
}
165+
}
166+
167+
mutating func encodeIfPresent(_ date: Date?, forKey key: Self.Key, using coding: DateCodingHelper.Type) throws {
168+
if let rawDate = date {
169+
try coding.encode(rawDate, to: self.superEncoder(forKey: key))
170+
}
171+
}
172+
}
173+
174+
extension KeyedDecodingContainerProtocol {
175+
func decode(_ type: Date.Type, forKey key: Self.Key, using coding: DateCodingHelper.Type) throws -> Date {
176+
try coding.decode(from: superDecoder(forKey: key))
177+
}
178+
179+
func decodeIfPresent(_ type: Date.Type, forKey key: Self.Key, using coding: DateCodingHelper.Type) throws -> Date? {
180+
if contains(key) {
181+
return try coding.decode(from: superDecoder(forKey: key))
182+
} else {
183+
return nil
184+
}
185+
}
186+
}

0 commit comments

Comments
 (0)