Skip to content

Commit 7bcecc5

Browse files
t089sebsto
andauthored
Use only FoundationEssentials when possible (#81)
Linking against `Foundation` on linux adds a hefty binary size penalty. Mostly due to the ICU data from `FoundationInternationalization`. To address this we conditionally import `FoundationEssentials` instead of `Foundation` if possible and replace `Foundation`-only API with a different implementation. ### Motivation: Smaller binary sizes are better. ### Modifications: - Replace `import Foundation` with `import FoundationEssentials`. - Update date parsing logic to use newer API if available. - Implement a custom parser for RFC5322 dates ### Result: Ability to build the package without linking `Foundation`. --------- Co-authored-by: Sébastien Stormacq <[email protected]>
1 parent d2761dc commit 7bcecc5

7 files changed

+522
-33
lines changed

Sources/AWSLambdaEvents/Utils/DateWrappers.swift

+50-28
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
#if canImport(FoundationEssentials)
16+
import FoundationEssentials
17+
#else
1518
import Foundation
19+
#endif
1620

1721
@propertyWrapper
1822
public 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

Comments
 (0)