@@ -2,25 +2,106 @@ import Foundation
22
33public struct DateTime : Val {
44 public static var valType : ValType { . DateTime }
5- public static let gmtName = " GMT "
5+ public static let utcName = " UTC "
66
77 public let date : Foundation . Date
8- public let timezone : String // TODO: Align with Foundation.TimeZone
8+ public let timezone : String
99
10- public init ( date: Foundation . Date , timezone: String ) {
10+ public init ( date: Foundation . Date , gmtOffset : Int = 0 , timezone: String = Self . utcName ) {
1111 self . date = date
1212 self . timezone = timezone
1313 }
1414
15+ public init (
16+ year: Int ,
17+ month: Int ,
18+ day: Int ,
19+ hour: Int = 0 ,
20+ minute: Int = 0 ,
21+ second: Int = 0 ,
22+ millisecond: Int = 0 ,
23+ gmtOffset: Int = 0 ,
24+ timezone: String = Self . utcName
25+ ) throws {
26+ let components = DateComponents (
27+ calendar: calendar,
28+ timeZone: . init( secondsFromGMT: gmtOffset) ,
29+ year: year,
30+ month: month,
31+ day: day,
32+ hour: hour,
33+ minute: minute,
34+ second: second,
35+ nanosecond: millisecond * 1_000_000
36+ )
37+ guard let date = components. date else {
38+ throw ValError . invalidDateTimeDefinition
39+ }
40+ self . date = date
41+ self . timezone = timezone
42+ }
43+
44+ public init (
45+ date: Date ,
46+ time: Time ,
47+ gmtOffset: Int = 0 ,
48+ timezone: String = Self . utcName
49+ ) throws {
50+ let components = DateComponents (
51+ calendar: calendar,
52+ timeZone: . init( secondsFromGMT: gmtOffset) ,
53+ year: date. year,
54+ month: date. month,
55+ day: date. day,
56+ hour: time. hour,
57+ minute: time. minute,
58+ second: time. second,
59+ nanosecond: time. millisecond * 1_000_000
60+ )
61+ guard let date = components. date else {
62+ throw ValError . invalidDateTimeDefinition
63+ }
64+ self . date = date
65+ self . timezone = timezone
66+ }
67+
68+ public init ( _ string: String ) throws {
69+ let splits = string. split ( separator: " " )
70+ let dateTimeStr = String ( splits [ 0 ] )
71+ self . date = try Self . dateFromString ( dateTimeStr)
72+ if splits. count > 1 {
73+ self . timezone = String ( splits [ 1 ] )
74+ } else {
75+ self . timezone = Self . utcName
76+ }
77+ }
78+
1579 public func toZinc( ) -> String {
16- let formatter = ISO8601DateFormatter ( )
17- formatter. formatOptions = [ . withInternetDateTime]
18- var zinc = formatter. string ( from: date)
19- if timezone != Self . gmtName {
80+ var zinc : String
81+ if hasMilliseconds {
82+ zinc = dateTimeWithMillisFormatter. string ( from: date)
83+ } else {
84+ zinc = dateTimeFormatter. string ( from: date)
85+ }
86+ if timezone != Self . utcName {
2087 zinc += " \( timezone) "
2188 }
2289 return zinc
2390 }
91+
92+ static func dateFromString( _ isoString: String ) throws -> Foundation . Date {
93+ if let date = dateTimeFormatter. date ( from: isoString) {
94+ return date
95+ } else if let date = dateTimeWithMillisFormatter. date ( from: isoString) {
96+ return date
97+ } else {
98+ throw ValError . invalidDateTimeFormat ( isoString)
99+ }
100+ }
101+
102+ private var hasMilliseconds : Bool {
103+ return calendar. component ( . nanosecond, from: date) != 0
104+ }
24105}
25106
26107/// Singleton Haystack DateTime formatter
@@ -30,13 +111,15 @@ var dateTimeFormatter: ISO8601DateFormatter {
30111 return formatter
31112}
32113
33- /// Singleton Haystack DateTime formatter
114+ /// Singleton Haystack DateTime formatter with fractional second support
34115var dateTimeWithMillisFormatter : ISO8601DateFormatter {
35116 let formatter = ISO8601DateFormatter ( )
36117 formatter. formatOptions = [ . withInternetDateTime, . withFractionalSeconds]
37118 return formatter
38119}
39120
121+ var calendar = Calendar ( identifier: . gregorian)
122+
40123/// See https://project-haystack.org/doc/docHaystack/Json#dateTime
41124extension DateTime {
42125 static let kindValue = " dateTime "
@@ -69,11 +152,9 @@ extension DateTime {
69152 }
70153
71154 let isoString = try container. decode ( String . self, forKey: . val)
72- if let date = dateTimeFormatter. date ( from: isoString) {
73- self . date = date
74- } else if let date = dateTimeWithMillisFormatter. date ( from: isoString) {
75- self . date = date
76- } else {
155+ do {
156+ self . date = try Self . dateFromString ( isoString)
157+ } catch {
77158 throw DecodingError . typeMismatch (
78159 Self . self,
79160 . init(
@@ -83,7 +164,7 @@ extension DateTime {
83164 )
84165 }
85166
86- let timezone = ( try ? container. decode ( String . self, forKey: . tz) ) ?? Self . gmtName
167+ let timezone = ( try ? container. decode ( String . self, forKey: . tz) ) ?? Self . utcName
87168 self . timezone = timezone
88169 }
89170
@@ -97,12 +178,8 @@ extension DateTime {
97178 isoString = dateTimeFormatter. string ( from: self . date)
98179 }
99180 try container. encode ( isoString, forKey: . val)
100- if timezone != DateTime . gmtName {
181+ if timezone != DateTime . utcName {
101182 try container. encode ( timezone, forKey: . tz)
102183 }
103184 }
104-
105- private var hasMilliseconds : Bool {
106- return date. timeIntervalSinceReferenceDate. remainder ( dividingBy: 1.0 ) != 0.0
107- }
108185}
0 commit comments