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 }