1212//
1313//===----------------------------------------------------------------------===//
1414
15+ #if canImport(FoundationEssentials)
16+ import FoundationEssentials
17+ #else
1518import Foundation
19+ #endif
1620
1721@propertyWrapper
1822public struct ISO8601Coding : Decodable , Sendable {
@@ -25,23 +29,35 @@ public struct ISO8601Coding: Decodable, Sendable {
2529 public init ( from decoder: Decoder ) throws {
2630 let container = try decoder. singleValueContainer ( )
2731 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 {
2934 throw DecodingError . dataCorruptedError (
3035 in: container,
3136 debugDescription:
3237 " Expected date to be in ISO8601 date format, but ` \( dateString) ` is not in the correct format "
3338 )
3439 }
40+
3541 self . wrappedValue = date
3642 }
3743
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)
3853 private static var dateFormatter : DateFormatter {
3954 let formatter = DateFormatter ( )
4055 formatter. locale = Locale ( identifier: " en_US_POSIX " )
4156 formatter. timeZone = TimeZone ( secondsFromGMT: 0 )
4257 formatter. dateFormat = " yyyy-MM-dd'T'HH:mm:ssZZZZZ "
4358 return formatter
4459 }
60+ #endif
4561}
4662
4763@propertyWrapper
@@ -55,23 +71,39 @@ public struct ISO8601WithFractionalSecondsCoding: Decodable, Sendable {
5571 public init ( from decoder: Decoder ) throws {
5672 let container = try decoder. singleValueContainer ( )
5773 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 {
5976 throw DecodingError . dataCorruptedError (
6077 in: container,
6178 debugDescription:
6279 " Expected date to be in ISO8601 date format with fractional seconds, but ` \( dateString) ` is not in the correct format "
6380 )
6481 }
82+
6583 self . wrappedValue = date
6684 }
6785
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
6899 private static var dateFormatter : DateFormatter {
69100 let formatter = DateFormatter ( )
70101 formatter. locale = Locale ( identifier: " en_US_POSIX " )
71102 formatter. timeZone = TimeZone ( secondsFromGMT: 0 )
72103 formatter. dateFormat = " yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ "
73104 return formatter
74105 }
106+ #endif
75107}
76108
77109@propertyWrapper
@@ -84,34 +116,24 @@ public struct RFC5322DateTimeCoding: Decodable, Sendable {
84116
85117 public init ( from decoder: Decoder ) throws {
86118 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+ )
98133 }
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- )
104134 }
105135
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) )
116138 }
117139}
0 commit comments