Skip to content
78 changes: 50 additions & 28 deletions Sources/AWSLambdaEvents/Utils/DateWrappers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
//
//===----------------------------------------------------------------------===//

#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif

@propertyWrapper
public struct ISO8601Coding: Decodable, Sendable {
Expand All @@ -25,23 +29,35 @@ public struct ISO8601Coding: Decodable, Sendable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
guard let date = Self.dateFormatter.date(from: dateString) else {

guard let date = Self.parseISO8601(dateString: dateString) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription:
"Expected date to be in ISO8601 date format, but `\(dateString)` is not in the correct format"
)
}

self.wrappedValue = date
}

private static func parseISO8601(dateString: String) -> Date? {
#if canImport(FoundationEssentials)
return try? Date(dateString, strategy: .iso8601)
#else
return Self.dateFormatter.date(from: dateString)
#endif
}

#if !canImport(FoundationEssentials)
private static var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
return formatter
}
#endif
}

@propertyWrapper
Expand All @@ -55,23 +71,39 @@ public struct ISO8601WithFractionalSecondsCoding: Decodable, Sendable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
guard let date = Self.dateFormatter.date(from: dateString) else {

guard let date = Self.parseISO8601WithFractionalSeconds(dateString: dateString) else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription:
"Expected date to be in ISO8601 date format with fractional seconds, but `\(dateString)` is not in the correct format"
)
}

self.wrappedValue = date
}

private static func parseISO8601WithFractionalSeconds(dateString: String) -> Date? {
#if canImport(FoundationEssentials)
return try? Date(dateString, strategy: Self.iso8601WithFractionalSeconds)
#else
return Self.dateFormatter.date(from: dateString)
#endif
}

#if canImport(FoundationEssentials)
private static var iso8601WithFractionalSeconds: Date.ISO8601FormatStyle {
Date.ISO8601FormatStyle(includingFractionalSeconds: true)
}
#else
private static var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
return formatter
}
#endif
}

@propertyWrapper
Expand All @@ -84,34 +116,24 @@ public struct RFC5322DateTimeCoding: Decodable, Sendable {

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
var string = try container.decode(String.self)
// RFC5322 dates sometimes have the alphabetic version of the timezone in brackets after the numeric version. The date formatter
// fails to parse this so we need to remove this before parsing.
if let bracket = string.firstIndex(of: "(") {
string = String(string[string.startIndex..<bracket].trimmingCharacters(in: .whitespaces))
}
for formatter in Self.dateFormatters {
if let date = formatter.date(from: string) {
self.wrappedValue = date
return
}
let string = try container.decode(String.self)

do {
#if canImport(FoundationEssentials)
self.wrappedValue = try Date(string, strategy: Self.rfc5322DateParseStrategy)
#else
self.wrappedValue = try Self.rfc5322DateParseStrategy.parse(string)
#endif
} catch {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription:
"Expected date to be in RFC5322 date-time format, but `\(string)` is not in the correct format"
)
}
throw DecodingError.dataCorruptedError(
in: container,
debugDescription:
"Expected date to be in RFC5322 date-time format, but `\(string)` is not in the correct format"
)
}

private static var dateFormatters: [DateFormatter] {
// rfc5322 dates received in SES mails sometimes do not include the day, so need two dateformatters
// one with a day and one without
let formatterWithDay = DateFormatter()
formatterWithDay.dateFormat = "EEE, d MMM yyy HH:mm:ss z"
formatterWithDay.locale = Locale(identifier: "en_US_POSIX")
let formatterWithoutDay = DateFormatter()
formatterWithoutDay.dateFormat = "d MMM yyy HH:mm:ss z"
formatterWithoutDay.locale = Locale(identifier: "en_US_POSIX")
return [formatterWithDay, formatterWithoutDay]
private static var rfc5322DateParseStrategy: RFC5322DateParseStrategy {
RFC5322DateParseStrategy(calendar: Calendar(identifier: .gregorian))
}
}
Loading
Loading