12
12
//
13
13
//===----------------------------------------------------------------------===//
14
14
15
+ #if canImport(FoundationEssentials)
16
+ import FoundationEssentials
17
+ #else
15
18
import Foundation
19
+ #endif
16
20
17
21
@propertyWrapper
18
22
public struct ISO8601Coding : Decodable , Sendable {
@@ -25,22 +29,16 @@ public struct ISO8601Coding: Decodable, Sendable {
25
29
public init ( from decoder: Decoder ) throws {
26
30
let container = try decoder. singleValueContainer ( )
27
31
let dateString = try container. decode ( String . self)
28
- guard let date = Self . dateFormatter. date ( from: dateString) else {
32
+
33
+ do {
34
+ self . wrappedValue = try Date ( dateString, strategy: . iso8601)
35
+ } catch {
29
36
throw DecodingError . dataCorruptedError (
30
37
in: container,
31
38
debugDescription:
32
39
" Expected date to be in ISO8601 date format, but ` \( dateString) ` is not in the correct format "
33
40
)
34
41
}
35
- self . wrappedValue = date
36
- }
37
-
38
- private static var dateFormatter : DateFormatter {
39
- let formatter = DateFormatter ( )
40
- formatter. locale = Locale ( identifier: " en_US_POSIX " )
41
- formatter. timeZone = TimeZone ( secondsFromGMT: 0 )
42
- formatter. dateFormat = " yyyy-MM-dd'T'HH:mm:ssZZZZZ "
43
- return formatter
44
42
}
45
43
}
46
44
@@ -55,22 +53,19 @@ public struct ISO8601WithFractionalSecondsCoding: Decodable, Sendable {
55
53
public init ( from decoder: Decoder ) throws {
56
54
let container = try decoder. singleValueContainer ( )
57
55
let dateString = try container. decode ( String . self)
58
- guard let date = Self . dateFormatter. date ( from: dateString) else {
56
+ do {
57
+ self . wrappedValue = try Date ( dateString, strategy: Self . iso8601WithFractionalSeconds)
58
+ } catch {
59
59
throw DecodingError . dataCorruptedError (
60
60
in: container,
61
61
debugDescription:
62
62
" Expected date to be in ISO8601 date format with fractional seconds, but ` \( dateString) ` is not in the correct format "
63
63
)
64
64
}
65
- self . wrappedValue = date
66
65
}
67
66
68
- private static var dateFormatter : DateFormatter {
69
- let formatter = DateFormatter ( )
70
- formatter. locale = Locale ( identifier: " en_US_POSIX " )
71
- formatter. timeZone = TimeZone ( secondsFromGMT: 0 )
72
- formatter. dateFormat = " yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ "
73
- return formatter
67
+ private static var iso8601WithFractionalSeconds : Date . ISO8601FormatStyle {
68
+ Date . ISO8601FormatStyle ( includingFractionalSeconds: true )
74
69
}
75
70
}
76
71
@@ -90,20 +85,18 @@ public struct RFC5322DateTimeCoding: Decodable, Sendable {
90
85
if let bracket = string. firstIndex ( of: " ( " ) {
91
86
string = String ( string [ string. startIndex..< bracket] . trimmingCharacters ( in: . whitespaces) )
92
87
}
93
- for formatter in Self . dateFormatters {
94
- if let date = formatter. date ( from: string) {
95
- self . wrappedValue = date
96
- return
97
- }
98
- }
99
- throw DecodingError . dataCorruptedError (
88
+ do {
89
+ self . wrappedValue = try Date ( string, strategy: RFC5322DateStrategy ( ) )
90
+ } catch {
91
+ throw DecodingError . dataCorruptedError (
100
92
in: container,
101
93
debugDescription:
102
94
" Expected date to be in RFC5322 date-time format, but ` \( string) ` is not in the correct format "
103
- )
95
+ )
96
+ }
104
97
}
105
98
106
- private static var dateFormatters : [ DateFormatter ] {
99
+ /* private static var dateFormatters: [DateFormatter] {
107
100
// rfc5322 dates received in SES mails sometimes do not include the day, so need two dateformatters
108
101
// one with a day and one without
109
102
let formatterWithDay = DateFormatter()
@@ -113,5 +106,204 @@ public struct RFC5322DateTimeCoding: Decodable, Sendable {
113
106
formatterWithoutDay.dateFormat = "d MMM yyy HH:mm:ss z"
114
107
formatterWithoutDay.locale = Locale(identifier: "en_US_POSIX")
115
108
return [formatterWithDay, formatterWithoutDay]
109
+ }*/
110
+ }
111
+
112
+ struct RFC5322DateError : Error { }
113
+
114
+ struct RFC5322DateStrategy : ParseStrategy {
115
+ func parse( _ input: String ) throws -> Date {
116
+ guard let components = self . components ( from: input) else {
117
+ throw RFC5322DateError ( )
118
+ }
119
+ guard let date = components. date else {
120
+ throw RFC5322DateError ( )
121
+ }
122
+ return date
123
+ }
124
+
125
+ func components( from input: String ) -> DateComponents ? {
126
+ var s = input [ ... ]
127
+ return s. withUTF8 { buffer -> DateComponents ? in
128
+ // if the 4th character is a comma, then we have a day of the week
129
+ guard buffer. count > 5 else { return nil }
130
+ var offset = 0
131
+ if buffer [ 3 ] == UInt8 ( ascii: " , " ) {
132
+ offset = 5
133
+ }
134
+
135
+ func parseDay( _ it: inout UnsafeBufferPointer < UInt8 > . Iterator ) -> Int ? {
136
+ let first = it. next ( )
137
+ let second = it. next ( )
138
+ guard let first = first, let second = second else { return nil }
139
+
140
+ guard first >= UInt8 ( ascii: " 0 " ) && first <= UInt8 ( ascii: " 9 " ) else { return nil }
141
+
142
+ if second >= UInt8 ( ascii: " 0 " ) && second <= UInt8 ( ascii: " 9 " ) {
143
+ return Int ( first - UInt8( ascii: " 0 " ) ) * 10 + Int( second - UInt8( ascii: " 0 " ) )
144
+ } else {
145
+ return Int ( first - UInt8( ascii: " 0 " ) )
146
+ }
147
+ }
148
+
149
+ func skipWhitespace( _ it: inout UnsafeBufferPointer < UInt8 > . Iterator ) -> UInt8 ? {
150
+ while let c = it. next ( ) {
151
+ if c != UInt8 ( ascii: " " ) {
152
+ return c
153
+ }
154
+ }
155
+ return nil
156
+ }
157
+
158
+ func parseMonth( _ it: inout UnsafeBufferPointer < UInt8 > . Iterator ) -> Int ? {
159
+ let first = it. nextAsciiLetter ( skippingWhitespace: true )
160
+ let second = it. nextAsciiLetter ( )
161
+ let third = it. nextAsciiLetter ( )
162
+ guard let first = first, let second = second, let third = third else { return nil }
163
+ guard first. isAsciiLetter else { return nil }
164
+ return monthMap [ [ first, second, third] ]
165
+ }
166
+
167
+ func parseYear( _ it: inout UnsafeBufferPointer < UInt8 > . Iterator ) -> Int ? {
168
+ let first = it. nextAsciiNumber ( skippingWhitespace: true )
169
+ let second = it. nextAsciiNumber ( )
170
+ let third = it. nextAsciiNumber ( )
171
+ let fourth = it. nextAsciiNumber ( )
172
+ guard let first = first, let second = second, let third = third, let fourth = fourth else { return nil }
173
+ return Int ( first - UInt8( ascii: " 0 " ) ) * 1000 + Int( second - UInt8( ascii: " 0 " ) ) * 100 + Int( third - UInt8( ascii: " 0 " ) ) * 10 + Int( fourth - UInt8( ascii: " 0 " ) )
174
+ }
175
+
176
+ func parseHour( _ it: inout UnsafeBufferPointer < UInt8 > . Iterator ) -> Int ? {
177
+ let first = it. nextAsciiNumber ( skippingWhitespace: true )
178
+ let second = it. nextAsciiNumber ( )
179
+ guard let first = first, let second = second else { return nil }
180
+ return Int ( first - UInt8( ascii: " 0 " ) ) * 10 + Int( second - UInt8( ascii: " 0 " ) )
181
+ }
182
+
183
+ func parseMinute( _ it: inout UnsafeBufferPointer < UInt8 > . Iterator ) -> Int ? {
184
+ let first = it. nextAsciiNumber ( skippingWhitespace: true )
185
+ let second = it. nextAsciiNumber ( )
186
+ guard let first = first, let second = second else { return nil }
187
+ return Int ( first - UInt8( ascii: " 0 " ) ) * 10 + Int( second - UInt8( ascii: " 0 " ) )
188
+ }
189
+
190
+ func parseSecond( _ it: inout UnsafeBufferPointer < UInt8 > . Iterator ) -> Int ? {
191
+ let first = it. nextAsciiNumber ( skippingWhitespace: true )
192
+ let second = it. nextAsciiNumber ( )
193
+ guard let first = first, let second = second else { return nil }
194
+ return Int ( first - UInt8( ascii: " 0 " ) ) * 10 + Int( second - UInt8( ascii: " 0 " ) )
195
+ }
196
+
197
+ func parseTimezone( _ it: inout UnsafeBufferPointer < UInt8 > . Iterator ) -> Int ? {
198
+ let plusMinus = it. nextSkippingWhitespace ( )
199
+ if let plusMinus, plusMinus == UInt8 ( ascii: " + " ) || plusMinus == UInt8 ( ascii: " - " ) {
200
+ let hour = parseHour ( & it)
201
+ let minute = parseMinute ( & it)
202
+ guard let hour = hour, let minute = minute else { return nil }
203
+ return ( hour * 60 + minute) * ( plusMinus == UInt8 ( ascii: " + " ) ? 1 : - 1 )
204
+ } else if let first = plusMinus {
205
+ let second = it. nextAsciiLetter ( )
206
+ let third = it. nextAsciiLetter ( )
207
+
208
+ guard let second = second, let third = third else { return nil }
209
+ let abbr = [ first, second, third]
210
+ return timezoneOffsetMap [ abbr]
211
+ }
212
+
213
+ return nil
214
+ }
215
+
216
+ var it = buffer. makeIterator ( )
217
+
218
+ for _ in 0 ..< offset {
219
+ _ = it. next ( )
220
+ }
221
+
222
+ guard let day = parseDay ( & it) else { return nil }
223
+ guard let month = parseMonth ( & it) else { return nil }
224
+ guard let year = parseYear ( & it) else { return nil }
225
+
226
+ guard let hour = parseHour ( & it) else { return nil }
227
+ guard it. expect ( UInt8 ( ascii: " : " ) ) else { return nil }
228
+ guard let minute = parseMinute ( & it) else { return nil }
229
+ guard it. expect ( UInt8 ( ascii: " : " ) ) else { return nil }
230
+ guard let second = parseSecond ( & it) else { return nil }
231
+
232
+ guard let timezoneOffset = parseTimezone ( & it) else { return nil }
233
+
234
+ return DateComponents (
235
+ calendar: Calendar ( identifier: . gregorian) ,
236
+ timeZone: TimeZone ( secondsFromGMT: timezoneOffset * 60 ) ,
237
+ year: year,
238
+ month: month,
239
+ day: day,
240
+ hour: hour,
241
+ minute: minute,
242
+ second: second
243
+ )
244
+ }
245
+ }
246
+ }
247
+
248
+ extension IteratorProtocol where Self. Element == UInt8 {
249
+ mutating func expect( _ expected: UInt8 ) -> Bool {
250
+ guard self . next ( ) == expected else { return false }
251
+ return true
252
+ }
253
+
254
+ mutating func nextSkippingWhitespace( ) -> UInt8 ? {
255
+ while let c = self . next ( ) {
256
+ if c != UInt8 ( ascii: " " ) {
257
+ return c
258
+ }
259
+ }
260
+ return nil
261
+ }
262
+
263
+ mutating func nextAsciiNumber( skippingWhitespace: Bool = false ) -> UInt8 ? {
264
+ while let c = self . next ( ) {
265
+ if skippingWhitespace {
266
+ if c == UInt8 ( ascii: " " ) {
267
+ continue
268
+ }
269
+ }
270
+ if c >= UInt8 ( ascii: " 0 " ) && c <= UInt8 ( ascii: " 9 " ) {
271
+ return c
272
+ } else {
273
+ return nil
274
+ }
275
+ }
276
+ return nil
277
+ }
278
+
279
+ mutating func nextAsciiLetter( skippingWhitespace: Bool = false ) -> UInt8 ? {
280
+ while let c = self . next ( ) {
281
+ if skippingWhitespace {
282
+ if c == UInt8 ( ascii: " " ) {
283
+ continue
284
+ }
285
+ }
286
+ if c >= UInt8 ( ascii: " A " ) && c <= UInt8 ( ascii: " Z " ) || c >= UInt8 ( ascii: " a " ) && c <= UInt8 ( ascii: " z " ) {
287
+ return c
288
+ } else {
289
+ return nil
290
+ }
291
+ }
292
+ return nil
293
+ }
294
+ }
295
+
296
+ extension UInt8 {
297
+ var isAsciiLetter : Bool {
298
+ return self >= UInt8 ( ascii: " A " ) && self <= UInt8 ( ascii: " Z " ) || self >= UInt8 ( ascii: " a " ) && self <= UInt8 ( ascii: " z " )
116
299
}
117
300
}
301
+
302
+ let monthMap : [ Array < UInt8 > : Int ] = [
303
+ Array ( " Jan " . utf8) : 1 , Array ( " Feb " . utf8) : 2 , Array ( " Mar " . utf8) : 3 , Array ( " Apr " . utf8) : 4 , Array ( " May " . utf8) : 5 , Array ( " Jun " . utf8) : 6 ,
304
+ Array ( " Jul " . utf8) : 7 , Array ( " Aug " . utf8) : 8 , Array ( " Sep " . utf8) : 9 , Array ( " Oct " . utf8) : 10 , Array ( " Nov " . utf8) : 11 , Array ( " Dec " . utf8) : 12
305
+ ]
306
+
307
+ let timezoneOffsetMap : [ Array < UInt8 > : Int ] = [
308
+ Array ( " UTC " . utf8) : 0 , Array ( " GMT " . utf8) : 0 , Array ( " EDT " . utf8) : - 4 * 60 , Array ( " CDT " . utf8) : - 5 * 60 , Array ( " MDT " . utf8) : - 6 * 60 , Array ( " PDT " . utf8) : - 7 * 60
309
+ ]
0 commit comments