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 }