1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.util.DateTime;
13 
14 import core.atomic;
15 import core.stdc.time;
16 import core.thread : Thread;
17 import std.datetime;
18 import std.format : formattedWrite;
19 import std.string;
20 
21 
22 int monthByName(string month) {
23     if (month == "Jan")
24         return 0;
25     if (month == "Feb")
26         return 1;
27     if (month == "Mar")
28         return 2;
29     if (month == "Apr")
30         return 3;
31     if (month == "May")
32         return 4;
33     if (month == "Jun")
34         return 5;
35     if (month == "Jul")
36         return 6;
37     if (month == "Aug")
38         return 7;
39     if (month == "Sep")
40         return 8;
41     if (month == "Oct")
42         return 9;
43     if (month == "Nov")
44         return 10;
45     if (month == "Dec")
46         return 11;
47 
48     return -1;
49 }
50 
51 int weekDayByName(string wday) {
52     if (wday == "Sun")
53         return 0;
54     if (wday == "Mon")
55         return 1;
56     if (wday == "Tue")
57         return 2;
58     if (wday == "Wed")
59         return 3;
60     if (wday == "Thu")
61         return 4;
62     if (wday == "Fri")
63         return 5;
64     if (wday == "Sat")
65         return 6;
66 
67     return -1;
68 }
69 
70 
71 short monthToShort(Month month) {
72     short resultMonth;
73     switch (month) {
74     case Month.jan:
75         resultMonth = 1;
76         break;
77     case Month.feb:
78         resultMonth = 2;
79         break;
80     case Month.mar:
81         resultMonth = 3;
82         break;
83     case Month.apr:
84         resultMonth = 4;
85         break;
86     case Month.may:
87         resultMonth = 5;
88         break;
89     case Month.jun:
90         resultMonth = 6;
91         break;
92     case Month.jul:
93         resultMonth = 7;
94         break;
95     case Month.aug:
96         resultMonth = 8;
97         break;
98     case Month.sep:
99         resultMonth = 9;
100         break;
101     case Month.oct:
102         resultMonth = 10;
103         break;
104     case Month.nov:
105         resultMonth = 11;
106         break;
107     case Month.dec:
108         resultMonth = 12;
109         break;
110     default:
111         resultMonth = 0;
112         break;
113     }
114 
115     return resultMonth;
116 }
117 
118 string dayAsString(DayOfWeek day) {
119     final switch (day) with (DayOfWeek) {
120     case mon:
121         return "Mon";
122     case tue:
123         return "Tue";
124     case wed:
125         return "Wed";
126     case thu:
127         return "Thu";
128     case fri:
129         return "Fri";
130     case sat:
131         return "Sat";
132     case sun:
133         return "Sun";
134     }
135 }
136 
137 string monthAsString(Month month) {
138     final switch (month) with (Month) {
139     case jan:
140         return "Jan";
141     case feb:
142         return "Feb";
143     case mar:
144         return "Mar";
145     case apr:
146         return "Apr";
147     case may:
148         return "May";
149     case jun:
150         return "Jun";
151     case jul:
152         return "Jul";
153     case aug:
154         return "Aug";
155     case sep:
156         return "Sep";
157     case oct:
158         return "Oct";
159     case nov:
160         return "Nov";
161     case dec:
162         return "Dec";
163     }
164 }
165 
166 enum TimeUnit : string {
167     Year = "years",
168     Month = "months",
169     Week = "weeks",
170     Day = "days",
171     Hour = "hours",
172     Second = "seconds",
173     Millisecond = "msecs",
174     Microsecond = "usecs",
175     HectoNanosecond = "hnsecs",
176     Nanosecond = "nsecs"
177 }
178 
179 // return unix timestamp
180 long time() {
181     return DateTime.timestamp;
182 }
183 
184 // return formated time string from timestamp
185 string date(string format, long timestamp = 0) {
186     import std.datetime : SysTime;
187     import std.conv : to;
188 
189     long newTimestamp = timestamp > 0 ? timestamp : time();
190 
191     string timeString;
192 
193     SysTime st = SysTime.fromUnixTime(newTimestamp);
194 
195     // format to ubyte
196     foreach (c; format) {
197         switch (c) {
198         case 'Y':
199             timeString ~= st.year.to!string;
200             break;
201         case 'y':
202             timeString ~= (st.year.to!string)[2 .. $];
203             break;
204         case 'm':
205             short month = monthToShort(st.month);
206             timeString ~= month < 10 ? "0" ~ month.to!string : month.to!string;
207             break;
208         case 'd':
209             timeString ~= st.day < 10 ? "0" ~ st.day.to!string : st.day.to!string;
210             break;
211         case 'H':
212             timeString ~= st.hour < 10 ? "0" ~ st.hour.to!string : st.hour.to!string;
213             break;
214         case 'i':
215             timeString ~= st.minute < 10 ? "0" ~ st.minute.to!string : st.minute.to!string;
216             break;
217         case 's':
218             timeString ~= st.second < 10 ? "0" ~ st.second.to!string : st.second.to!string;
219             break;
220         default:
221             timeString ~= c;
222             break;
223         }
224     }
225 
226     return timeString;
227 }
228 
229 
230 deprecated("Using DateTime instead.")
231 alias DateTimeHelper = DateTime;
232 
233 /**
234  * 
235  */
236 class DateTime {
237     /**
238      * Returns the current time in milliseconds.  Note that
239      * while the unit of time of the return value is a millisecond,
240      * the granularity of the value depends on the underlying
241      * operating system and may be larger.  For example, many
242      * operating systems measure time in units of tens of
243      * milliseconds.
244      *
245      * <p> See the description of the class {@code Date} for
246      * a discussion of slight discrepancies that may arise between
247      * "computer time" and coordinated universal time (UTC).
248      *
249      * @return  the difference, measured in milliseconds, between
250      *          the current time and midnight, January 1, 1970 UTC.
251      */
252     static long currentTimeMillis() @trusted @property {
253         return currentTime!(TimeUnit.Millisecond)();
254     }
255 
256     static long currentTimeNsecs() @trusted @property {
257         return currentTime!(TimeUnit.Nanosecond)();
258     }
259 
260     static long currentUnixTime() @trusted @property {
261         return currentTime!(TimeUnit.Second)();
262     }
263 
264     alias currentTimeSecond = currentUnixTime;
265 
266     /**
267     */
268     static long currentTime(TimeUnit targetUnit)() @trusted @property {
269         version (Windows) {
270             import core.sys.windows.winbase;
271             import core.sys.windows.winnt;
272 
273             /**
274                 http://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/
275                 https://stackoverflow.com/questions/10849717/what-is-the-significance-of-january-1-1601
276                 https://stackoverflow.com/questions/1090869/why-is-1-1-1970-the-epoch-time
277                 https://www.unixtimestamp.com/
278             */
279             FILETIME fileTime;
280             GetSystemTimeAsFileTime(&fileTime);
281             ULARGE_INTEGER date, adjust;
282             date.HighPart = fileTime.dwHighDateTime;
283             date.LowPart = fileTime.dwLowDateTime;
284 
285             // 100-nanoseconds = milliseconds * 10000
286             adjust.QuadPart = 11644473600000 * 10000;
287 
288             // removes the diff between 1970 and 1601
289             date.QuadPart -= adjust.QuadPart;
290 
291             // converts back from 100-nanoseconds to milliseconds
292             return convert!(TimeUnit.HectoNanosecond, targetUnit)(date.QuadPart);
293 
294         } else version (Posix) {
295                 import core.sys.posix.signal : timespec;
296             version (OSX) {
297                 import core.sys.posix.sys.time : gettimeofday, timeval;
298 
299                 timeval tv = void;
300                 // Posix gettimeofday called with a valid timeval address
301                 // and a null second parameter doesn't fail.
302                 gettimeofday(&tv, null);
303                 return convert!(TimeUnit.Second, targetUnit)(tv.tv_sec) + 
304                     convert!(TimeUnit.Microsecond, targetUnit)(tv.tv_usec);
305 
306             } else version (linux) {
307                     import core.sys.linux.time : CLOCK_REALTIME_COARSE;
308                     import core.sys.posix.time : clock_gettime, CLOCK_REALTIME;
309 
310                     timespec ts = void;
311                     immutable error = clock_gettime(CLOCK_REALTIME, &ts);
312                     // Posix clock_gettime called with a valid address and valid clock_id is only
313                     // permitted to fail if the number of seconds does not fit in time_t. If tv_sec
314                     // is long or larger overflow won't happen before 292 billion years A.D.
315                     static if (ts.tv_sec.max < long.max) {
316                         if (error)
317                             throw new TimeException("Call to clock_gettime() failed");
318                     }
319                     return convert!(TimeUnit.Second, targetUnit)(ts.tv_sec) + 
320                         convert!(TimeUnit.Nanosecond, targetUnit)(ts.tv_nsec);
321 
322             } else version (FreeBSD) {
323                 import core.sys.freebsd.time : clock_gettime, CLOCK_REALTIME;
324 
325                 timespec ts = void;
326                 immutable error = clock_gettime(CLOCK_REALTIME, &ts);
327                 // Posix clock_gettime called with a valid address and valid clock_id is only
328                 // permitted to fail if the number of seconds does not fit in time_t. If tv_sec
329                 // is long or larger overflow won't happen before 292 billion years A.D.
330                 static if (ts.tv_sec.max < long.max) {
331                     if (error)
332                         throw new TimeException("Call to clock_gettime() failed");
333                 }
334                 return convert!(TimeUnit.Second, targetUnit)(ts.tv_sec) + 
335                         convert!(TimeUnit.Nanosecond, targetUnit)(ts.tv_nsec);
336             } else version (NetBSD) {
337                 import core.sys.netbsd.time : clock_gettime, CLOCK_REALTIME;
338 
339                 timespec ts = void;
340                 immutable error = clock_gettime(CLOCK_REALTIME, &ts);
341                 // Posix clock_gettime called with a valid address and valid clock_id is only
342                 // permitted to fail if the number of seconds does not fit in time_t. If tv_sec
343                 // is long or larger overflow won't happen before 292 billion years A.D.
344                 static if (ts.tv_sec.max < long.max) {
345                     if (error)
346                         throw new TimeException("Call to clock_gettime() failed");
347                 }
348                 return convert!(TimeUnit.Second, targetUnit)(ts.tv_sec) + 
349                     convert!(TimeUnit.Nanosecond, targetUnit)(ts.tv_nsec);
350             } else version (DragonFlyBSD) {
351                 import core.sys.dragonflybsd.time : clock_gettime, CLOCK_REALTIME;
352 
353                 timespec ts = void;
354                 immutable error = clock_gettime(CLOCK_REALTIME, &ts);
355                 // Posix clock_gettime called with a valid address and valid clock_id is only
356                 // permitted to fail if the number of seconds does not fit in time_t. If tv_sec
357                 // is long or larger overflow won't happen before 292 billion years A.D.
358                 static if (ts.tv_sec.max < long.max) {
359                     if (error)
360                         throw new TimeException("Call to clock_gettime() failed");
361                 }
362                 return convert!(TimeUnit.Second, targetUnit)(ts.tv_sec) + 
363                     convert!(TimeUnit.Nanosecond, targetUnit)(ts.tv_nsec);
364             } else version (Solaris) {
365                 import core.sys.solaris.time : clock_gettime, CLOCK_REALTIME;
366 
367                 timespec ts = void;
368                 immutable error = clock_gettime(CLOCK_REALTIME, &ts);
369                 // Posix clock_gettime called with a valid address and valid clock_id is only
370                 // permitted to fail if the number of seconds does not fit in time_t. If tv_sec
371                 // is long or larger overflow won't happen before 292 billion years A.D.
372                 static if (ts.tv_sec.max < long.max) {
373                     if (error)
374                         throw new TimeException("Call to clock_gettime() failed");
375                 }
376                 return convert!(TimeUnit.Second, targetUnit)(ts.tv_sec) + 
377                     convert!(TimeUnit.Nanosecond, targetUnit)(ts.tv_nsec);
378             } else
379                 static assert(0, "Unsupported OS");
380         } else
381             static assert(0, "Unsupported OS");
382     }
383 
384     static string getTimeAsGMT() {
385         return cast(string)*timingValue;
386     }
387 
388     alias getDateAsGMT = getTimeAsGMT;
389 
390     static shared long timestamp;
391 
392     static void startClock() {
393         if (cas(&_isClockRunning, false, true)) {
394             dateThread.start();
395         }
396     }
397 
398     static void stopClock() @nogc {
399         atomicStore(_isClockRunning, false);
400     }
401 
402     private static shared const(char)[]* timingValue;
403     private __gshared Thread dateThread;
404     private static shared bool _isClockRunning = false;
405 
406     shared static this() {
407         import std.array;
408 
409         Appender!(char[])[2] bufs;
410         const(char)[][2] targets;
411 
412         void tick(size_t index) {
413             bufs[index].clear();
414             timestamp = core.stdc.time.time(null);
415             auto date = Clock.currTime!(ClockType.coarse)(UTC());
416             size_t sz = updateDate(bufs[index], date);
417             targets[index] = bufs[index].data;
418             atomicStore(timingValue, cast(shared)&targets[index]);
419         }
420 
421         tick(0);
422 
423         dateThread = new Thread({
424             size_t cur = 1;
425             while (_isClockRunning) {
426                 tick(cur);
427                 cur = 1 - cur;
428                 Thread.sleep(1.seconds);
429             }
430         });
431 
432         dateThread.isDaemon = true;
433         // FIXME: Needing refactor or cleanup -@zxp at 12/30/2018, 10:10:09 AM
434         // 
435         // It's not a good idea to launch another thread in shared static this().
436         // https://issues.dlang.org/show_bug.cgi?id=19492
437         // startClock();
438     }
439 
440     shared static ~this() @nogc {
441         if (cas(&_isClockRunning, true, false)) {
442             // dateThread.join();
443         }
444     }
445 
446     private static size_t updateDate(Output, D)(ref Output sink, D date) {
447         return formattedWrite(sink, "%s, %02s %s %04s %02s:%02s:%02s GMT", dayAsString(date.dayOfWeek),
448                 date.day, monthAsString(date.month), date.year, date.hour,
449                 date.minute, date.second);
450     }
451 
452 }