1 /* 2 * Geario - A cross-platform abstraction library with asynchronous I/O. 3 * 4 * Copyright (C) 2021-2022 Kerisy.com 5 * 6 * Website: https://www.kerisy.com 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module geario.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 current unix timestamp 180 long time() { 181 return Clock.currTime.toUnixTime(); 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 * 231 */ 232 class DateTime { 233 /** 234 * Returns the current time in milliseconds. Note that 235 * while the unit of time of the return value is a millisecond, 236 * the granularity of the value depends on the underlying 237 * operating system and may be larger. For example, many 238 * operating systems measure time in units of tens of 239 * milliseconds. 240 * 241 * <p> See the description of the class {@code Date} for 242 * a discussion of slight discrepancies that may arise between 243 * "computer time" and coordinated universal time (UTC). 244 * 245 * @return the difference, measured in milliseconds, between 246 * the current time and midnight, January 1, 1970 UTC. 247 */ 248 static long CurrentTimeMillis() @trusted @property { 249 return CurrentTime!(TimeUnit.Millisecond)(); 250 } 251 252 static long CurrentTimeNsecs() @trusted @property { 253 return CurrentTime!(TimeUnit.Nanosecond)(); 254 } 255 256 static long CurrentUnixTime() @trusted @property { 257 return CurrentTime!(TimeUnit.Second)(); 258 } 259 260 alias CurrentTimeSecond = CurrentUnixTime; 261 262 /** 263 */ 264 static long CurrentTime(TimeUnit targetUnit)() @trusted @property { 265 version (Windows) { 266 import core.sys.windows.winbase; 267 import core.sys.windows.winnt; 268 269 /** 270 http://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/ 271 https://stackoverflow.com/questions/10849717/what-is-the-significance-of-january-1-1601 272 https://stackoverflow.com/questions/1090869/why-is-1-1-1970-the-epoch-time 273 https://www.unixtimestamp.com/ 274 */ 275 FILETIME fileTime; 276 GetSystemTimeAsFileTime(&fileTime); 277 ULARGE_INTEGER date, adjust; 278 date.HighPart = fileTime.dwHighDateTime; 279 date.LowPart = fileTime.dwLowDateTime; 280 281 // 100-nanoseconds = milliseconds * 10000 282 adjust.QuadPart = 11644473600000 * 10000; 283 284 // removes the diff between 1970 and 1601 285 date.QuadPart -= adjust.QuadPart; 286 287 // converts back from 100-nanoseconds to milliseconds 288 return convert!(TimeUnit.HectoNanosecond, targetUnit)(date.QuadPart); 289 290 } else version (Posix) { 291 import core.sys.posix.signal : timespec; 292 version (OSX) { 293 import core.sys.posix.sys.time : gettimeofday, timeval; 294 295 timeval tv = void; 296 // Posix gettimeofday called with a valid timeval address 297 // and a null second parameter doesn't fail. 298 gettimeofday(&tv, null); 299 return convert!(TimeUnit.Second, targetUnit)(tv.tv_sec) + 300 convert!(TimeUnit.Microsecond, targetUnit)(tv.tv_usec); 301 302 } else version (linux) { 303 import core.sys.linux.time : CLOCK_REALTIME_COARSE; 304 import core.sys.posix.time : clock_gettime, CLOCK_REALTIME; 305 306 timespec ts = void; 307 immutable Error = clock_gettime(CLOCK_REALTIME, &ts); 308 // Posix clock_gettime called with a valid address and valid clock_id is only 309 // permitted to fail if the number of seconds does not fit in time_t. If tv_sec 310 // is long or larger overflow won't happen before 292 billion years A.D. 311 static if (ts.tv_sec.max < long.max) { 312 if (Error) 313 throw new TimeException("Call to clock_gettime() failed"); 314 } 315 return convert!(TimeUnit.Second, targetUnit)(ts.tv_sec) + 316 convert!(TimeUnit.Nanosecond, targetUnit)(ts.tv_nsec); 317 318 } else version (FreeBSD) { 319 import core.sys.freebsd.time : clock_gettime, CLOCK_REALTIME; 320 321 timespec ts = void; 322 immutable Error = clock_gettime(CLOCK_REALTIME, &ts); 323 // Posix clock_gettime called with a valid address and valid clock_id is only 324 // permitted to fail if the number of seconds does not fit in time_t. If tv_sec 325 // is long or larger overflow won't happen before 292 billion years A.D. 326 static if (ts.tv_sec.max < long.max) { 327 if (Error) 328 throw new TimeException("Call to clock_gettime() failed"); 329 } 330 return convert!(TimeUnit.Second, targetUnit)(ts.tv_sec) + 331 convert!(TimeUnit.Nanosecond, targetUnit)(ts.tv_nsec); 332 } else version (NetBSD) { 333 import core.sys.netbsd.time : clock_gettime, CLOCK_REALTIME; 334 335 timespec ts = void; 336 immutable Error = clock_gettime(CLOCK_REALTIME, &ts); 337 // Posix clock_gettime called with a valid address and valid clock_id is only 338 // permitted to fail if the number of seconds does not fit in time_t. If tv_sec 339 // is long or larger overflow won't happen before 292 billion years A.D. 340 static if (ts.tv_sec.max < long.max) { 341 if (Error) 342 throw new TimeException("Call to clock_gettime() failed"); 343 } 344 return convert!(TimeUnit.Second, targetUnit)(ts.tv_sec) + 345 convert!(TimeUnit.Nanosecond, targetUnit)(ts.tv_nsec); 346 } else version (DragonFlyBSD) { 347 import core.sys.dragonflybsd.time : clock_gettime, CLOCK_REALTIME; 348 349 timespec ts = void; 350 immutable Error = clock_gettime(CLOCK_REALTIME, &ts); 351 // Posix clock_gettime called with a valid address and valid clock_id is only 352 // permitted to fail if the number of seconds does not fit in time_t. If tv_sec 353 // is long or larger overflow won't happen before 292 billion years A.D. 354 static if (ts.tv_sec.max < long.max) { 355 if (Error) 356 throw new TimeException("Call to clock_gettime() failed"); 357 } 358 return convert!(TimeUnit.Second, targetUnit)(ts.tv_sec) + 359 convert!(TimeUnit.Nanosecond, targetUnit)(ts.tv_nsec); 360 } else version (Solaris) { 361 import core.sys.solaris.time : clock_gettime, CLOCK_REALTIME; 362 363 timespec ts = void; 364 immutable Error = clock_gettime(CLOCK_REALTIME, &ts); 365 // Posix clock_gettime called with a valid address and valid clock_id is only 366 // permitted to fail if the number of seconds does not fit in time_t. If tv_sec 367 // is long or larger overflow won't happen before 292 billion years A.D. 368 static if (ts.tv_sec.max < long.max) { 369 if (Error) 370 throw new TimeException("Call to clock_gettime() failed"); 371 } 372 return convert!(TimeUnit.Second, targetUnit)(ts.tv_sec) + 373 convert!(TimeUnit.Nanosecond, targetUnit)(ts.tv_nsec); 374 } else 375 static assert(0, "Unsupported OS"); 376 } else 377 static assert(0, "Unsupported OS"); 378 } 379 380 static string GetTimeAsGMT() { 381 return cast(string)*timingValue; 382 } 383 384 static shared long timestamp; 385 386 static void StartClock() { 387 if (cas(&_isClockRunning, false, true)) { 388 dateThread.start(); 389 } 390 } 391 392 static void StopClock() @nogc { 393 atomicStore(_isClockRunning, false); 394 } 395 396 private static shared const(char)[]* timingValue; 397 private __gshared Thread dateThread; 398 private static shared bool _isClockRunning = false; 399 400 shared static this() { 401 import std.array; 402 403 Appender!(char[])[2] bufs; 404 const(char)[][2] targets; 405 406 void tick(size_t index) { 407 bufs[index].clear(); 408 timestamp = core.stdc.time.time(null); 409 auto date = Clock.currTime!(ClockType.coarse)(UTC()); 410 size_t sz = UpdateDate(bufs[index], date); 411 targets[index] = bufs[index].data; 412 atomicStore(timingValue, cast(shared)&targets[index]); 413 } 414 415 tick(0); 416 417 dateThread = new Thread({ 418 size_t cur = 1; 419 while (_isClockRunning) { 420 tick(cur); 421 cur = 1 - cur; 422 Thread.sleep(1.seconds); 423 } 424 }); 425 426 dateThread.isDaemon = true; 427 // FIXME: Needing refactor or cleanup -@zxp at 12/30/2018, 10:10:09 AM 428 // 429 // It's not a good idea to launch another thread in shared static this(). 430 // https://issues.dlang.org/show_bug.cgi?id=19492 431 // StartClock(); 432 } 433 434 shared static ~this() @nogc { 435 if (cas(&_isClockRunning, true, false)) { 436 // dateThread.join(); 437 } 438 } 439 440 private static size_t UpdateDate(Output, D)(ref Output sink, D date) { 441 return formattedWrite(sink, "%s, %02s %s %04s %02s:%02s:%02s GMT", DayAsString(date.dayOfWeek), 442 date.day, MonthAsString(date.month), date.year, date.hour, 443 date.minute, date.second); 444 } 445 446 }