Skip to content

Commit 442be5f

Browse files
committed
remove requirment on macOS 10.13
motivation: simplify downstream libraries changes: use custom ISO8601 decoders on macOS < 10.13
1 parent 2067f4a commit 442be5f

File tree

3 files changed

+112
-11
lines changed

3 files changed

+112
-11
lines changed

Package.swift

-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ import PackageDescription
44

55
let package = Package(
66
name: "swift-aws-lambda-runtime",
7-
platforms: [
8-
.macOS(.v10_13),
9-
],
107
products: [
118
// this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods
129
.library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]),

Sources/AWSLambdaEvents/Utils/DateWrappers.swift

+80-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
#if os(Linux)
16+
import Glibc
17+
#else
18+
import Darwin.C
19+
#endif
20+
1521
import struct Foundation.Date
1622
import class Foundation.DateFormatter
1723
import class Foundation.ISO8601DateFormatter
@@ -28,14 +34,46 @@ public struct ISO8601Coding: Decodable {
2834
public init(from decoder: Decoder) throws {
2935
let container = try decoder.singleValueContainer()
3036
let dateString = try container.decode(String.self)
31-
guard let date = Self.dateFormatter.date(from: dateString) else {
37+
guard let date = Self.decodeDate(from: dateString) else {
3238
throw DecodingError.dataCorruptedError(in: container, debugDescription:
33-
"Expected date to be in iso8601 date format, but `\(dateString)` does not forfill format")
39+
"Expected date to be in ISO8601 date format, but `\(dateString)` is not in the correct format")
3440
}
3541
self.wrappedValue = date
3642
}
3743

44+
private static func decodeDate(from string: String) -> Date? {
45+
#if os(Linux)
46+
return Self.dateFormatter.date(from: string)
47+
#elseif os(macOS)
48+
if #available(macOS 10.12, *) {
49+
return Self.dateFormatter.date(from: string)
50+
} else {
51+
return self.decodeISO8601Date(from: string).flatMap(Date.init(timeIntervalSince1970:))
52+
}
53+
#endif
54+
}
55+
56+
@available(macOS 10.12, *)
3857
private static let dateFormatter = ISO8601DateFormatter()
58+
59+
// strptime not avail on through Glibc
60+
#if os(macOS)
61+
// 1970-01-01T00:00:00Z
62+
internal static func decodeISO8601Date(from string: String) -> Double? {
63+
if string.last != "Z" {
64+
return nil
65+
}
66+
var parsedTime = tm()
67+
_ = string.withCString { cstr in
68+
strptime(cstr, "%Y-%m-%dT%H:%M:%S%z", &parsedTime)
69+
}
70+
let time = timegm(&parsedTime)
71+
if time == -1 {
72+
return nil
73+
}
74+
return Double(time)
75+
}
76+
#endif
3977
}
4078

4179
@propertyWrapper
@@ -49,14 +87,29 @@ public struct ISO8601WithFractionalSecondsCoding: Decodable {
4987
public init(from decoder: Decoder) throws {
5088
let container = try decoder.singleValueContainer()
5189
let dateString = try container.decode(String.self)
52-
guard let date = Self.dateFormatter.date(from: dateString) else {
90+
guard let date = Self.decodeDate(from: dateString) else {
5391
throw DecodingError.dataCorruptedError(in: container, debugDescription:
54-
"Expected date to be in iso8601 date format with fractional seconds, but `\(dateString)` does not forfill format")
92+
"Expected date to be in ISO8601 date format with fractional seconds, but `\(dateString)` is not in the correct format")
5593
}
5694
self.wrappedValue = date
5795
}
5896

97+
private static func decodeDate(from string: String) -> Date? {
98+
#if os(Linux)
99+
return Self.dateFormatter.date(from: string)
100+
#elseif os(macOS)
101+
if #available(macOS 10.13, *) {
102+
return self.dateFormatter.date(from: string)
103+
} else {
104+
return self.decodeISO8601Date(from: string).flatMap(Date.init(timeIntervalSince1970:))
105+
}
106+
#endif
107+
}
108+
109+
@available(macOS 10.13, *)
59110
private static let dateFormatter: ISO8601DateFormatter = Self.createDateFormatter()
111+
112+
@available(macOS 10.13, *)
60113
private static func createDateFormatter() -> ISO8601DateFormatter {
61114
let formatter = ISO8601DateFormatter()
62115
formatter.formatOptions = [
@@ -68,6 +121,28 @@ public struct ISO8601WithFractionalSecondsCoding: Decodable {
68121
]
69122
return formatter
70123
}
124+
125+
// strptime not avail on through Glibc
126+
#if os(macOS)
127+
// 1970-01-01T00:00:00.00Z
128+
internal static func decodeISO8601Date(from string: String) -> Double? {
129+
guard let msIndex = string.lastIndex(of: ".") else {
130+
return nil
131+
}
132+
guard let endIndex = string.lastIndex(of: "Z") else {
133+
return nil
134+
}
135+
if endIndex <= msIndex {
136+
return nil
137+
}
138+
let msString = string[msIndex ..< endIndex]
139+
guard let ms = Double(msString) else {
140+
return nil
141+
}
142+
143+
return ISO8601Coding.decodeISO8601Date(from: string)?.advanced(by: ms)
144+
}
145+
#endif
71146
}
72147

73148
@propertyWrapper
@@ -88,7 +163,7 @@ public struct RFC5322DateTimeCoding: Decodable {
88163
}
89164
guard let date = Self.dateFormatter.date(from: string) else {
90165
throw DecodingError.dataCorruptedError(in: container, debugDescription:
91-
"Expected date to be in RFC5322 date-time format with fractional seconds, but `\(string)` does not forfill format")
166+
"Expected date to be in RFC5322 date-time format with fractional seconds, but `\(string)` is not in the correct format")
92167
}
93168
self.wrappedValue = date
94169
}

Tests/AWSLambdaEventsTests/Utils/DateWrapperTests.swift

+32-3
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,22 @@ class DateWrapperTests: XCTestCase {
4343
}
4444

4545
XCTAssertEqual(context.codingPath.compactMap { $0.stringValue }, ["date"])
46-
XCTAssertEqual(context.debugDescription, "Expected date to be in iso8601 date format, but `\(date)` does not forfill format")
46+
XCTAssertEqual(context.debugDescription, "Expected date to be in ISO8601 date format, but `\(date)` is not in the correct format")
4747
XCTAssertNil(context.underlyingError)
4848
}
4949
}
5050

51+
#if os(macOS)
52+
@available(macOS 10.12, *)
53+
func testISO8601CustomDecoder() {
54+
let formatter = ISO8601DateFormatter()
55+
let string = formatter.string(from: Date())
56+
let date = formatter.date(from: string)
57+
let date2 = ISO8601Coding.decodeISO8601Date(from: string)
58+
XCTAssertEqual(date?.timeIntervalSince1970, date2)
59+
}
60+
#endif
61+
5162
func testISO8601WithFractionalSecondsCodingWrapperSuccess() {
5263
struct TestEvent: Decodable {
5364
@ISO8601WithFractionalSecondsCoding
@@ -75,11 +86,29 @@ class DateWrapperTests: XCTestCase {
7586
}
7687

7788
XCTAssertEqual(context.codingPath.compactMap { $0.stringValue }, ["date"])
78-
XCTAssertEqual(context.debugDescription, "Expected date to be in iso8601 date format with fractional seconds, but `\(date)` does not forfill format")
89+
XCTAssertEqual(context.debugDescription, "Expected date to be in ISO8601 date format with fractional seconds, but `\(date)` is not in the correct format")
7990
XCTAssertNil(context.underlyingError)
8091
}
8192
}
8293

94+
#if os(macOS)
95+
@available(macOS 10.13, *)
96+
func testISO8601WithFractionalSecondsCustomDecoder() {
97+
let formatter = ISO8601DateFormatter()
98+
formatter.formatOptions = [
99+
.withInternetDateTime,
100+
.withDashSeparatorInDate,
101+
.withColonSeparatorInTime,
102+
.withColonSeparatorInTimeZone,
103+
.withFractionalSeconds,
104+
]
105+
let string = formatter.string(from: Date())
106+
let date = formatter.date(from: string)
107+
let date2 = ISO8601WithFractionalSecondsCoding.decodeISO8601Date(from: string)
108+
XCTAssertEqual(date?.timeIntervalSince1970, date2)
109+
}
110+
#endif
111+
83112
func testRFC5322DateTimeCodingWrapperSuccess() {
84113
struct TestEvent: Decodable {
85114
@RFC5322DateTimeCoding
@@ -133,7 +162,7 @@ class DateWrapperTests: XCTestCase {
133162
}
134163

135164
XCTAssertEqual(context.codingPath.compactMap { $0.stringValue }, ["date"])
136-
XCTAssertEqual(context.debugDescription, "Expected date to be in RFC5322 date-time format with fractional seconds, but `\(date)` does not forfill format")
165+
XCTAssertEqual(context.debugDescription, "Expected date to be in RFC5322 date-time format with fractional seconds, but `\(date)` is not in the correct format")
137166
XCTAssertNil(context.underlyingError)
138167
}
139168
}

0 commit comments

Comments
 (0)