1 /**
2 Datetime extension.
3 */
4 module karasux.datetime;
5 
6 import core.time : Duration, hnsecs;
7 import std.datetime : Clock, UTC, SysTime, unixTimeToStdTime;
8 
9 /**
10 Singleton UTC timezone.
11 */
12 immutable timeZoneUTC = UTC();
13 
14 /**
15 UNIX epoch time.
16 */
17 immutable UNIX_EPOCH = SysTime(unixTimeToStdTime(0), timeZoneUTC);
18 
19 /**
20 Calculate duration since UNIX epoch time.
21 
22 Params:
23     t = SysTime value.
24 Returns:
25     Duration since UNIX epoch time (1970/1/1 00:00)
26 */
27 Duration sinceUnixEpoch()(scope auto ref const(SysTime) t) @nogc nothrow pure @safe
28 {
29     return t - UNIX_EPOCH;
30 }
31 
32 ///
33 nothrow pure @safe unittest
34 {
35     import core.time : seconds;
36 
37     assert(UNIX_EPOCH.sinceUnixEpoch == Duration.zero);
38     assert(SysTime(unixTimeToStdTime(1000), timeZoneUTC).sinceUnixEpoch == 1000.seconds);
39 }
40 
41 /**
42 Returns;
43     Current timestamp since UNIX epoch.
44 */
45 Duration currentUnixTime() @safe
46 {
47     return Clock.currTime(timeZoneUTC).sinceUnixEpoch;
48 }
49 
50 ///
51 @safe unittest
52 {
53     assert(currentUnixTime.total!"seconds" == Clock.currTime(timeZoneUTC).toUnixTime);
54 }
55 
56 /**
57 Timestamp type.
58 */
59 struct Timestamp 
60 {
61     /**
62     Returns:
63         now timestamp.
64     */
65     static @property Timestamp now() @safe
66     {
67         return Timestamp(Clock.currTime(timeZoneUTC).stdTime);
68     }
69 
70     ///
71     @safe unittest
72     {
73         immutable now = Clock.currTime(timeZoneUTC).stdTime;
74         assert(Timestamp.now.stdTime >= now);
75     }
76 
77     /**
78     Returns:
79         timestamp ISO8601 string reperesentation.
80     */
81     string toString() const nothrow @safe scope
82     {
83         return SysTime(stdTime, timeZoneUTC).toISOExtString();
84     }
85 
86     /**
87     Parse timestamp.
88 
89     Params:
90         timestamp = timestamp string.
91     Returns:
92         parsed timestamp.
93     */
94     static Timestamp fromString(string timestamp) @safe
95     {
96         return Timestamp(SysTime.fromISOExtString(timestamp).stdTime);
97     }
98 
99     /**
100     calculate duration.
101     */
102     Duration opBinary(string op)(scope auto ref const(Timestamp) rhs)
103         const @nogc nothrow @safe scope if (op == "-")
104     {
105         return hnsecs(mixin("stdTime " ~ op ~ " rhs.stdTime"));
106     }
107 
108     ///
109     @nogc nothrow @safe unittest
110     {
111         immutable t1 = Timestamp(1000);
112         immutable t2 = Timestamp(500);
113         assert(t1 - t2 == hnsecs(500));
114         assert(t2 - t1 == hnsecs(-500));
115     }
116 
117     /**
118     add or subtract duration.
119     */
120     Timestamp opBinary(string op)(scope auto ref const(Duration) rhs)
121         const @nogc nothrow @safe scope if (op == "-" || op == "+")
122     {
123         return Timestamp(mixin("stdTime " ~ op ~ ` rhs.total!"hnsecs"`));
124     }
125 
126     ///
127     @nogc nothrow @safe unittest
128     {
129         import core.time : dur;
130         immutable t = Timestamp(1000);
131         assert(t + dur!"seconds"(1) == Timestamp(1000 + dur!"seconds"(1).total!"hnsecs"));
132         assert(t - dur!"hnsecs"(100) == Timestamp(900));
133     }
134 
135     /**
136     add or subtract duration.
137     */
138     void opOpAssign(string op)(scope auto ref const(Duration) rhs)
139         @nogc nothrow @safe scope if (op == "-" || op == "+")
140     {
141         mixin("stdTime " ~ op ~ `= rhs.total!"hnsecs";`);
142     }
143 
144     ///
145     @nogc nothrow @safe unittest
146     {
147         import core.time : dur;
148         auto t = Timestamp(1000);
149         t += dur!"seconds"(1);
150         assert(t == Timestamp(1000 + dur!"seconds"(1).total!"hnsecs"));
151 
152         t = Timestamp(1000);
153         t -= dur!"hnsecs"(100);
154         assert(t == Timestamp(900));
155     }
156 
157     /**
158     Round down time by unit duration.
159 
160     Params:
161         unit = unit duration.
162     Returns:
163         Rounded down timestamp.
164     */
165     Timestamp roundDown(Duration unit) const @nogc nothrow pure @safe scope
166     {
167         immutable total = unit.total!"hnsecs";
168         return (total == 0) ? this : Timestamp(stdTime - stdTime % total);
169     }
170 
171     ///
172     @nogc nothrow pure @safe unittest
173     {
174         import core.time : hnsecs, dur;
175         immutable t = Timestamp(1234567890);
176         assert(t.roundDown(dur!"seconds"(1)) == Timestamp(1230000000));
177         assert(t.roundDown(dur!"msecs"(1)) == Timestamp(1234560000));
178     }
179 
180     /**
181     Compare two timestmaps.
182 
183     Params:
184         rhs = other hand side.
185     Returns:
186         compare result.
187     */
188     int opCmp()(auto scope ref const(Timestamp) rhs) const @nogc nothrow pure @safe scope
189     {
190         // prevent overflow.
191         immutable diff = cast(long)(stdTime - rhs.stdTime);
192         return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
193     }
194 
195     ///
196     @nogc nothrow pure @safe unittest
197     {
198         immutable t1 = Timestamp(12345678912340);
199         immutable t2 = Timestamp(12345678912341);
200         assert(t1 < t2);
201         assert(t1 <= t2);
202         assert(t2 > t1);
203         assert(t2 >= t1);
204 
205         assert(!(t1 < t1));
206         assert(t1 <= t1);
207         assert(!(t1 > t1));
208         assert(t1 >= t1);
209     }
210 
211     /**
212     Compare two timestmaps.
213 
214     Params:
215         rhs = other hand side.
216     Returns:
217         compare result.
218     */
219     bool opEquals()(auto scope ref const(Timestamp) rhs) const @nogc nothrow pure @safe scope
220     {
221         return stdTime == rhs.stdTime;
222     }
223 
224     ///
225     @nogc nothrow pure @safe unittest
226     {
227         immutable t1 = Timestamp(12345678912340);
228         immutable t2 = Timestamp(12345678912341);
229         assert(t1 == t1);
230         assert(t2 == t2);
231         assert(t1 != t2);
232         assert(t2 != t1);
233     }
234 
235     ulong stdTime;
236 }
237 
238 ///
239 @safe unittest
240 {
241     immutable t = Timestamp.fromString("2021-01-02T03:04:05.678Z");
242     assert(t.toString() == "2021-01-02T03:04:05.678+00:00");
243 }
244