Skip to content

Use only FoundationEssentials when possible #81

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Dec 19, 2024
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