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.thread : Thread; 16 import std.datetime; 17 import std.format : formattedWrite; 18 import std.string; 19 20 short monthToShort(Month month) { 21 short resultMonth; 22 switch (month) { 23 case Month.jan: 24 resultMonth = 1; 25 break; 26 case Month.feb: 27 resultMonth = 2; 28 break; 29 case Month.mar: 30 resultMonth = 3; 31 break; 32 case Month.apr: 33 resultMonth = 4; 34 break; 35 case Month.may: 36 resultMonth = 5; 37 break; 38 case Month.jun: 39 resultMonth = 6; 40 break; 41 case Month.jul: 42 resultMonth = 7; 43 break; 44 case Month.aug: 45 resultMonth = 8; 46 break; 47 case Month.sep: 48 resultMonth = 9; 49 break; 50 case Month.oct: 51 resultMonth = 10; 52 break; 53 case Month.nov: 54 resultMonth = 11; 55 break; 56 case Month.dec: 57 resultMonth = 12; 58 break; 59 default: 60 resultMonth = 0; 61 break; 62 } 63 64 return resultMonth; 65 } 66 67 string dayAsString(DayOfWeek day) { 68 final switch (day) with (DayOfWeek) { 69 case mon: 70 return "Mon"; 71 case tue: 72 return "Tue"; 73 case wed: 74 return "Wed"; 75 case thu: 76 return "Thu"; 77 case fri: 78 return "Fri"; 79 case sat: 80 return "Sat"; 81 case sun: 82 return "Sun"; 83 } 84 } 85 86 string monthAsString(Month month) { 87 final switch (month) with (Month) { 88 case jan: 89 return "Jan"; 90 case feb: 91 return "Feb"; 92 case mar: 93 return "Mar"; 94 case apr: 95 return "Apr"; 96 case may: 97 return "May"; 98 case jun: 99 return "Jun"; 100 case jul: 101 return "Jul"; 102 case aug: 103 return "Aug"; 104 case sep: 105 return "Sep"; 106 case oct: 107 return "Oct"; 108 case nov: 109 return "Nov"; 110 case dec: 111 return "Dec"; 112 } 113 } 114 115 enum TimeUnit : string { 116 Year = "years", 117 Month = "months", 118 Week = "weeks", 119 Day = "days", 120 Hour = "hours", 121 Second = "seconds", 122 Millisecond = "msecs", 123 Microsecond = "usecs", 124 HectoNanosecond = "hnsecs", 125 Nanosecond = "nsecs" 126 } 127 128 // return unix timestamp 129 long time() { 130 return DateTimeHelper.timestamp; 131 } 132 133 // return formated time string from timestamp 134 string date(string format, long timestamp = 0) { 135 import std.datetime : SysTime; 136 import std.conv : to; 137 138 long newTimestamp = timestamp > 0 ? timestamp : time(); 139 140 string timeString; 141 142 SysTime st = SysTime.fromUnixTime(newTimestamp); 143 144 // format to ubyte 145 foreach (c; format) { 146 switch (c) { 147 case 'Y': 148 timeString ~= st.year.to!string; 149 break; 150 case 'y': 151 timeString ~= (st.year.to!string)[2 .. $]; 152 break; 153 case 'm': 154 short month = monthToShort(st.month); 155 timeString ~= month < 10 ? "0" ~ month.to!string : month.to!string; 156 break; 157 case 'd': 158 timeString ~= st.day < 10 ? "0" ~ st.day.to!string : st.day.to!string; 159 break; 160 case 'H': 161 timeString ~= st.hour < 10 ? "0" ~ st.hour.to!string : st.hour.to!string; 162 break; 163 case 'i': 164 timeString ~= st.minute < 10 ? "0" ~ st.minute.to!string : st.minute.to!string; 165 break; 166 case 's': 167 timeString ~= st.second < 10 ? "0" ~ st.second.to!string : st.second.to!string; 168 break; 169 default: 170 timeString ~= c; 171 break; 172 } 173 } 174 175 return timeString; 176 } 177 178 /** 179 */ 180 class DateTimeHelper { 181 static long currentTimeMillis() { 182 return convert!(TimeUnit.HectoNanosecond, TimeUnit.Millisecond)(Clock.currStdTime); 183 } 184 185 static string getTimeAsGMT() { 186 return cast(string)*timingValue; 187 } 188 189 alias getDateAsGMT = getTimeAsGMT; 190 191 static shared long timestamp; 192 193 static void startClock() { 194 if (cas(&_isClockRunning, false, true)) { 195 dateThread.start(); 196 } 197 } 198 199 static void stopClock() { 200 atomicStore(_isClockRunning, false); 201 } 202 203 private static shared const(char)[]* timingValue; 204 private __gshared Thread dateThread; 205 private static shared bool _isClockRunning = false; 206 207 shared static this() { 208 import std.array; 209 210 Appender!(char[])[2] bufs; 211 const(char)[][2] targets; 212 213 void tick(size_t index) { 214 import core.stdc.time : time; 215 216 bufs[index].clear(); 217 timestamp = time(null); 218 auto date = Clock.currTime!(ClockType.coarse)(UTC()); 219 size_t sz = updateDate(bufs[index], date); 220 targets[index] = bufs[index].data; 221 atomicStore(timingValue, cast(shared)&targets[index]); 222 } 223 224 tick(0); 225 226 dateThread = new Thread({ 227 size_t cur = 1; 228 while (_isClockRunning) { 229 tick(cur); 230 cur = 1 - cur; 231 Thread.sleep(1.seconds); 232 } 233 }); 234 235 dateThread.isDaemon = true; 236 // FIXME: Needing refactor or cleanup -@zxp at 12/30/2018, 10:10:09 AM 237 // 238 // It's not a good idea to launch another thread in shared static this(). 239 // https://issues.dlang.org/show_bug.cgi?id=19492 240 // startClock(); 241 } 242 243 shared static ~this() { 244 if (cas(&_isClockRunning, true, false)) { 245 dateThread.join(); 246 } 247 } 248 249 private static size_t updateDate(Output, D)(ref Output sink, D date) { 250 return formattedWrite(sink, "%s, %02s %s %04s %02s:%02s:%02s GMT", dayAsString(date.dayOfWeek), 251 date.day, monthAsString(date.month), date.year, date.hour, 252 date.minute, date.second); 253 } 254 255 static string getSystemTimeZoneId() { 256 version (Posix) { 257 return cast(string) fromStringz(findTZ_md("")); 258 } else version (Windows) { 259 return cast(string) fromStringz(findTZ_md("")); 260 } else { 261 // return "Asia/Shanghai"; 262 return ""; 263 } 264 } 265 } 266 267 version (Posix) { 268 import core.sys.posix.stdlib; 269 import core.sys.posix.unistd; 270 import core.sys.posix.fcntl; 271 import core.stdc.errno; 272 import core.sys.linux.unistd; 273 import core.sys.posix.sys.stat; 274 import core.sys.posix.dirent; 275 import std.file; 276 import core.stdc.string; 277 import std.stdio; 278 279 static const char* ETC_TIMEZONE_FILE = "/etc/timezone"; 280 static const char* ZONEINFO_DIR = "/usr/share/zoneinfo"; 281 static const char* DEFAULT_ZONEINFO_FILE = "/etc/localtime"; 282 enum int PATH_MAX = 1024; 283 284 string RESTARTABLE(string _cmd, string _result) { 285 string str; 286 str ~= `do { 287 do { `; 288 str ~= _result ~ "= " ~ _cmd ~ `; 289 } while((` ~ _result 290 ~ `== -1) && (errno == EINTR)); 291 } while(0);`; 292 return str; 293 } 294 295 static char* getPlatformTimeZoneID() { 296 /* struct */ 297 stat_t statbuf; 298 char* tz = null; 299 FILE* fp; 300 int fd; 301 char* buf; 302 size_t size; 303 int res; 304 305 /* #if defined(__linux__) */ /* 306 * Try reading the /etc/timezone file for Debian distros. There's 307 * no spec of the file format available. This parsing assumes that 308 * there's one line of an Olson tzid followed by a '\n', no 309 * leading or trailing spaces, no comments. 310 */ 311 if ((fp = core.stdc.stdio.fopen(ETC_TIMEZONE_FILE, "r")) !is null) { 312 char[256] line; 313 314 if (fgets(line.ptr, (line.sizeof), fp) !is null) { 315 char* p = strchr(line.ptr, '\n'); 316 if (p !is null) { 317 *p = '\0'; 318 } 319 if (strlen(line.ptr) > 0) { 320 tz = strdup(line.ptr); 321 } 322 } 323 /* (void) */ 324 fclose(fp); 325 if (tz !is null) { 326 return tz; 327 } 328 } 329 /* #endif */ /* defined(__linux__) */ 330 331 /* 332 * Next, try /etc/localtime to find the zone ID. 333 */ 334 mixin(RESTARTABLE("lstat(DEFAULT_ZONEINFO_FILE, &statbuf)", "res")); 335 if (res == -1) { 336 return null; 337 } 338 339 /* 340 * If it's a symlink, get the link name and its zone ID part. (The 341 * older versions of timeconfig created a symlink as described in 342 * the Red Hat man page. It was changed in 1999 to create a copy 343 * of a zoneinfo file. It's no longer possible to get the zone ID 344 * from /etc/localtime.) 345 */ 346 if (S_ISLNK(statbuf.st_mode)) { 347 char[PATH_MAX + 1] linkbuf; 348 int len; 349 350 if ((len = cast(int) readlink(DEFAULT_ZONEINFO_FILE, linkbuf.ptr, 351 cast(int)(linkbuf.sizeof) - 1)) == -1) { 352 // /* jio_fprintf */writefln(stderr, cast(const char * ) "can't get a symlink of %s\n", 353 // DEFAULT_ZONEINFO_FILE); 354 return null; 355 } 356 linkbuf[len] = '\0'; 357 tz = getZoneName(linkbuf.ptr); 358 if (tz !is null) { 359 tz = strdup(tz); 360 return tz; 361 } 362 } 363 364 /* 365 * If it's a regular file, we need to find out the same zoneinfo file 366 * that has been copied as /etc/localtime. 367 * If initial symbolic link resolution failed, we should treat target 368 * file as a regular file. 369 */ 370 mixin(RESTARTABLE(`open(DEFAULT_ZONEINFO_FILE, O_RDONLY)`, "fd")); 371 if (fd == -1) { 372 return null; 373 } 374 375 mixin(RESTARTABLE(`fstat(fd, &statbuf)`, "res")); 376 if (res == -1) { 377 /* (void) */ 378 close(fd); 379 return null; 380 } 381 size = cast(size_t) statbuf.st_size; 382 buf = cast(char*) malloc(size); 383 if (buf is null) { 384 /* (void) */ 385 close(fd); 386 return null; 387 } 388 389 mixin(RESTARTABLE(`cast(int)read(fd, buf, size)`, "res")); 390 if (res != cast(int) size) { 391 /* (void) */ 392 close(fd); 393 free(cast(void*) buf); 394 return null; 395 } 396 /* (void) */ 397 close(fd); 398 399 tz = findZoneinfoFile(buf, size, ZONEINFO_DIR); 400 free(cast(void*) buf); 401 return tz; 402 } 403 404 char* findTZ_md(const char* java_home_dir) { 405 char* tz; 406 char* javatz = null; 407 char* freetz = null; 408 409 tz = getenv("TZ"); 410 411 if (tz is null || *tz == '\0') { 412 tz = getPlatformTimeZoneID(); 413 freetz = tz; 414 } 415 // writeln("tz : ", tz, " freeTz : ", freetz); 416 if (tz !is null) { 417 /* Ignore preceding ':' */ 418 if (*tz == ':') { 419 tz++; 420 } 421 // #if defined(__linux__) 422 /* Ignore "posix/" prefix on Linux. */ 423 if (strncmp(tz, "posix/", 6) == 0) { 424 tz += 6; 425 } 426 // #endif 427 428 // #if defined(_AIX) 429 // /* On AIX do the platform to Java mapping. */ 430 // javatz = mapPlatformToJavaTimezone(java_home_dir, tz); 431 // if (freetz !is null) { 432 // free((void *) freetz); 433 // } 434 // #else 435 // #if defined(__solaris__) 436 // /* Solaris might use localtime, so handle it here. */ 437 // if (strcmp(tz, "localtime") == 0) { 438 // javatz = getSolarisDefaultZoneID(); 439 // if (freetz !is null) { 440 // free((void *) freetz); 441 // } 442 // } else 443 // #endif 444 if (freetz is null) { 445 /* strdup if we are still working on getenv result. */ 446 javatz = strdup(tz); 447 } else if (freetz != tz) { 448 /* strdup and free the old buffer, if we moved the pointer. */ 449 javatz = strdup(tz); 450 free(cast(void*) freetz); 451 } else { 452 /* we are good if we already work on a freshly allocated buffer. */ 453 javatz = tz; 454 } 455 // #endif 456 } 457 458 return javatz; 459 } 460 461 static char* getZoneName(char* str) { 462 static const char* zidir = "zoneinfo/"; 463 464 char* pos = cast(char*) strstr(cast(const char*) str, zidir); 465 if (pos is null) { 466 return null; 467 } 468 return pos + strlen(zidir); 469 } 470 471 static char* getPathName(const char* dir, const char* name) { 472 char* path; 473 474 path = cast(char*) malloc(strlen(dir) + strlen(name) + 2); 475 if (path is null) { 476 return null; 477 } 478 return strcat(strcat(strcpy(path, dir), "/"), name); 479 } 480 481 static char* findZoneinfoFile(char* buf, size_t size, const char* dir) { 482 DIR* dirp = null; 483 /* struct */ 484 stat_t statbuf; 485 /* struct */ 486 dirent* dp = null; 487 char* pathname = null; 488 int fd = -1; 489 char* dbuf = null; 490 char* tz = null; 491 int res; 492 493 dirp = opendir(dir); 494 if (dirp is null) { 495 return null; 496 } 497 498 while ((dp = readdir(dirp)) != null) { 499 /* 500 * Skip '.' and '..' (and possibly other .* files) 501 */ 502 if (dp.d_name[0] == '.') { 503 continue; 504 } 505 506 /* 507 * Skip "ROC", "posixrules", and "localtime". 508 */ 509 if ((strcmp(dp.d_name.ptr, "ROC") == 0) || (strcmp(dp.d_name.ptr, 510 "posixrules") == 0) || (strcmp(dp.d_name.ptr, "localtime") == 0)) { 511 continue; 512 } 513 514 pathname = getPathName(dir, dp.d_name.ptr); 515 if (pathname is null) { 516 break; 517 } 518 mixin(RESTARTABLE(`stat(pathname, &statbuf)`, "res")); 519 if (res == -1) { 520 break; 521 } 522 523 if (S_ISDIR(statbuf.st_mode)) { 524 tz = findZoneinfoFile(buf, size, pathname); 525 if (tz != null) { 526 break; 527 } 528 } else if (S_ISREG(statbuf.st_mode) && cast(size_t) statbuf.st_size == size) { 529 dbuf = cast(char*) malloc(size); 530 if (dbuf is null) { 531 break; 532 } 533 mixin(RESTARTABLE(`open(pathname, O_RDONLY)`, "fd")); 534 if (fd == -1) { 535 break; 536 } 537 mixin(RESTARTABLE(`cast(int)read(fd, dbuf, size)`, "res")); 538 if (res != cast(ssize_t) size) { 539 break; 540 } 541 if (memcmp(buf, dbuf, size) == 0) { 542 tz = getZoneName(pathname); 543 if (tz != null) { 544 tz = strdup(tz); 545 } 546 break; 547 } 548 free(cast(void*) dbuf); 549 dbuf = null; 550 /* (void) */ 551 close(fd); 552 fd = -1; 553 } 554 free(cast(void*) pathname); 555 pathname = null; 556 } 557 558 if (dirp != null) { 559 /* (void) */ 560 closedir(dirp); 561 } 562 if (pathname != null) { 563 free(cast(void*) pathname); 564 } 565 if (fd != -1) { 566 /* (void) */ 567 close(fd); 568 } 569 if (dbuf != null) { 570 free(cast(void*) dbuf); 571 } 572 return tz; 573 } 574 } 575 576 version (Windows) { 577 import core.sys.windows.wtypes; 578 import core.sys.windows.windows; 579 import core.sys.windows.w32api; 580 581 // import core.sys.windows.; 582 import core.stdc.stdio; 583 import core.stdc.stdlib; 584 import core.stdc.string; 585 import core.stdc.time; 586 import core.stdc.wchar_; 587 import core.sys.windows.winnls; 588 import core.sys.windows.winbase; 589 import std.string; 590 591 enum int VALUE_UNKNOWN = 0; 592 enum int VALUE_KEY = 1; 593 enum int VALUE_MAPID = 2; 594 enum int VALUE_GMTOFFSET = 3; 595 596 enum int MAX_ZONE_CHAR = 256; 597 enum int MAX_MAPID_LENGTH = 32; 598 enum int MAX_REGION_LENGTH = 4; 599 600 enum string NT_TZ_KEY = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"; 601 enum string WIN_TZ_KEY = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones"; 602 enum string WIN_CURRENT_TZ_KEY = "System\\CurrentControlSet\\Control\\TimeZoneInformation"; 603 604 struct TziValue { 605 LONG bias; 606 LONG stdBias; 607 LONG dstBias; 608 SYSTEMTIME stdDate; 609 SYSTEMTIME dstDate; 610 }; 611 612 /* 613 * Registry key names 614 */ 615 static string[] keyNames = [("StandardName"), ("StandardName"), ("Std"), ("Std")]; 616 617 /* 618 * Indices to keyNames[] 619 */ 620 enum STANDARD_NAME = 0; 621 enum STD_NAME = 2; 622 623 /* 624 * Calls RegQueryValueEx() to get the value for the specified key. If 625 * the platform is NT, 2000 or XP, it calls the Unicode 626 * version. Otherwise, it calls the ANSI version and converts the 627 * value to Unicode. In this case, it assumes that the current ANSI 628 * Code Page is the same as the native platform code page (e.g., Code 629 * Page 932 for the Japanese Windows systems. 630 * 631 * `keyIndex' is an index value to the keyNames in Unicode 632 * (WCHAR). `keyIndex' + 1 points to its ANSI value. 633 * 634 * Returns the status value. ERROR_SUCCESS if succeeded, a 635 * non-ERROR_SUCCESS value otherwise. 636 */ 637 static LONG getValueInRegistry(HKEY hKey, int keyIndex, LPDWORD typePtr, 638 LPBYTE buf, LPDWORD bufLengthPtr) { 639 LONG ret; 640 DWORD bufLength = *bufLengthPtr; 641 char[MAX_ZONE_CHAR] val; 642 DWORD valSize; 643 int len; 644 645 *typePtr = 0; 646 ret = RegQueryValueExW(hKey, cast(WCHAR*) keyNames[keyIndex], null, 647 typePtr, buf, bufLengthPtr); 648 if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) { 649 return ret; 650 } 651 652 valSize = (val.sizeof); 653 ret = RegQueryValueExA(hKey, cast(char*) keyNames[keyIndex + 1], null, 654 typePtr, val.ptr, &valSize); 655 if (ret != ERROR_SUCCESS) { 656 return ret; 657 } 658 if (*typePtr != REG_SZ) { 659 return ERROR_BADKEY; 660 } 661 662 len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, 663 cast(LPCSTR) val, -1, cast(LPWSTR) buf, bufLength / (WCHAR.sizeof)); 664 if (len <= 0) { 665 return ERROR_BADKEY; 666 } 667 return ERROR_SUCCESS; 668 } 669 670 /* 671 * Produces custom name "GMT+hh:mm" from the given bias in buffer. 672 */ 673 static void customZoneName(LONG bias, char* buffer) { 674 LONG gmtOffset; 675 int sign; 676 677 if (bias > 0) { 678 gmtOffset = bias; 679 sign = -1; 680 } else { 681 gmtOffset = -bias; 682 sign = 1; 683 } 684 if (gmtOffset != 0) { 685 sprintf(buffer, "GMT%c%02d:%02d", ((sign >= 0) ? '+' : '-'), 686 gmtOffset / 60, gmtOffset % 60); 687 } else { 688 strcpy(buffer, "GMT"); 689 } 690 } 691 692 /* 693 * Gets the current time zone entry in the "Time Zones" registry. 694 */ 695 static int getWinTimeZone(char* winZoneName) { 696 // DYNAMIC_TIME_ZONE_INFORMATION dtzi; 697 DWORD timeType; 698 DWORD bufSize; 699 DWORD val; 700 HANDLE hKey = null; 701 LONG ret; 702 ULONG valueType; 703 704 /* 705 * Get the dynamic time zone information so that time zone redirection 706 * can be supported. (see JDK-7044727) 707 */ 708 // timeType = GetDynamicTimeZoneInformation(&dtzi); 709 // if (timeType == TIME_ZONE_ID_INVALID) 710 // { 711 // goto err; 712 // } 713 714 /* 715 * Make sure TimeZoneKeyName is available from the API call. If 716 * DynamicDaylightTime is disabled, return a custom time zone name 717 * based on the GMT offset. Otherwise, return the TimeZoneKeyName 718 * value. 719 */ 720 // if (dtzi.TimeZoneKeyName[0] != 0) 721 // { 722 // if (dtzi.DynamicDaylightTimeDisabled) 723 // { 724 // customZoneName(dtzi.Bias, winZoneName); 725 // return VALUE_GMTOFFSET; 726 // } 727 // wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR); 728 // return VALUE_KEY; 729 // } 730 731 /* 732 * If TimeZoneKeyName is not available, check whether StandardName 733 * is available to fall back to the older API GetTimeZoneInformation. 734 * If not, directly read the value from registry keys. 735 */ 736 // if (dtzi.StandardName[0] == 0) 737 // { 738 // ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, KEY_READ, cast(PHKEY)&hKey); 739 // if (ret != ERROR_SUCCESS) 740 // { 741 // goto err; 742 // } 743 744 // /* 745 // * Determine if auto-daylight time adjustment is turned off. 746 // */ 747 // bufSize = (val.sizeof); 748 // ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", null, 749 // &valueType, cast(LPBYTE)&val, &bufSize); 750 // if (ret != ERROR_SUCCESS) 751 // { 752 // goto err; 753 // } 754 // /* 755 // * Return a custom time zone name if auto-daylight time adjustment 756 // * is disabled. 757 // */ 758 // if (val == 1) 759 // { 760 // customZoneName(dtzi.Bias, winZoneName); 761 // /* (void) */ 762 // RegCloseKey(hKey); 763 // return VALUE_GMTOFFSET; 764 // } 765 766 // bufSize = MAX_ZONE_CHAR; 767 // ret = RegQueryValueExA(hKey, "TimeZoneKeyName", null, &valueType, 768 // cast(LPBYTE) winZoneName, &bufSize); 769 // if (ret != ERROR_SUCCESS) 770 // { 771 // goto err; 772 // } 773 // /* (void) */ 774 // RegCloseKey(hKey); 775 // return VALUE_KEY; 776 // } 777 // else 778 { 779 /* 780 * Fall back to GetTimeZoneInformation 781 */ 782 TIME_ZONE_INFORMATION tzi; 783 HANDLE hSubKey = null; 784 DWORD nSubKeys, i; 785 ULONG valueType2; 786 TCHAR[MAX_ZONE_CHAR] subKeyName; 787 TCHAR[MAX_ZONE_CHAR] szValue; 788 WCHAR[MAX_ZONE_CHAR] stdNameInReg; 789 TziValue tempTzi; 790 WCHAR* stdNamePtr = tzi.StandardName.ptr; 791 int onlyMapID; 792 793 timeType = GetTimeZoneInformation(&tzi); 794 if (timeType == TIME_ZONE_ID_INVALID) { 795 goto err; 796 } 797 798 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, 799 KEY_READ, cast(PHKEY)&hKey); 800 if (ret == ERROR_SUCCESS) { 801 /* 802 * Determine if auto-daylight time adjustment is turned off. 803 */ 804 bufSize = (val.sizeof); 805 ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", 806 null, &valueType2, cast(LPBYTE)&val, &bufSize); 807 if (ret == ERROR_SUCCESS) { 808 if (val == 1 && tzi.DaylightDate.wMonth != 0) { 809 /* (void) */ 810 RegCloseKey(hKey); 811 customZoneName(tzi.Bias, winZoneName); 812 return VALUE_GMTOFFSET; 813 } 814 } 815 816 /* 817 * Win32 problem: If the length of the standard time name is equal 818 * to (or probably longer than) 32 in the registry, 819 * GetTimeZoneInformation() on NT returns a null string as its 820 * standard time name. We need to work around this problem by 821 * getting the same information from the TimeZoneInformation 822 * registry. 823 */ 824 if (tzi.StandardName[0] == 0) { 825 bufSize = (stdNameInReg.sizeof); 826 ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType2, 827 cast(LPBYTE) stdNameInReg, &bufSize); 828 if (ret != ERROR_SUCCESS) { 829 goto err; 830 } 831 stdNamePtr = stdNameInReg.ptr; 832 } 833 /* (void) */ 834 RegCloseKey(hKey); 835 } 836 837 /* 838 * Open the "Time Zones" registry. 839 */ 840 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, cast(PHKEY)&hKey); 841 if (ret != ERROR_SUCCESS) { 842 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, cast(PHKEY)&hKey); 843 /* 844 * If both failed, then give up. 845 */ 846 if (ret != ERROR_SUCCESS) { 847 return VALUE_UNKNOWN; 848 } 849 } 850 851 /* 852 * Get the number of subkeys of the "Time Zones" registry for 853 * enumeration. 854 */ 855 ret = RegQueryInfoKey(hKey, null, null, null, &nSubKeys, null, 856 null, null, null, null, null, null); 857 if (ret != ERROR_SUCCESS) { 858 goto err; 859 } 860 861 /* 862 * Compare to the "Std" value of each subkey and find the entry that 863 * matches the current control panel setting. 864 */ 865 onlyMapID = 0; 866 for (i = 0; i < nSubKeys; ++i) { 867 DWORD size = (subKeyName.sizeof); 868 ret = RegEnumKeyEx(hKey, i, subKeyName.ptr, &size, null, null, null, null); 869 if (ret != ERROR_SUCCESS) { 870 goto err; 871 } 872 ret = RegOpenKeyEx(hKey, subKeyName.ptr, 0, KEY_READ, cast(PHKEY)&hSubKey); 873 if (ret != ERROR_SUCCESS) { 874 goto err; 875 } 876 877 size = (szValue.sizeof); 878 ret = getValueInRegistry(hSubKey, STD_NAME, &valueType, 879 cast(ubyte*)(szValue.ptr), &size); 880 if (ret != ERROR_SUCCESS) { 881 /* 882 * NT 4.0 SP3 fails here since it doesn't have the "Std" 883 * entry in the Time Zones registry. 884 */ 885 RegCloseKey(hSubKey); 886 onlyMapID = 1; 887 ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, cast(PHKEY)&hSubKey); 888 if (ret != ERROR_SUCCESS) { 889 goto err; 890 } 891 break; 892 } 893 894 if (wcscmp(cast(WCHAR*) szValue, stdNamePtr) == 0) { 895 /* 896 * Some localized Win32 platforms use a same name to 897 * different time zones. So, we can't rely only on the name 898 * here. We need to check GMT offsets and transition dates 899 * to make sure it's the registry of the current time 900 * zone. 901 */ 902 DWORD tziValueSize = (tempTzi.sizeof); 903 ret = RegQueryValueEx(hSubKey, "TZI", null, &valueType, 904 cast(char*)&tempTzi, &tziValueSize); 905 if (ret == ERROR_SUCCESS) { 906 if ((tzi.Bias != tempTzi.bias) 907 || (memcmp(cast(const void*)&tzi.StandardDate, 908 cast(const void*)&tempTzi.stdDate, (SYSTEMTIME.sizeof)) != 0)) { 909 goto exitout; 910 } 911 912 if (tzi.DaylightBias != 0) { 913 if ((tzi.DaylightBias != tempTzi.dstBias) 914 || (memcmp(cast(const void*)&tzi.DaylightDate, 915 cast(const void*)&tempTzi.dstDate, (SYSTEMTIME.sizeof)) != 0)) { 916 goto exitout; 917 } 918 } 919 } 920 921 /* 922 * found matched record, terminate search 923 */ 924 strcpy(winZoneName, cast(const char*)(subKeyName.ptr)); 925 break; 926 } 927 exitout: /* (void) */ 928 RegCloseKey(hSubKey); 929 } 930 931 /* (void) */ 932 RegCloseKey(hKey); 933 } 934 935 return VALUE_KEY; 936 937 err: 938 if (hKey != null) { 939 /* (void) */ 940 RegCloseKey(hKey); 941 } 942 return VALUE_UNKNOWN; 943 } 944 945 /* 946 * The mapping table file name. 947 */ 948 enum string MAPPINGS_FILE = "\\lib\\tzmappings"; 949 950 /* 951 * Index values for the mapping table. 952 */ 953 enum int TZ_WIN_NAME = 0; 954 enum int TZ_REGION = 1; 955 enum int TZ_JAVA_NAME = 2; 956 957 enum int TZ_NITEMS = 3; /* number of items (fields) */ 958 959 /* 960 * Looks up the mapping table (tzmappings) and returns a Java time 961 * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, null is 962 * returned. 963 */ 964 static char* matchJavaTZ(const char* java_home_dir, char* tzName) { 965 int line; 966 int IDmatched = 0; 967 FILE* fp; 968 char* javaTZName = null; 969 char*[TZ_NITEMS] items; 970 char* mapFileName; 971 char[MAX_ZONE_CHAR * 4] lineBuffer; 972 int offset = 0; 973 char* errorMessage = cast(char*) toStringz("unknown error"); 974 char[MAX_REGION_LENGTH] region; 975 976 // Get the user's location 977 if (GetGeoInfo(GetUserGeoID(SYSGEOCLASS.GEOCLASS_NATION), 978 SYSGEOTYPE.GEO_ISO2, cast(wchar*) region.ptr, MAX_REGION_LENGTH, 0) == 0) { 979 // If GetGeoInfo fails, fallback to LCID's country 980 LCID lcid = GetUserDefaultLCID(); 981 if (GetLocaleInfo(lcid, LOCALE_SISO3166CTRYNAME, 982 cast(wchar*) region.ptr, MAX_REGION_LENGTH) == 0 /* && GetLocaleInfo(lcid, LOCALE_SISO3166CTRYNAME2, cast(wchar*)region.ptr, MAX_REGION_LENGTH) == 0 */ 983 ) { 984 region[0] = '\0'; 985 } 986 } 987 988 mapFileName = cast(char*) malloc(strlen(java_home_dir) + strlen(MAPPINGS_FILE) + 1); 989 if (mapFileName == null) { 990 return null; 991 } 992 strcpy(mapFileName, java_home_dir); 993 strcat(mapFileName, MAPPINGS_FILE); 994 995 if ((fp = fopen(mapFileName, "r")) == null) { 996 // jio_fprintf(stderr, "can't open %s.\n", mapFileName); 997 free(cast(void*) mapFileName); 998 return null; 999 } 1000 free(cast(void*) mapFileName); 1001 1002 line = 0; 1003 while (fgets(lineBuffer.ptr, (lineBuffer.sizeof), fp) != null) { 1004 char* start; 1005 char* idx; 1006 char* endp; 1007 int itemIndex = 0; 1008 1009 line++; 1010 start = idx = lineBuffer.ptr; 1011 endp = &lineBuffer[(lineBuffer.length - 1)]; ///@gxc 1012 1013 /* 1014 * Ignore comment and blank lines. 1015 */ 1016 if (*idx == '#' || *idx == '\n') { 1017 continue; 1018 } 1019 1020 for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) { 1021 items[itemIndex] = start; 1022 while (*idx && *idx != ':') { 1023 if (++idx >= endp) { 1024 errorMessage = cast(char*) toStringz("premature end of line"); 1025 offset = cast(int)(idx - lineBuffer.ptr); 1026 goto illegal_format; 1027 } 1028 } 1029 if (*idx == '\0') { 1030 errorMessage = cast(char*) toStringz("illegal null character found"); 1031 offset = cast(int)(idx - lineBuffer.ptr); 1032 goto illegal_format; 1033 } 1034 *idx++ = '\0'; 1035 start = idx; 1036 } 1037 1038 if (*idx != '\n') { 1039 errorMessage = cast(char*) toStringz("illegal non-newline character found"); 1040 offset = cast(int)(idx - lineBuffer.ptr); 1041 goto illegal_format; 1042 } 1043 1044 /* 1045 * We need to scan items until the 1046 * exact match is found or the end of data is detected. 1047 */ 1048 if (strcmp(items[TZ_WIN_NAME], tzName) == 0) { 1049 /* 1050 * Found the time zone in the mapping table. 1051 * Check the region code and select the appropriate entry 1052 */ 1053 if (strcmp(items[TZ_REGION], region.ptr) == 0 || strcmp(items[TZ_REGION], "001") == 0) { 1054 javaTZName = strdup(items[TZ_JAVA_NAME]); 1055 break; 1056 } 1057 } 1058 } 1059 fclose(fp); 1060 1061 return javaTZName; 1062 1063 illegal_format: 1064 /* (void) */ 1065 fclose(fp); 1066 // jio_fprintf(stderr, "Illegal format in tzmappings file: %s at line %d, offset %d.\n", 1067 // errorMessage, line, offset); 1068 return null; 1069 } 1070 1071 /* 1072 * Detects the platform time zone which maps to a Java time zone ID. 1073 */ 1074 char* findTZ_md(const char* java_home_dir) { 1075 char[MAX_ZONE_CHAR] winZoneName; 1076 char* std_timezone = null; 1077 int result; 1078 1079 result = getWinTimeZone(winZoneName.ptr); 1080 1081 if (result != VALUE_UNKNOWN) { 1082 if (result == VALUE_GMTOFFSET) { 1083 std_timezone = strdup(winZoneName.ptr); 1084 } else { 1085 std_timezone = matchJavaTZ(java_home_dir, winZoneName.ptr); 1086 if (std_timezone == null) { 1087 std_timezone = getGMTOffsetID(); 1088 } 1089 } 1090 } 1091 return std_timezone; 1092 } 1093 1094 /** 1095 * Returns a GMT-offset-based time zone ID. 1096 */ 1097 char* getGMTOffsetID() { 1098 LONG bias = 0; 1099 LONG ret; 1100 HANDLE hKey = null; 1101 char[32] zonename; 1102 1103 // Obtain the current GMT offset value of ActiveTimeBias. 1104 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, KEY_READ, cast(PHKEY)&hKey); 1105 if (ret == ERROR_SUCCESS) { 1106 DWORD val; 1107 DWORD bufSize = (val.sizeof); 1108 ULONG valueType = 0; 1109 ret = RegQueryValueExA(hKey, "ActiveTimeBias", null, &valueType, 1110 cast(LPBYTE)&val, &bufSize); 1111 if (ret == ERROR_SUCCESS) { 1112 bias = cast(LONG) val; 1113 } 1114 cast(void) RegCloseKey(hKey); 1115 } 1116 1117 // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation. 1118 // Note: Bias doesn't reflect current daylight saving. 1119 if (ret != ERROR_SUCCESS) { 1120 TIME_ZONE_INFORMATION tzi; 1121 if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) { 1122 bias = tzi.Bias; 1123 } 1124 } 1125 1126 customZoneName(bias, zonename.ptr); 1127 return strdup(zonename.ptr); 1128 } 1129 1130 }