Skip to content

Commit 3b00660

Browse files
committed
Update to swift-rfc-3339 0.3.0 with rawValue API
- Update swift-rfc-3339 dependency to 0.3.0 - Update tests to use new Int comparison operators for Time.Month/Day
1 parent 131aa49 commit 3b00660

File tree

11 files changed

+98
-128
lines changed

11 files changed

+98
-128
lines changed

Package.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ extension String {
99
extension Target.Dependency {
1010
static var rfc4287: Self { .target(name: .rfc4287) }
1111
static var rfc2822: Self { .product(name: "RFC 2822", package: "swift-rfc-2822") }
12+
static var rfc3339: Self { .product(name: "RFC 3339", package: "swift-rfc-3339") }
1213
static var rfc3987: Self { .product(name: "RFC 3987", package: "swift-rfc-3987") }
14+
static var rfc4648: Self { .product(name: "RFC 4648", package: "swift-rfc-4648") }
1315
}
1416

1517
let package = Package(
@@ -27,13 +29,15 @@ let package = Package(
2729
),
2830
],
2931
dependencies: [
30-
.package(url: "https://github.com/swift-standards/swift-rfc-2822.git", from: "0.1.0"),
31-
.package(url: "https://github.com/swift-standards/swift-rfc-3987.git", from: "0.1.0")
32+
.package(url: "https://github.com/swift-standards/swift-rfc-2822.git", from: "0.4.0"),
33+
.package(url: "https://github.com/swift-standards/swift-rfc-3339.git", from: "0.3.0"),
34+
.package(url: "https://github.com/swift-standards/swift-rfc-3987.git", from: "0.1.0"),
35+
.package(url: "https://github.com/swift-standards/swift-rfc-4648.git", from: "0.1.0")
3236
],
3337
targets: [
3438
.target(
3539
name: .rfc4287,
36-
dependencies: [.rfc2822, .rfc3987],
40+
dependencies: [.rfc2822, .rfc3339, .rfc3987, .rfc4648],
3741
swiftSettings: [
3842
.swiftLanguageMode(.v6),
3943
.enableExperimentalFeature("StrictConcurrency")

Sources/RFC 4287/Content.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,23 +83,24 @@ extension RFC_4287 {
8383
self.lang = lang
8484
}
8585

86-
/// Creates inline binary content from data
86+
/// Creates inline binary content from raw bytes
8787
///
8888
/// - Parameters:
89-
/// - data: The binary content data (will be base64-encoded)
89+
/// - rawBytes: The raw binary content bytes (will be base64-encoded per RFC 4287)
9090
/// - mediaType: The MIME type of the content
9191
/// - base: Base IRI for resolving relative references
9292
/// - lang: Language of the content
9393
///
94-
/// This initializer automatically base64-encodes the data as required by RFC 4287
95-
/// for binary media types.
94+
/// This initializer automatically base64-encodes the bytes as required by RFC 4287
95+
/// for binary media types. If you already have a base64-encoded string, use
96+
/// `init(value:type:base:lang:)` instead with `type: .media(mediaType)`.
9697
public init(
97-
data: Data,
98+
rawBytes: [UInt8],
9899
mediaType: String,
99100
base: (any RFC_3987.IRI.Representable)? = nil,
100101
lang: String? = nil
101102
) {
102-
self.value = data.base64EncodedString()
103+
self.value = rawBytes.base64()
103104
self.type = .media(mediaType)
104105
self.src = nil
105106
self.base = base?.iri

Sources/RFC 4287/Date+Atom.swift

Lines changed: 0 additions & 35 deletions
This file was deleted.

Sources/RFC 4287/Entry.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ extension RFC_4287 {
2727
public let links: [Link]
2828

2929
/// Timestamp of last significant modification (required)
30-
public let updated: Date
30+
public let updated: RFC_3339.DateTime
3131

3232
/// Timestamp of initial creation or publication (optional)
33-
public let published: Date?
33+
public let published: RFC_3339.DateTime?
3434

3535
/// Copyright and licensing information (optional)
3636
public let rights: Rights?
@@ -76,13 +76,13 @@ extension RFC_4287 {
7676
public init?(
7777
id: RFC_3987.IRI,
7878
title: Title,
79-
updated: Date,
79+
updated: RFC_3339.DateTime,
8080
authors: [Author] = [],
8181
content: Content? = nil,
8282
links: [Link] = [],
8383
categories: [Category] = [],
8484
contributors: [Contributor] = [],
85-
published: Date? = nil,
85+
published: RFC_3339.DateTime? = nil,
8686
rights: Rights? = nil,
8787
source: Source? = nil,
8888
summary: Summary? = nil,
@@ -147,13 +147,13 @@ extension RFC_4287 {
147147
public init?(
148148
id: any RFC_3987.IRI.Representable,
149149
title: Title,
150-
updated: Date,
150+
updated: RFC_3339.DateTime,
151151
authors: [Author] = [],
152152
content: Content? = nil,
153153
links: [Link] = [],
154154
categories: [Category] = [],
155155
contributors: [Contributor] = [],
156-
published: Date? = nil,
156+
published: RFC_3339.DateTime? = nil,
157157
rights: Rights? = nil,
158158
source: Source? = nil,
159159
summary: Summary? = nil,
@@ -182,13 +182,13 @@ extension RFC_4287 {
182182
internal static func makeUnchecked(
183183
id: String,
184184
title: Title,
185-
updated: Date,
185+
updated: RFC_3339.DateTime,
186186
authors: [Author],
187187
content: Content?,
188188
links: [Link],
189189
categories: [Category],
190190
contributors: [Contributor],
191-
published: Date?,
191+
published: RFC_3339.DateTime?,
192192
rights: Rights?,
193193
source: Source?,
194194
summary: Summary?,
@@ -216,13 +216,13 @@ extension RFC_4287 {
216216
private init(
217217
uncheckedId id: RFC_3987.IRI,
218218
uncheckedTitle title: Title,
219-
uncheckedUpdated updated: Date,
219+
uncheckedUpdated updated: RFC_3339.DateTime,
220220
uncheckedAuthors authors: [Author],
221221
uncheckedContent content: Content?,
222222
uncheckedLinks links: [Link],
223223
uncheckedCategories categories: [Category],
224224
uncheckedContributors contributors: [Contributor],
225-
uncheckedPublished published: Date?,
225+
uncheckedPublished published: RFC_3339.DateTime?,
226226
uncheckedRights rights: Rights?,
227227
uncheckedSource source: Source?,
228228
uncheckedSummary summary: Summary?,

Sources/RFC 4287/Feed.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ extension RFC_4287 {
4646
public let title: Title
4747

4848
/// Timestamp of last significant modification (required)
49-
public let updated: Date
49+
public let updated: RFC_3339.DateTime
5050

5151
/// Base IRI for resolving relative references (xml:base)
5252
///
@@ -84,7 +84,7 @@ extension RFC_4287 {
8484
public init?(
8585
id: RFC_3987.IRI,
8686
title: Title,
87-
updated: Date,
87+
updated: RFC_3339.DateTime,
8888
authors: [Author] = [],
8989
entries: [Entry] = [],
9090
links: [Link] = [],
@@ -148,7 +148,7 @@ extension RFC_4287 {
148148
public init?(
149149
id: any RFC_3987.IRI.Representable,
150150
title: Title,
151-
updated: Date,
151+
updated: RFC_3339.DateTime,
152152
authors: [Author] = [],
153153
entries: [Entry] = [],
154154
links: [Link] = [],
@@ -185,7 +185,7 @@ extension RFC_4287 {
185185
internal static func makeUnchecked(
186186
id: String,
187187
title: Title,
188-
updated: Date,
188+
updated: RFC_3339.DateTime,
189189
authors: [Author],
190190
entries: [Entry],
191191
links: [Link],
@@ -221,7 +221,7 @@ extension RFC_4287 {
221221
private init(
222222
uncheckedId id: RFC_3987.IRI,
223223
uncheckedTitle title: Title,
224-
uncheckedUpdated updated: Date,
224+
uncheckedUpdated updated: RFC_3339.DateTime,
225225
uncheckedAuthors authors: [Author],
226226
uncheckedEntries entries: [Entry],
227227
uncheckedLinks links: [Link],

Sources/RFC 4287/Person.swift

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,16 @@ extension RFC_4287 {
8080
/// - emailString: Email address as string (will be parsed as local@domain)
8181
/// - base: Base IRI string for resolving relative references
8282
/// - lang: Language of the person construct
83-
/// - Throws: RFC_2822.ValidationError if email is invalid
83+
/// - Throws: RFC_2822.AddrSpec.Error if email is invalid
8484
public init(
8585
name: String,
8686
uri: String? = nil,
8787
emailString: String,
8888
base: String? = nil,
8989
lang: String? = nil
9090
) throws {
91-
// Parse email into local-part and domain
92-
let components = emailString.split(separator: "@", maxSplits: 1)
93-
guard components.count == 2 else {
94-
throw RFC_2822.ValidationError.invalidFieldValue("email", emailString)
95-
}
96-
let addrSpec = try RFC_2822.AddrSpec(
97-
localPart: String(components[0]),
98-
domain: String(components[1])
99-
)
91+
// Parse email using RFC 2822 canonical byte parsing
92+
let addrSpec = try RFC_2822.AddrSpec(ascii: emailString.utf8)
10093
let iri: RFC_3987.IRI? = uri.map { RFC_3987.IRI(unchecked: $0) }
10194
let baseIRI: RFC_3987.IRI? = base.map { RFC_3987.IRI(unchecked: $0) }
10295
self.init(name: name, uri: iri, email: addrSpec, base: baseIRI, lang: lang)
@@ -123,18 +116,15 @@ extension RFC_4287.Person: Codable {
123116

124117
// Decode email as string and convert to AddrSpec
125118
if let emailString = try container.decodeIfPresent(String.self, forKey: .email) {
126-
let components = emailString.split(separator: "@", maxSplits: 1)
127-
guard components.count == 2 else {
119+
do {
120+
email = try RFC_2822.AddrSpec(ascii: emailString.utf8)
121+
} catch {
128122
throw DecodingError.dataCorruptedError(
129123
forKey: .email,
130124
in: container,
131125
debugDescription: "Invalid email format: \(emailString)"
132126
)
133127
}
134-
email = try RFC_2822.AddrSpec(
135-
localPart: String(components[0]),
136-
domain: String(components[1])
137-
)
138128
} else {
139129
email = nil
140130
}

Sources/RFC 4287/RFC_4287.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
@_exported import RFC_3339
12
@_exported import RFC_3987
3+
@_exported import RFC_4648
24

35
/// RFC 4287: The Atom Syndication Format
46
///

Sources/RFC 4287/Source.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ extension RFC_4287 {
3939
public let title: Title?
4040

4141
/// Last updated timestamp
42-
public let updated: Date?
42+
public let updated: RFC_3339.DateTime?
4343

4444
/// Base IRI for resolving relative references (xml:base)
4545
///
@@ -80,7 +80,7 @@ extension RFC_4287 {
8080
rights: Rights? = nil,
8181
subtitle: Subtitle? = nil,
8282
title: Title? = nil,
83-
updated: Date? = nil,
83+
updated: RFC_3339.DateTime? = nil,
8484
base: RFC_3987.IRI? = nil,
8585
lang: String? = nil
8686
) {
@@ -131,7 +131,7 @@ extension RFC_4287 {
131131
rights: Rights? = nil,
132132
subtitle: Subtitle? = nil,
133133
title: Title? = nil,
134-
updated: Date? = nil,
134+
updated: RFC_3339.DateTime? = nil,
135135
base: (any RFC_3987.IRI.Representable)? = nil,
136136
lang: String? = nil
137137
) {

Tests/RFC 4287 Tests/DateFormattingTests.swift

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,51 +3,51 @@ import Testing
33
@testable import RFC_4287
44

55
@Suite
6-
struct `Date Formatting Tests` {
7-
@Test func dateToAtomString() async throws {
8-
let date = Date(timeIntervalSince1970: 1_609_459_200) // 2021-01-01 00:00:00 UTC
9-
10-
let formatted = date.atomFormatted()
11-
12-
// Should be ISO 8601 format
13-
#expect(formatted.contains("2021-01-01"))
14-
#expect(formatted.contains("T"))
15-
#expect(formatted.contains("Z"))
6+
struct `RFC 3339 DateTime Tests` {
7+
@Test func createDateTimeFromComponents() async throws {
8+
let time = try Time(year: 2021, month: 1, day: 1, hour: 0, minute: 0, second: 0)
9+
let dateTime = RFC_3339.DateTime(time: time, offset: .utc)
10+
11+
#expect(dateTime.time.year == 2021)
12+
#expect(dateTime.time.month == 1)
13+
#expect(dateTime.time.day == 1)
14+
#expect(dateTime.offset == .utc)
1615
}
1716

18-
@Test func atomStringToDate() async throws {
17+
@Test func parseRFC3339String() async throws {
1918
let dateString = "2021-01-01T00:00:00Z"
19+
let dateTime = try RFC_3339.Parser.parse(dateString)
2020

21-
let date = try Date(atomString: dateString)
22-
23-
#expect(date.timeIntervalSince1970 == 1_609_459_200)
21+
#expect(dateTime.time.year == 2021)
22+
#expect(dateTime.time.month == 1)
23+
#expect(dateTime.time.day == 1)
24+
#expect(dateTime.offset == .utc)
2425
}
2526

26-
@Test func atomStringWithFractionalSeconds() async throws {
27-
let dateString = "2021-01-01T00:00:00.123Z"
27+
@Test func formatRFC3339String() async throws {
28+
let time = try Time(year: 2021, month: 1, day: 1, hour: 12, minute: 30, second: 45)
29+
let dateTime = RFC_3339.DateTime(time: time, offset: .utc)
2830

29-
let date = try Date(atomString: dateString)
31+
let formatted = RFC_3339.Formatter.format(dateTime)
3032

31-
#expect(date.timeIntervalSince1970 > 0)
33+
// Should be ISO 8601 format
34+
#expect(formatted.contains("2021-01-01"))
35+
#expect(formatted.contains("T"))
36+
#expect(formatted.contains("12:30:45"))
3237
}
3338

34-
@Test func roundTripDateFormatting() async throws {
35-
let originalDate = Date(timeIntervalSince1970: 1_609_459_200)
36-
37-
let formatted = originalDate.atomFormatted()
38-
let parsedDate = try Date(atomString: formatted)
39+
@Test func roundTripDateTimeFormatting() async throws {
40+
let time = try Time(year: 2021, month: 6, day: 15, hour: 14, minute: 30, second: 0)
41+
let original = RFC_3339.DateTime(time: time, offset: .utc)
3942

40-
// Should be equal within a second (accounting for fractional seconds)
41-
let difference = abs(originalDate.timeIntervalSince1970 - parsedDate.timeIntervalSince1970)
42-
#expect(difference < 1.0)
43-
}
44-
45-
@Test func invalidDateStringThrows() async throws {
46-
let invalidString = "not a date"
43+
let formatted = RFC_3339.Formatter.format(original)
44+
let parsed = try RFC_3339.Parser.parse(formatted)
4745

48-
#expect(throws: RFC_4287.ValidationError.self) {
49-
try Date(atomString: invalidString)
50-
}
46+
#expect(parsed.time.year == original.time.year)
47+
#expect(parsed.time.month == original.time.month)
48+
#expect(parsed.time.day == original.time.day)
49+
#expect(parsed.time.hour == original.time.hour)
50+
#expect(parsed.time.minute == original.time.minute)
5151
}
5252

5353
@Test func variousRFC3339Formats() async throws {
@@ -59,8 +59,8 @@ struct `Date Formatting Tests` {
5959
]
6060

6161
for format in formats {
62-
let date = try Date(atomString: format)
63-
#expect(date.timeIntervalSince1970 > 0)
62+
let dateTime = try RFC_3339.Parser.parse(format)
63+
#expect(dateTime.time.year > 0)
6464
}
6565
}
6666
}

0 commit comments

Comments
 (0)