Skip to content

Commit 9e8f5af

Browse files
authored
Use default date/UUID representations (#67)
* Use default date/UUID representations * wip
1 parent 2e853d6 commit 9e8f5af

File tree

11 files changed

+105
-23
lines changed

11 files changed

+105
-23
lines changed

Examples/CaseStudies/SwiftDataTemplateDemo.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ struct SwiftDataTemplateView: SwiftUICaseStudy {
5858
@Table
5959
private struct Item: Identifiable {
6060
let id: Int
61-
@Column(as: Date.ISO8601Representation.self)
6261
var timestamp: Date
6362
}
6463

Examples/Reminders/Schema.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ struct RemindersList: Hashable, Identifiable {
1616
@Table
1717
struct Reminder: Equatable, Identifiable {
1818
var id: Int
19-
@Column(as: Date.ISO8601Representation?.self)
2019
var dueDate: Date?
2120
var isCompleted = false
2221
var isFlagged = false

Examples/SyncUps/Schema.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ struct Attendee: Hashable, Identifiable {
2020
@Table
2121
struct Meeting: Hashable, Identifiable {
2222
let id: Int
23-
@Column(as: Date.ISO8601Representation.self)
2423
var date: Date
2524
var syncUpID: SyncUp.ID
2625
var transcript: String

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ let package = Package(
3333
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.9.0"),
3434
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.5.0"),
3535
.package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.3.0"),
36-
.package(url: "https://github.com/pointfreeco/swift-structured-queries", from: "0.2.0"),
36+
.package(url: "https://github.com/pointfreeco/swift-structured-queries", from: "0.4.0"),
3737
],
3838
targets: [
3939
.target(

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ simple as adding it to your `Package.swift`:
324324

325325
``` swift
326326
dependencies: [
327-
.package(url: "https://github.com/pointfreeco/sharing-grdb", from: "0.2.0")
327+
.package(url: "https://github.com/pointfreeco/sharing-grdb", from: "0.4.0")
328328
]
329329
```
330330

SharingGRDB.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/SharingGRDBCore/Documentation.docc/Articles/Fetching.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,11 @@ your table:
2828
struct Reminder {
2929
let id: Int
3030
var title = ""
31-
@Column(as: Date.ISO8601Representation?.self)
3231
var dueAt: Date?
3332
var isCompleted = false
3433
}
3534
```
3635

37-
> Note: The `@Column` macro determines how to store the date in SQLite, which does not have a native
38-
> date data type. The `Date.ISO8601Representation` strategy stores dates as text formatted with the
39-
> ISO-8601 standard. See [Defining your schema] for more info.
40-
41-
[Defining your schema]: https://swiftpackageindex.com/pointfreeco/swift-structured-queries/main/documentation/structuredqueriescore/definingyourschema
42-
4336
With that done you can already fetch all records from the `Reminder` table in their default order by
4437
simply doing:
4538

@@ -106,7 +99,6 @@ exactly one list:
10699
struct Reminder {
107100
let id: Int
108101
var title = ""
109-
@Column(as: Date.ISO8601Representation?.self)
110102
var dueAt: Date?
111103
var isCompleted = false
112104
var remindersListID: RemindersList.ID
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import Foundation
2+
3+
extension Date {
4+
@usableFromInline
5+
var iso8601String: String {
6+
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
7+
return formatted(.iso8601.currentTimestamp(includingFractionalSeconds: true))
8+
} else {
9+
return DateFormatter.iso8601(includingFractionalSeconds: true).string(from: self)
10+
}
11+
}
12+
13+
@usableFromInline
14+
init(iso8601String: String) throws {
15+
if #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) {
16+
do {
17+
try self.init(
18+
iso8601String.queryOutput,
19+
strategy: .iso8601.currentTimestamp(includingFractionalSeconds: true)
20+
)
21+
} catch {
22+
try self.init(
23+
iso8601String.queryOutput,
24+
strategy: .iso8601.currentTimestamp(includingFractionalSeconds: false)
25+
)
26+
}
27+
} else {
28+
guard
29+
let date = DateFormatter.iso8601(includingFractionalSeconds: true).date(from: iso8601String)
30+
?? DateFormatter.iso8601(includingFractionalSeconds: false).date(from: iso8601String)
31+
else {
32+
struct InvalidDate: Error { let string: String }
33+
throw InvalidDate(string: iso8601String)
34+
}
35+
self = date
36+
}
37+
}
38+
}
39+
40+
extension DateFormatter {
41+
fileprivate static func iso8601(includingFractionalSeconds: Bool) -> DateFormatter {
42+
includingFractionalSeconds ? iso8601Fractional : iso8601Whole
43+
}
44+
45+
fileprivate static let iso8601Fractional: DateFormatter = {
46+
let formatter = DateFormatter()
47+
formatter.calendar = Calendar(identifier: .iso8601)
48+
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
49+
formatter.locale = Locale(identifier: "en_US_POSIX")
50+
formatter.timeZone = TimeZone(secondsFromGMT: 0)
51+
return formatter
52+
}()
53+
54+
fileprivate static let iso8601Whole: DateFormatter = {
55+
let formatter = DateFormatter()
56+
formatter.calendar = Calendar(identifier: .iso8601)
57+
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
58+
formatter.locale = Locale(identifier: "en_US_POSIX")
59+
formatter.timeZone = TimeZone(secondsFromGMT: 0)
60+
return formatter
61+
}()
62+
}
63+
64+
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
65+
extension Date.ISO8601FormatStyle {
66+
fileprivate func currentTimestamp(includingFractionalSeconds: Bool) -> Self {
67+
year().month().day()
68+
.dateTimeSeparator(.space)
69+
.time(includingFractionalSeconds: includingFractionalSeconds)
70+
}
71+
}

Sources/StructuredQueriesGRDBCore/QueryCursor.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ extension QueryBinding {
109109
switch self {
110110
case let .blob(blob):
111111
return Data(blob).databaseValue
112+
case let .date(date):
113+
return date.iso8601String.databaseValue
112114
case let .double(double):
113115
return double.databaseValue
114116
case let .int(int):
@@ -117,6 +119,8 @@ extension QueryBinding {
117119
return .null
118120
case let .text(text):
119121
return text.databaseValue
122+
case let .uuid(uuid):
123+
return uuid.uuidString.lowercased().databaseValue
120124
case let .invalid(error):
121125
throw error
122126
}

Sources/StructuredQueriesGRDBCore/SQLiteQueryDecoder.swift

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Foundation
12
import GRDBSQLite
23
import StructuredQueriesCore
34

@@ -31,13 +32,28 @@ struct SQLiteQueryDecoder: QueryDecoder {
3132
)
3233
}
3334

35+
@inlinable
36+
mutating func decode(_ columnType: Bool.Type) throws -> Bool? {
37+
try decode(Int64.self).map { $0 != 0 }
38+
}
39+
40+
@inlinable
41+
mutating func decode(_ columnType: Date.Type) throws -> Date? {
42+
try decode(String.self).map { try Date(iso8601String: $0) }
43+
}
44+
3445
@inlinable
3546
mutating func decode(_ columnType: Double.Type) throws -> Double? {
3647
defer { currentIndex += 1 }
3748
guard sqlite3_column_type(statement, currentIndex) != SQLITE_NULL else { return nil }
3849
return sqlite3_column_double(statement, currentIndex)
3950
}
4051

52+
@inlinable
53+
mutating func decode(_ columnType: Int.Type) throws -> Int? {
54+
try decode(Int64.self).map(Int.init)
55+
}
56+
4157
@inlinable
4258
mutating func decode(_ columnType: Int64.Type) throws -> Int64? {
4359
defer { currentIndex += 1 }
@@ -53,12 +69,15 @@ struct SQLiteQueryDecoder: QueryDecoder {
5369
}
5470

5571
@inlinable
56-
mutating func decode(_ columnType: Bool.Type) throws -> Bool? {
57-
try decode(Int64.self).map { $0 != 0 }
72+
mutating func decode(_ columnType: UUID.Type) throws -> UUID? {
73+
guard let uuidString = try decode(String.self) else { return nil }
74+
guard let uuid = UUID(uuidString: uuidString) else { throw InvalidUUID() }
75+
return uuid
5876
}
77+
}
5978

60-
@inlinable
61-
mutating func decode(_ columnType: Int.Type) throws -> Int? {
62-
try decode(Int64.self).map(Int.init)
63-
}
79+
@usableFromInline
80+
struct InvalidUUID: Error {
81+
@usableFromInline
82+
init() {}
6483
}

0 commit comments

Comments
 (0)