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,23 +29,35 @@ 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
+ guard let date = Self . parseISO8601 ( dateString: dateString) else {
29
34
throw DecodingError . dataCorruptedError (
30
35
in: container,
31
36
debugDescription:
32
37
" Expected date to be in ISO8601 date format, but ` \( dateString) ` is not in the correct format "
33
38
)
34
39
}
40
+
35
41
self . wrappedValue = date
36
42
}
37
43
44
+ private static func parseISO8601( dateString: String ) -> Date ? {
45
+ #if canImport(FoundationEssentials)
46
+ return try ? Date ( dateString, strategy: . iso8601)
47
+ #else
48
+ return Self . dateFormatter. date ( from: dateString)
49
+ #endif
50
+ }
51
+
52
+ #if !canImport(FoundationEssentials)
38
53
private static var dateFormatter : DateFormatter {
39
54
let formatter = DateFormatter ( )
40
55
formatter. locale = Locale ( identifier: " en_US_POSIX " )
41
56
formatter. timeZone = TimeZone ( secondsFromGMT: 0 )
42
57
formatter. dateFormat = " yyyy-MM-dd'T'HH:mm:ssZZZZZ "
43
58
return formatter
44
59
}
60
+ #endif
45
61
}
46
62
47
63
@propertyWrapper
@@ -55,23 +71,39 @@ public struct ISO8601WithFractionalSecondsCoding: Decodable, Sendable {
55
71
public init ( from decoder: Decoder ) throws {
56
72
let container = try decoder. singleValueContainer ( )
57
73
let dateString = try container. decode ( String . self)
58
- guard let date = Self . dateFormatter. date ( from: dateString) else {
74
+
75
+ guard let date = Self . parseISO8601WithFractionalSeconds ( dateString: dateString) else {
59
76
throw DecodingError . dataCorruptedError (
60
77
in: container,
61
78
debugDescription:
62
79
" Expected date to be in ISO8601 date format with fractional seconds, but ` \( dateString) ` is not in the correct format "
63
80
)
64
81
}
82
+
65
83
self . wrappedValue = date
66
84
}
67
85
86
+ private static func parseISO8601WithFractionalSeconds( dateString: String ) -> Date ? {
87
+ #if canImport(FoundationEssentials)
88
+ return try ? Date ( dateString, strategy: Self . iso8601WithFractionalSeconds)
89
+ #else
90
+ return Self . dateFormatter. date ( from: dateString)
91
+ #endif
92
+ }
93
+
94
+ #if canImport(FoundationEssentials)
95
+ private static var iso8601WithFractionalSeconds : Date . ISO8601FormatStyle {
96
+ Date . ISO8601FormatStyle ( includingFractionalSeconds: true )
97
+ }
98
+ #else
68
99
private static var dateFormatter : DateFormatter {
69
100
let formatter = DateFormatter ( )
70
101
formatter. locale = Locale ( identifier: " en_US_POSIX " )
71
102
formatter. timeZone = TimeZone ( secondsFromGMT: 0 )
72
103
formatter. dateFormat = " yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ "
73
104
return formatter
74
105
}
106
+ #endif
75
107
}
76
108
77
109
@propertyWrapper
@@ -84,34 +116,24 @@ public struct RFC5322DateTimeCoding: Decodable, Sendable {
84
116
85
117
public init ( from decoder: Decoder ) throws {
86
118
let container = try decoder. singleValueContainer ( )
87
- var string = try container. decode ( String . self)
88
- // RFC5322 dates sometimes have the alphabetic version of the timezone in brackets after the numeric version. The date formatter
89
- // fails to parse this so we need to remove this before parsing.
90
- if let bracket = string. firstIndex ( of: " ( " ) {
91
- string = String ( string [ string. startIndex..< bracket] . trimmingCharacters ( in: . whitespaces) )
92
- }
93
- for formatter in Self . dateFormatters {
94
- if let date = formatter. date ( from: string) {
95
- self . wrappedValue = date
96
- return
97
- }
119
+ let string = try container. decode ( String . self)
120
+
121
+ do {
122
+ #if canImport(FoundationEssentials)
123
+ self . wrappedValue = try Date ( string, strategy: Self . rfc5322DateParseStrategy)
124
+ #else
125
+ self . wrappedValue = try Self . rfc5322DateParseStrategy. parse ( string)
126
+ #endif
127
+ } catch {
128
+ throw DecodingError . dataCorruptedError (
129
+ in: container,
130
+ debugDescription:
131
+ " Expected date to be in RFC5322 date-time format, but ` \( string) ` is not in the correct format "
132
+ )
98
133
}
99
- throw DecodingError . dataCorruptedError (
100
- in: container,
101
- debugDescription:
102
- " Expected date to be in RFC5322 date-time format, but ` \( string) ` is not in the correct format "
103
- )
104
134
}
105
135
106
- private static var dateFormatters : [ DateFormatter ] {
107
- // rfc5322 dates received in SES mails sometimes do not include the day, so need two dateformatters
108
- // one with a day and one without
109
- let formatterWithDay = DateFormatter ( )
110
- formatterWithDay. dateFormat = " EEE, d MMM yyy HH:mm:ss z "
111
- formatterWithDay. locale = Locale ( identifier: " en_US_POSIX " )
112
- let formatterWithoutDay = DateFormatter ( )
113
- formatterWithoutDay. dateFormat = " d MMM yyy HH:mm:ss z "
114
- formatterWithoutDay. locale = Locale ( identifier: " en_US_POSIX " )
115
- return [ formatterWithDay, formatterWithoutDay]
136
+ private static var rfc5322DateParseStrategy : RFC5322DateParseStrategy {
137
+ RFC5322DateParseStrategy ( calendar: Calendar ( identifier: . gregorian) )
116
138
}
117
139
}
0 commit comments