-
-
Notifications
You must be signed in to change notification settings - Fork 745
/
Copy pathDate.swift
168 lines (155 loc) · 6.34 KB
/
Date.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// Import C SQLite functions
#if GRDBCIPHER
import SQLCipher
#elseif SWIFT_PACKAGE
import GRDBSQLite
#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
import SQLite3
#endif
import Foundation
#if !os(Linux)
/// NSDate is stored in the database using the format
/// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone.
extension NSDate: DatabaseValueConvertible {
/// Returns a TEXT database value that contains the date encoded as
/// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone.
public var databaseValue: DatabaseValue {
(self as Date).databaseValue
}
/// Creates an `NSDate` with the specified database value.
///
/// If the database value contains a number, that number is interpreted as a
/// timeinterval since 00:00:00 UTC on 1 January 1970.
///
/// If the database value contains a string, that string is interpreted as a
/// [SQLite date](https://sqlite.org/lang_datefunc.html) in the UTC time
/// zone. Nil is returned if the date string does not contain at least the
/// year, month and day components. Other components (minutes, etc.)
/// are set to zero if missing.
///
/// Otherwise, returns nil.
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Self? {
guard let date = Date.fromDatabaseValue(dbValue) else {
return nil
}
return cast(date)
}
}
#endif
/// Date is stored in the database using the format
/// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone.
extension Date: DatabaseValueConvertible {
/// Returns a TEXT database value that contains the date encoded as
/// "yyyy-MM-dd HH:mm:ss.SSS", in the UTC time zone.
public var databaseValue: DatabaseValue {
storageDateFormatter.string(from: self).databaseValue
}
/// Creates an `Date` with the specified database value.
///
/// If the database value contains a number, that number is interpreted as a
/// timeinterval since 00:00:00 UTC on 1 January 1970.
///
/// If the database value contains a string, that string is interpreted as a
/// [SQLite date](https://sqlite.org/lang_datefunc.html) in the UTC time
/// zone. Nil is returned if the date string does not contain at least the
/// year, month and day components. Other components (minutes, etc.)
/// are set to zero if missing.
///
/// Otherwise, returns nil.
public static func fromDatabaseValue(_ dbValue: DatabaseValue) -> Date? {
if let databaseDateComponents = DatabaseDateComponents.fromDatabaseValue(dbValue) {
return Date(databaseDateComponents: databaseDateComponents)
}
if let timestamp = Double.fromDatabaseValue(dbValue) {
return Date(timeIntervalSince1970: timestamp)
}
return nil
}
@usableFromInline
init?(databaseDateComponents: DatabaseDateComponents) {
guard databaseDateComponents.format.hasYMDComponents else {
// Refuse to turn hours without any date information into Date:
return nil
}
guard let date = UTCCalendar.date(from: databaseDateComponents.dateComponents) else {
return nil
}
self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
}
/// Creates a date from a [Julian Day](https://en.wikipedia.org/wiki/Julian_day).
public init?(julianDay: Double) {
// Conversion uses the same algorithm as SQLite: https://www.sqlite.org/src/artifact/8ec787fed4929d8c
// TODO: check for overflows one day, and return nil when computation can't complete.
let JD = Int64(julianDay * 86400000)
let Z = Int(((JD + 43200000)/86400000))
var A = Int(((Double(Z) - 1867216.25)/36524.25))
A = Z + 1 + A - (A/4)
let B = A + 1524
let C = Int(((Double(B) - 122.1)/365.25))
let D = (36525*(C&32767))/100
let E = Int((Double(B-D)/30.6001))
let X1 = Int((30.6001*Double(E)))
let day = B - D - X1
let month = E<14 ? E-1 : E-13
let year = month>2 ? C - 4716 : C - 4715
var s = Int(((JD + 43200000) % 86400000))
var second = Double(s)/1000.0
s = Int(second)
second -= Double(s)
let hour = s/3600
s -= hour*3600
let minute = s/60
second += Double(s - minute*60)
var dateComponents = DateComponents()
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
dateComponents.hour = hour
dateComponents.minute = minute
dateComponents.second = Int(second)
dateComponents.nanosecond = Int((second - Double(Int(second))) * 1.0e9)
guard let date = UTCCalendar.date(from: dateComponents) else {
return nil
}
self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
}
}
extension Date: StatementColumnConvertible {
/// Returns a value initialized from a raw SQLite statement pointer.
///
/// - parameters:
/// - sqliteStatement: A pointer to an SQLite statement.
/// - index: The column index.
@inline(__always)
@inlinable
public init?(sqliteStatement: SQLiteStatement, index: CInt) {
switch sqlite3_column_type(sqliteStatement, index) {
case SQLITE_INTEGER, SQLITE_FLOAT:
self.init(timeIntervalSince1970: sqlite3_column_double(sqliteStatement, index))
case SQLITE_TEXT:
guard let components = DatabaseDateComponents(sqliteStatement: sqliteStatement, index: index),
let date = Date(databaseDateComponents: components)
else {
return nil
}
self.init(timeIntervalSinceReferenceDate: date.timeIntervalSinceReferenceDate)
default:
return nil
}
}
}
/// The DatabaseDate date formatter for stored dates.
private let storageDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
// The NSCalendar for stored dates.
private let UTCCalendar: Calendar = {
var calendar = Calendar(identifier: .gregorian)
calendar.locale = Locale(identifier: "en_US_POSIX")
calendar.timeZone = TimeZone(secondsFromGMT: 0)!
return calendar
}()