1 module hunt.system.TimeZone;
2 
3 import std.string;
4 
5 string getSystemTimeZoneId(string homeDir = "") {
6     version (Posix) {
7         return findTZ_md(homeDir);
8     } else version (Windows) {
9         return findTZ_md(homeDir);
10     } else {
11         // return "Asia/Shanghai";
12         return "";
13     }
14 }
15 
16 // class TimeZone {
17 
18 // }
19 
20 version (Posix) {
21     import core.sys.posix.stdlib;
22     import core.sys.posix.unistd;
23     import core.sys.posix.fcntl;
24     import core.stdc.errno;
25     import core.sys.linux.unistd;
26     import core.sys.posix.sys.stat;
27     import core.sys.posix.dirent;
28     import std.file;
29     import core.stdc.string;
30     import std.stdio;
31 
32     static const char* ETC_TIMEZONE_FILE = "/etc/timezone";
33     static const char* ZONEINFO_DIR = "/usr/share/zoneinfo";
34     static const char* DEFAULT_ZONEINFO_FILE = "/etc/localtime";
35     enum int PATH_MAX = 1024;
36 
37     string RESTARTABLE(string _cmd, string _result) {
38         string str;
39         str ~= `do { 
40                 do { `;
41         str ~= _result ~ "= " ~ _cmd ~ `; 
42                 } while((` ~ _result
43             ~ `== -1) && (errno == EINTR)); 
44             } while(0);`;
45         return str;
46     }
47 
48     static char* getPlatformTimeZoneID() {
49         /* struct */
50         stat_t statbuf;
51         char* tz = null;
52         FILE* fp;
53         int fd;
54         char* buf;
55         size_t size;
56         int res;
57 
58         /* #if defined(__linux__) */ /*
59         * Try reading the /etc/timezone file for Debian distros. There's
60         * no spec of the file format available. This parsing assumes that
61         * there's one line of an Olson tzid followed by a '\n', no
62         * leading or trailing spaces, no comments.
63         */
64         if ((fp = core.stdc.stdio.fopen(ETC_TIMEZONE_FILE, "r")) !is null) {
65             char[256] line;
66 
67             if (fgets(line.ptr, (line.sizeof), fp) !is null) {
68                 char* p = strchr(line.ptr, '\n');
69                 if (p !is null) {
70                     *p = '\0';
71                 }
72                 if (strlen(line.ptr) > 0) {
73                     tz = strdup(line.ptr);
74                 }
75             }
76             /* (void) */
77             fclose(fp);
78             if (tz !is null) {
79                 return tz;
80             }
81         }
82         /* #endif */ /* defined(__linux__) */
83 
84         /*
85         * Next, try /etc/localtime to find the zone ID.
86         */
87         mixin(RESTARTABLE("lstat(DEFAULT_ZONEINFO_FILE, &statbuf)", "res"));
88         if (res == -1) {
89             return null;
90         }
91 
92         /*
93         * If it's a symlink, get the link name and its zone ID part. (The
94         * older versions of timeconfig created a symlink as described in
95         * the Red Hat man page. It was changed in 1999 to create a copy
96         * of a zoneinfo file. It's no longer possible to get the zone ID
97         * from /etc/localtime.)
98         */
99         if (S_ISLNK(statbuf.st_mode)) {
100             char[PATH_MAX + 1] linkbuf;
101             int len;
102 
103             if ((len = cast(int) readlink(DEFAULT_ZONEINFO_FILE, linkbuf.ptr,
104                     cast(int)(linkbuf.sizeof) - 1)) == -1) {
105                 // /* jio_fprintf */writefln(stderr, cast(const char * ) "can't get a symlink of %s\n",
106                 //         DEFAULT_ZONEINFO_FILE);
107                 return null;
108             }
109             linkbuf[len] = '\0';
110             tz = getZoneName(linkbuf.ptr);
111             if (tz !is null) {
112                 tz = strdup(tz);
113                 return tz;
114             }
115         }
116 
117         /*
118         * If it's a regular file, we need to find out the same zoneinfo file
119         * that has been copied as /etc/localtime.
120         * If initial symbolic link resolution failed, we should treat target
121         * file as a regular file.
122         */
123         mixin(RESTARTABLE(`open(DEFAULT_ZONEINFO_FILE, O_RDONLY)`, "fd"));
124         if (fd == -1) {
125             return null;
126         }
127 
128         mixin(RESTARTABLE(`fstat(fd, &statbuf)`, "res"));
129         if (res == -1) {
130             /* (void) */
131             close(fd);
132             return null;
133         }
134         size = cast(size_t) statbuf.st_size;
135         buf = cast(char*) malloc(size);
136         if (buf is null) {
137             /* (void) */
138             close(fd);
139             return null;
140         }
141 
142         mixin(RESTARTABLE(`cast(int)read(fd, buf, size)`, "res"));
143         if (res != cast(int) size) {
144             /* (void) */
145             close(fd);
146             free(cast(void*) buf);
147             return null;
148         }
149         /* (void) */
150         close(fd);
151 
152         tz = findZoneinfoFile(buf, size, ZONEINFO_DIR);
153         free(cast(void*) buf);
154         return tz;
155     }
156 
157     string findTZ_md(string home_dir) {
158         char* tz;
159         char* javatz = null;
160         char* freetz = null;
161 
162         tz = getenv("TZ");
163 
164         if (tz is null || *tz == '\0') {
165             tz = getPlatformTimeZoneID();
166             freetz = tz;
167         }
168         // writeln("tz : ", tz, " freeTz : ", freetz);
169         if (tz !is null) {
170             /* Ignore preceding ':' */
171             if (*tz == ':') {
172                 tz++;
173             }
174             // #if defined(__linux__)
175             /* Ignore "posix/" prefix on Linux. */
176             if (strncmp(tz, "posix/", 6) == 0) {
177                 tz += 6;
178             }
179             // #endif
180 
181             // #if defined(_AIX)
182             //         /* On AIX do the platform to Java mapping. */
183             //         javatz = mapPlatformToJavaTimezone(home_dir, tz);
184             //         if (freetz !is  null) {
185             //             free((void *) freetz);
186             //         }
187             // #else
188             // #if defined(__solaris__)
189             //         /* Solaris might use localtime, so handle it here. */
190             //         if (strcmp(tz, "localtime") == 0) {
191             //             javatz = getSolarisDefaultZoneID();
192             //             if (freetz !is  null) {
193             //                 free((void *) freetz);
194             //             }
195             //         } else
196             // #endif
197             if (freetz is null) {
198                 /* strdup if we are still working on getenv result. */
199                 javatz = strdup(tz);
200             } else if (freetz != tz) {
201                 /* strdup and free the old buffer, if we moved the pointer. */
202                 javatz = strdup(tz);
203                 free(cast(void*) freetz);
204             } else {
205                 /* we are good if we already work on a freshly allocated buffer. */
206                 javatz = tz;
207             }
208             // #endif
209         }
210 
211         return cast(string)fromStringz(javatz); 
212     }
213 
214     static char* getZoneName(char* str) {
215         static const char* zidir = "zoneinfo/";
216 
217         char* pos = cast(char*) strstr(cast(const char*) str, zidir);
218         if (pos is null) {
219             return null;
220         }
221         return pos + strlen(zidir);
222     }
223 
224     static char* getPathName(const char* dir, const char* name) {
225         char* path;
226 
227         path = cast(char*) malloc(strlen(dir) + strlen(name) + 2);
228         if (path is null) {
229             return null;
230         }
231         return strcat(strcat(strcpy(path, dir), "/"), name);
232     }
233 
234     static char* findZoneinfoFile(char* buf, size_t size, const char* dir) {
235         DIR* dirp = null;
236         /* struct */
237         stat_t statbuf;
238         /* struct */
239         dirent* dp = null;
240         char* pathname = null;
241         int fd = -1;
242         char* dbuf = null;
243         char* tz = null;
244         int res;
245 
246         dirp = opendir(dir);
247         if (dirp is null) {
248             return null;
249         }
250 
251         while ((dp = readdir(dirp)) != null) {
252             /*
253             * Skip '.' and '..' (and possibly other .* files)
254             */
255             if (dp.d_name[0] == '.') {
256                 continue;
257             }
258 
259             /*
260             * Skip "ROC", "posixrules", and "localtime".
261             */
262             if ((strcmp(dp.d_name.ptr, "ROC") == 0) || (strcmp(dp.d_name.ptr,
263                     "posixrules") == 0) || (strcmp(dp.d_name.ptr, "localtime") == 0)) {
264                 continue;
265             }
266 
267             pathname = getPathName(dir, dp.d_name.ptr);
268             if (pathname is null) {
269                 break;
270             }
271             mixin(RESTARTABLE(`stat(pathname, &statbuf)`, "res"));
272             if (res == -1) {
273                 break;
274             }
275 
276             if (S_ISDIR(statbuf.st_mode)) {
277                 tz = findZoneinfoFile(buf, size, pathname);
278                 if (tz != null) {
279                     break;
280                 }
281             } else if (S_ISREG(statbuf.st_mode) && cast(size_t) statbuf.st_size == size) {
282                 dbuf = cast(char*) malloc(size);
283                 if (dbuf is null) {
284                     break;
285                 }
286                 mixin(RESTARTABLE(`open(pathname, O_RDONLY)`, "fd"));
287                 if (fd == -1) {
288                     break;
289                 }
290                 mixin(RESTARTABLE(`cast(int)read(fd, dbuf, size)`, "res"));
291                 if (res != cast(ssize_t) size) {
292                     break;
293                 }
294                 if (memcmp(buf, dbuf, size) == 0) {
295                     tz = getZoneName(pathname);
296                     if (tz != null) {
297                         tz = strdup(tz);
298                     }
299                     break;
300                 }
301                 free(cast(void*) dbuf);
302                 dbuf = null;
303                 /* (void) */
304                 close(fd);
305                 fd = -1;
306             }
307             free(cast(void*) pathname);
308             pathname = null;
309         }
310 
311         if (dirp != null) {
312             /* (void) */
313             closedir(dirp);
314         }
315         if (pathname != null) {
316             free(cast(void*) pathname);
317         }
318         if (fd != -1) {
319             /* (void) */
320             close(fd);
321         }
322         if (dbuf != null) {
323             free(cast(void*) dbuf);
324         }
325         return tz;
326     }
327 }
328 
329 version (Windows) {
330     import core.sys.windows.wtypes;
331     import core.sys.windows.windows;
332     import core.sys.windows.w32api;
333 
334     // import core.sys.windows.;
335     import core.stdc.stdio;
336     import core.stdc.stdlib;
337     import core.stdc.string;
338     import core.stdc.time;
339     import core.stdc.wchar_;
340     import core.sys.windows.winnls;
341     import core.sys.windows.winbase;
342     import std.string;
343 
344     enum int VALUE_UNKNOWN = 0;
345     enum int VALUE_KEY = 1;
346     enum int VALUE_MAPID = 2;
347     enum int VALUE_GMTOFFSET = 3;
348 
349     enum int MAX_ZONE_CHAR = 256;
350     enum int MAX_MAPID_LENGTH = 32;
351     enum int MAX_REGION_LENGTH = 4;
352 
353     enum string NT_TZ_KEY = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones";
354     enum string WIN_TZ_KEY = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Time Zones";
355     enum string WIN_CURRENT_TZ_KEY = "System\\CurrentControlSet\\Control\\TimeZoneInformation";
356 
357     struct TziValue {
358         LONG bias;
359         LONG stdBias;
360         LONG dstBias;
361         SYSTEMTIME stdDate;
362         SYSTEMTIME dstDate;
363     };
364 
365     /*
366     * Registry key names
367     */
368     static string[] keyNames = [
369         ("StandardName"), ("StandardName"), ("Std"), ("Std")
370     ];
371 
372     /*
373     * Indices to keyNames[]
374     */
375     enum STANDARD_NAME = 0;
376     enum STD_NAME = 2;
377 
378     /*
379     * Calls RegQueryValueEx() to get the value for the specified key. If
380     * the platform is NT, 2000 or XP, it calls the Unicode
381     * version. Otherwise, it calls the ANSI version and converts the
382     * value to Unicode. In this case, it assumes that the current ANSI
383     * Code Page is the same as the native platform code page (e.g., Code
384     * Page 932 for the Japanese Windows systems.
385     *
386     * `keyIndex' is an index value to the keyNames in Unicode
387     * (WCHAR). `keyIndex' + 1 points to its ANSI value.
388     *
389     * Returns the status value. ERROR_SUCCESS if succeeded, a
390     * non-ERROR_SUCCESS value otherwise.
391     */
392     static LONG getValueInRegistry(HKEY hKey, int keyIndex, LPDWORD typePtr,
393             LPBYTE buf, LPDWORD bufLengthPtr) {
394         LONG ret;
395         DWORD bufLength = *bufLengthPtr;
396         char[MAX_ZONE_CHAR] val;
397         DWORD valSize;
398         int len;
399 
400         *typePtr = 0;
401         ret = RegQueryValueExW(hKey, cast(WCHAR*) keyNames[keyIndex], null,
402                 typePtr, buf, bufLengthPtr);
403         if (ret == ERROR_SUCCESS && *typePtr == REG_SZ) {
404             return ret;
405         }
406 
407         valSize = (val.sizeof);
408         ret = RegQueryValueExA(hKey, cast(char*) keyNames[keyIndex + 1], null,
409                 typePtr, val.ptr, &valSize);
410         if (ret != ERROR_SUCCESS) {
411             return ret;
412         }
413         if (*typePtr != REG_SZ) {
414             return ERROR_BADKEY;
415         }
416 
417         len = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
418                 cast(LPCSTR) val, -1, cast(LPWSTR) buf, bufLength / (WCHAR.sizeof));
419         if (len <= 0) {
420             return ERROR_BADKEY;
421         }
422         return ERROR_SUCCESS;
423     }
424 
425     /*
426     * Produces custom name "GMT+hh:mm" from the given bias in buffer.
427     */
428     static void customZoneName(LONG bias, char* buffer) {
429         LONG gmtOffset;
430         int sign;
431 
432         if (bias > 0) {
433             gmtOffset = bias;
434             sign = -1;
435         } else {
436             gmtOffset = -bias;
437             sign = 1;
438         }
439         if (gmtOffset != 0) {
440             sprintf(buffer, "GMT%c%02d:%02d", ((sign >= 0) ? '+' : '-'),
441                     gmtOffset / 60, gmtOffset % 60);
442         } else {
443             strcpy(buffer, "GMT");
444         }
445     }
446 
447     /*
448     * Gets the current time zone entry in the "Time Zones" registry.
449     */
450     static int getWinTimeZone(char* winZoneName) {
451         // DYNAMIC_TIME_ZONE_INFORMATION dtzi;
452         DWORD timeType;
453         DWORD bufSize;
454         DWORD val;
455         HANDLE hKey = null;
456         LONG ret;
457         ULONG valueType;
458 
459         /*
460         * Get the dynamic time zone information so that time zone redirection
461         * can be supported. (see JDK-7044727)
462         */
463         // timeType = GetDynamicTimeZoneInformation(&dtzi);
464         // if (timeType == TIME_ZONE_ID_INVALID)
465         // {
466         //     goto err;
467         // }
468 
469         /*
470         * Make sure TimeZoneKeyName is available from the API call. If
471         * DynamicDaylightTime is disabled, return a custom time zone name
472         * based on the GMT offset. Otherwise, return the TimeZoneKeyName
473         * value.
474         */
475         // if (dtzi.TimeZoneKeyName[0] != 0)
476         // {
477         //     if (dtzi.DynamicDaylightTimeDisabled)
478         //     {
479         //         customZoneName(dtzi.Bias, winZoneName);
480         //         return VALUE_GMTOFFSET;
481         //     }
482         //     wcstombs(winZoneName, dtzi.TimeZoneKeyName, MAX_ZONE_CHAR);
483         //     return VALUE_KEY;
484         // }
485 
486         /*
487         * If TimeZoneKeyName is not available, check whether StandardName
488         * is available to fall back to the older API GetTimeZoneInformation.
489         * If not, directly read the value from registry keys.
490         */
491         // if (dtzi.StandardName[0] == 0)
492         // {
493         //     ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, KEY_READ, cast(PHKEY)&hKey);
494         //     if (ret != ERROR_SUCCESS)
495         //     {
496         //         goto err;
497         //     }
498 
499         //     /*
500         //  * Determine if auto-daylight time adjustment is turned off.
501         //  */
502         //     bufSize = (val.sizeof);
503         //     ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled", null,
504         //             &valueType, cast(LPBYTE)&val, &bufSize);
505         //     if (ret != ERROR_SUCCESS)
506         //     {
507         //         goto err;
508         //     }
509         //     /*
510         //  * Return a custom time zone name if auto-daylight time adjustment
511         //  * is disabled.
512         //  */
513         //     if (val == 1)
514         //     {
515         //         customZoneName(dtzi.Bias, winZoneName);
516         //         /* (void) */
517         //         RegCloseKey(hKey);
518         //         return VALUE_GMTOFFSET;
519         //     }
520 
521         //     bufSize = MAX_ZONE_CHAR;
522         //     ret = RegQueryValueExA(hKey, "TimeZoneKeyName", null, &valueType,
523         //             cast(LPBYTE) winZoneName, &bufSize);
524         //     if (ret != ERROR_SUCCESS)
525         //     {
526         //         goto err;
527         //     }
528         //     /* (void)  */
529         //     RegCloseKey(hKey);
530         //     return VALUE_KEY;
531         // }
532         // else
533         {
534             /*
535          * Fall back to GetTimeZoneInformation
536          */
537             TIME_ZONE_INFORMATION tzi;
538             HANDLE hSubKey = null;
539             DWORD nSubKeys, i;
540             ULONG valueType2;
541             TCHAR[MAX_ZONE_CHAR] subKeyName;
542             TCHAR[MAX_ZONE_CHAR] szValue;
543             WCHAR[MAX_ZONE_CHAR] stdNameInReg;
544             TziValue tempTzi;
545             WCHAR* stdNamePtr = tzi.StandardName.ptr;
546             int onlyMapID;
547 
548             timeType = GetTimeZoneInformation(&tzi);
549             if (timeType == TIME_ZONE_ID_INVALID) {
550                 goto err;
551             }
552 
553             ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0,
554                     KEY_READ, cast(PHKEY)&hKey);
555             if (ret == ERROR_SUCCESS) {
556                 /*
557              * Determine if auto-daylight time adjustment is turned off.
558              */
559                 bufSize = (val.sizeof);
560                 ret = RegQueryValueExA(hKey, "DynamicDaylightTimeDisabled",
561                         null, &valueType2, cast(LPBYTE)&val, &bufSize);
562                 if (ret == ERROR_SUCCESS) {
563                     if (val == 1 && tzi.DaylightDate.wMonth != 0) {
564                         /* (void) */
565                         RegCloseKey(hKey);
566                         customZoneName(tzi.Bias, winZoneName);
567                         return VALUE_GMTOFFSET;
568                     }
569                 }
570 
571                 /*
572              * Win32 problem: If the length of the standard time name is equal
573              * to (or probably longer than) 32 in the registry,
574              * GetTimeZoneInformation() on NT returns a null string as its
575              * standard time name. We need to work around this problem by
576              * getting the same information from the TimeZoneInformation
577              * registry.
578              */
579                 if (tzi.StandardName[0] == 0) {
580                     bufSize = (stdNameInReg.sizeof);
581                     ret = getValueInRegistry(hKey, STANDARD_NAME, &valueType2,
582                             cast(LPBYTE) stdNameInReg, &bufSize);
583                     if (ret != ERROR_SUCCESS) {
584                         goto err;
585                     }
586                     stdNamePtr = stdNameInReg.ptr;
587                 }
588                 /* (void) */
589                 RegCloseKey(hKey);
590             }
591 
592             /*
593          * Open the "Time Zones" registry.
594          */
595             ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, NT_TZ_KEY, 0, KEY_READ, cast(PHKEY)&hKey);
596             if (ret != ERROR_SUCCESS) {
597                 ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_TZ_KEY, 0, KEY_READ, cast(PHKEY)&hKey);
598                 /*
599              * If both failed, then give up.
600              */
601                 if (ret != ERROR_SUCCESS) {
602                     return VALUE_UNKNOWN;
603                 }
604             }
605 
606             /*
607          * Get the number of subkeys of the "Time Zones" registry for
608          * enumeration.
609          */
610             ret = RegQueryInfoKey(hKey, null, null, null, &nSubKeys, null,
611                     null, null, null, null, null, null);
612             if (ret != ERROR_SUCCESS) {
613                 goto err;
614             }
615 
616             /*
617          * Compare to the "Std" value of each subkey and find the entry that
618          * matches the current control panel setting.
619          */
620             onlyMapID = 0;
621             for (i = 0; i < nSubKeys; ++i) {
622                 DWORD size = (subKeyName.sizeof);
623                 ret = RegEnumKeyEx(hKey, i, subKeyName.ptr, &size, null, null, null, null);
624                 if (ret != ERROR_SUCCESS) {
625                     goto err;
626                 }
627                 ret = RegOpenKeyEx(hKey, subKeyName.ptr, 0, KEY_READ, cast(PHKEY)&hSubKey);
628                 if (ret != ERROR_SUCCESS) {
629                     goto err;
630                 }
631 
632                 size = (szValue.sizeof);
633                 ret = getValueInRegistry(hSubKey, STD_NAME, &valueType,
634                         cast(ubyte*)(szValue.ptr), &size);
635                 if (ret != ERROR_SUCCESS) {
636                     /*
637                  * NT 4.0 SP3 fails here since it doesn't have the "Std"
638                  * entry in the Time Zones registry.
639                  */
640                     RegCloseKey(hSubKey);
641                     onlyMapID = 1;
642                     ret = RegOpenKeyExW(hKey, stdNamePtr, 0, KEY_READ, cast(PHKEY)&hSubKey);
643                     if (ret != ERROR_SUCCESS) {
644                         goto err;
645                     }
646                     break;
647                 }
648 
649                 if (wcscmp(cast(WCHAR*) szValue, stdNamePtr) == 0) {
650                     /*
651                  * Some localized Win32 platforms use a same name to
652                  * different time zones. So, we can't rely only on the name
653                  * here. We need to check GMT offsets and transition dates
654                  * to make sure it's the registry of the current time
655                  * zone.
656                  */
657                     DWORD tziValueSize = (tempTzi.sizeof);
658                     ret = RegQueryValueEx(hSubKey, "TZI", null, &valueType,
659                             cast(char*)&tempTzi, &tziValueSize);
660                     if (ret == ERROR_SUCCESS) {
661                         if ((tzi.Bias != tempTzi.bias)
662                                 || (memcmp(cast(const void*)&tzi.StandardDate,
663                                     cast(const void*)&tempTzi.stdDate, (SYSTEMTIME.sizeof)) != 0)) {
664                             goto exitout;
665                         }
666 
667                         if (tzi.DaylightBias != 0) {
668                             if ((tzi.DaylightBias != tempTzi.dstBias)
669                                     || (memcmp(cast(const void*)&tzi.DaylightDate,
670                                         cast(const void*)&tempTzi.dstDate, (SYSTEMTIME.sizeof)) != 0)) {
671                                 goto exitout;
672                             }
673                         }
674                     }
675 
676                     /*
677                  * found matched record, terminate search
678                  */
679                     strcpy(winZoneName, cast(const char*)(subKeyName.ptr));
680                     break;
681                 }
682             exitout: /* (void) */
683                 RegCloseKey(hSubKey);
684             }
685 
686             /* (void) */
687             RegCloseKey(hKey);
688         }
689 
690         return VALUE_KEY;
691 
692     err:
693         if (hKey != null) {
694             /* (void) */
695             RegCloseKey(hKey);
696         }
697         return VALUE_UNKNOWN;
698     }
699 
700     /*
701     * The mapping table file name.
702     */
703     enum string MAPPINGS_FILE = "\\lib\\tzmappings";
704 
705     /*
706     * Index values for the mapping table.
707     */
708     enum int TZ_WIN_NAME = 0;
709     enum int TZ_REGION = 1;
710     enum int TZ_JAVA_NAME = 2;
711 
712     enum int TZ_NITEMS = 3; /* number of items (fields) */
713 
714     /*
715     * Looks up the mapping table (tzmappings) and returns a Java time
716     * zone ID (e.g., "America/Los_Angeles") if found. Otherwise, null is
717     * returned.
718     */
719     private static char* matchJavaTZ(const char* home_dir, char* tzName) {
720         int line;
721         int IDmatched = 0;
722         FILE* fp;
723         char* javaTZName = null;
724         char*[TZ_NITEMS] items;
725         char* mapFileName;
726         char[MAX_ZONE_CHAR * 4] lineBuffer;
727         int offset = 0;
728         char* errorMessage = cast(char*) toStringz("unknown error");
729         char[MAX_REGION_LENGTH] region;
730 
731         // Get the user's location
732         if (GetGeoInfo(GetUserGeoID(SYSGEOCLASS.GEOCLASS_NATION),
733                 SYSGEOTYPE.GEO_ISO2, cast(wchar*) region.ptr, MAX_REGION_LENGTH, 0) == 0) {
734             // If GetGeoInfo fails, fallback to LCID's country
735             LCID lcid = GetUserDefaultLCID();
736             if (GetLocaleInfo(lcid, LOCALE_SISO3166CTRYNAME,
737                     cast(wchar*) region.ptr, MAX_REGION_LENGTH) == 0 /* && GetLocaleInfo(lcid, LOCALE_SISO3166CTRYNAME2, cast(wchar*)region.ptr, MAX_REGION_LENGTH) == 0 */
738                 ) {
739                 region[0] = '\0';
740             }
741         }
742 
743         mapFileName = cast(char*) malloc(strlen(home_dir) + strlen(MAPPINGS_FILE) + 1);
744         if (mapFileName == null) {
745             return null;
746         }
747         strcpy(mapFileName, home_dir);
748         strcat(mapFileName, MAPPINGS_FILE);
749 
750         if ((fp = fopen(mapFileName, "r")) == null) {
751             // jio_fprintf(stderr, "can't open %s.\n", mapFileName);
752             free(cast(void*) mapFileName);
753             return null;
754         }
755         free(cast(void*) mapFileName);
756 
757         line = 0;
758         while (fgets(lineBuffer.ptr, (lineBuffer.sizeof), fp) != null) {
759             char* start;
760             char* idx;
761             char* endp;
762             int itemIndex = 0;
763 
764             line++;
765             start = idx = lineBuffer.ptr;
766             endp = &lineBuffer[(lineBuffer.length - 1)]; ///@gxc
767 
768             /*
769             * Ignore comment and blank lines.
770             */
771             if (*idx == '#' || *idx == '\n') {
772                 continue;
773             }
774 
775             for (itemIndex = 0; itemIndex < TZ_NITEMS; itemIndex++) {
776                 items[itemIndex] = start;
777                 while (*idx && *idx != ':') {
778                     if (++idx >= endp) {
779                         errorMessage = cast(char*) toStringz("premature end of line");
780                         offset = cast(int)(idx - lineBuffer.ptr);
781                         goto illegal_format;
782                     }
783                 }
784                 if (*idx == '\0') {
785                     errorMessage = cast(char*) toStringz("illegal null character found");
786                     offset = cast(int)(idx - lineBuffer.ptr);
787                     goto illegal_format;
788                 }
789                 *idx++ = '\0';
790                 start = idx;
791             }
792 
793             if (*idx != '\n') {
794                 errorMessage = cast(char*) toStringz("illegal non-newline character found");
795                 offset = cast(int)(idx - lineBuffer.ptr);
796                 goto illegal_format;
797             }
798 
799             /*
800          * We need to scan items until the
801          * exact match is found or the end of data is detected.
802          */
803             if (strcmp(items[TZ_WIN_NAME], tzName) == 0) {
804                 /*
805              * Found the time zone in the mapping table.
806              * Check the region code and select the appropriate entry
807              */
808                 if (strcmp(items[TZ_REGION], region.ptr) == 0 || strcmp(items[TZ_REGION], "001") == 0) {
809                     javaTZName = strdup(items[TZ_JAVA_NAME]);
810                     break;
811                 }
812             }
813         }
814         fclose(fp);
815 
816         return javaTZName;
817 
818     illegal_format:
819         /* (void) */
820         fclose(fp);
821         // jio_fprintf(stderr, "Illegal format in tzmappings file: %s at line %d, offset %d.\n",
822         //         errorMessage, line, offset);
823         return null;
824     }
825 
826     /*
827     * Detects the platform time zone which maps to a Java time zone ID.
828     */
829     string findTZ_md(string home_dir) {
830         char[MAX_ZONE_CHAR] winZoneName;
831         char* std_timezone = null;
832         int result;
833 
834         result = getWinTimeZone(winZoneName.ptr);
835 
836         if (result != VALUE_UNKNOWN) {
837             if (result == VALUE_GMTOFFSET) {
838                 std_timezone = strdup(winZoneName.ptr);
839             } else {
840                 std_timezone = matchJavaTZ(home_dir.toStringz(), winZoneName.ptr);
841                 if (std_timezone == null) {
842                     std_timezone = getGMTOffsetID();
843                 }
844             }
845         }
846         return cast(string)fromStringz(std_timezone);
847     }
848 
849     /**
850     * Returns a GMT-offset-based time zone ID.
851     */
852     char* getGMTOffsetID() {
853         LONG bias = 0;
854         LONG ret;
855         HANDLE hKey = null;
856         char[32] zonename;
857 
858         // Obtain the current GMT offset value of ActiveTimeBias.
859         ret = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_CURRENT_TZ_KEY, 0, KEY_READ, cast(PHKEY)&hKey);
860         if (ret == ERROR_SUCCESS) {
861             DWORD val;
862             DWORD bufSize = (val.sizeof);
863             ULONG valueType = 0;
864             ret = RegQueryValueExA(hKey, "ActiveTimeBias", null, &valueType,
865                     cast(LPBYTE)&val, &bufSize);
866             if (ret == ERROR_SUCCESS) {
867                 bias = cast(LONG) val;
868             }
869             cast(void) RegCloseKey(hKey);
870         }
871 
872         // If we can't get the ActiveTimeBias value, use Bias of TimeZoneInformation.
873         // Note: Bias doesn't reflect current daylight saving.
874         if (ret != ERROR_SUCCESS) {
875             TIME_ZONE_INFORMATION tzi;
876             if (GetTimeZoneInformation(&tzi) != TIME_ZONE_ID_INVALID) {
877                 bias = tzi.Bias;
878             }
879         }
880 
881         customZoneName(bias, zonename.ptr);
882         return strdup(zonename.ptr);
883     }
884 
885 }