1 // Written in the D programming language
2 
3 /++
4 
5 $(SCRIPT inhibitQuickIndex = 1;)
6 $(DIVC quickindex,
7 $(BOOKTABLE,
8 $(TR $(TH Category) $(TH Functions))
9 $(TR $(TD Time zones) $(TD
10     $(LREF TimeZone)
11     $(LREF UTC)
12     $(LREF LocalTime)
13     $(LREF PosixTimeZone)
14     $(LREF WindowsTimeZone)
15     $(LREF SimpleTimeZone)
16 ))
17 $(TR $(TD Utilities) $(TD
18     $(LREF clearTZEnvVar)
19     $(LREF parseTZConversions)
20     $(LREF setTZEnvVar)
21     $(LREF TZConversions)
22 ))
23 ))
24 
25     License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
26     Authors:   $(HTTP jmdavisprog.com, Jonathan M Davis)
27     Source:    $(PHOBOSSRC std/datetime/timezone.d)
28 +/
29 module std.datetime.timezone;
30 
31 import core.time : abs, convert, dur, Duration, hours, minutes;
32 import std.datetime.systime : Clock, stdTimeToUnixTime, SysTime;
33 import std.range.primitives : back, empty, front, isOutputRange, popFront;
34 import std.traits : isIntegral, isSomeString;
35 
36 version (OSX)
37     version = Darwin;
38 else version (iOS)
39     version = Darwin;
40 else version (TVOS)
41     version = Darwin;
42 else version (WatchOS)
43     version = Darwin;
44 
45 version (Windows)
46 {
47     import core.stdc.time : time_t;
48     import core.sys.windows.winbase;
49     import core.sys.windows.winsock2;
50     import std.windows.registry;
51 
52     // Uncomment and run unittests to print missing Windows TZ translations.
53     // Please subscribe to Microsoft Daylight Saving Time & Time Zone Blog
54     // (https://blogs.technet.microsoft.com/dst2007/) if you feel responsible
55     // for updating the translations.
56     // version = UpdateWindowsTZTranslations;
57 }
58 else version (Posix)
59 {
60     import core.sys.posix.signal : timespec;
61     import core.sys.posix.sys.types : time_t;
62 }
63 
64 version (StdUnittest) import std.exception : assertThrown;
65 
66 
67 /++
68     Represents a time zone. It is used with $(REF SysTime,std,datetime,systime)
69     to indicate the time zone of a $(REF SysTime,std,datetime,systime).
70   +/
71 abstract class TimeZone
72 {
73 public:
74 
75     /++
76         The name of the time zone. Exactly how the time zone name is formatted
77         depends on the derived class. In the case of $(LREF PosixTimeZone), it's
78         the TZ Database name, whereas with $(LREF WindowsTimeZone), it's the
79         name that Windows chose to give the registry key for that time zone
80         (typically the name that they give $(LREF stdTime) if the OS is in
81         English). For other time zone types, what it is depends on how they're
82         implemented.
83 
84         See_Also:
85             $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
86               Database)<br>
87             $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of
88               Time Zones)
89       +/
90     @property string name() @safe const nothrow
91     {
92         return _name;
93     }
94 
95 
96     /++
97         Typically, the abbreviation (generally 3 or 4 letters) for the time zone
98         when DST is $(I not) in effect (e.g. PST). It is not necessarily unique.
99 
100         However, on Windows, it may be the unabbreviated name (e.g. Pacific
101         Standard Time). Regardless, it is not the same as name.
102       +/
103     @property string stdName() @safe const scope nothrow
104     {
105         return _stdName;
106     }
107 
108 
109     /++
110         Typically, the abbreviation (generally 3 or 4 letters) for the time zone
111         when DST $(I is) in effect (e.g. PDT). It is not necessarily unique.
112 
113         However, on Windows, it may be the unabbreviated name (e.g. Pacific
114         Daylight Time). Regardless, it is not the same as name.
115       +/
116     @property string dstName() @safe const scope nothrow
117     {
118         return _dstName;
119     }
120 
121 
122     /++
123         Whether this time zone has Daylight Savings Time at any point in time.
124         Note that for some time zone types it may not have DST for current dates
125         but will still return true for `hasDST` because the time zone did at
126         some point have DST.
127       +/
128     @property abstract bool hasDST() @safe const nothrow;
129 
130 
131     /++
132         Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
133         in UTC time (i.e. std time) and returns whether DST is effect in this
134         time zone at the given point in time.
135 
136         Params:
137             stdTime = The UTC time that needs to be checked for DST in this time
138                       zone.
139       +/
140     abstract bool dstInEffect(long stdTime) @safe const scope nothrow;
141 
142 
143     /++
144         Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
145         in UTC time (i.e. std time) and converts it to this time zone's time.
146 
147         Params:
148             stdTime = The UTC time that needs to be adjusted to this time zone's
149                       time.
150       +/
151     abstract long utcToTZ(long stdTime) @safe const scope nothrow;
152 
153 
154     /++
155         Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
156         in this time zone's time and converts it to UTC (i.e. std time).
157 
158         Params:
159             adjTime = The time in this time zone that needs to be adjusted to
160                       UTC time.
161       +/
162     abstract long tzToUTC(long adjTime) @safe const scope nothrow;
163 
164 
165     /++
166         Returns what the offset from UTC is at the given std time.
167         It includes the DST offset in effect at that time (if any).
168 
169         Params:
170             stdTime = The UTC time for which to get the offset from UTC for this
171                       time zone.
172       +/
173     Duration utcOffsetAt(long stdTime) @safe const scope nothrow
174     {
175         return dur!"hnsecs"(utcToTZ(stdTime) - stdTime);
176     }
177 
178     // The purpose of this is to handle the case where a Windows time zone is
179     // new and exists on an up-to-date Windows box but does not exist on Windows
180     // boxes which have not been properly updated. The "date added" is included
181     // on the theory that we'll be able to remove them at some point in the
182     // the future once enough time has passed, and that way, we know how much
183     // time has passed.
184     private static string _getOldName(string windowsTZName) @safe pure nothrow
185     {
186         switch (windowsTZName)
187         {
188             case "Belarus Standard Time": return "Kaliningrad Standard Time"; // Added 2014-10-08
189             case "Russia Time Zone 10": return "Magadan Standard Time"; // Added 2014-10-08
190             case "Russia Time Zone 11": return "Magadan Standard Time"; // Added 2014-10-08
191             case "Russia Time Zone 3": return "Russian Standard Time"; // Added 2014-10-08
192             default: return null;
193         }
194     }
195 
196     // Since reading in the time zone files could be expensive, most unit tests
197     // are consolidated into this one unittest block which minimizes how often
198     // it reads a time zone file.
199     @system unittest
200     {
201         import core.exception : AssertError;
202         import std.conv : to;
203         import std.file : exists, isFile;
204         import std.format : format;
205         import std.path : chainPath;
206         import std.stdio : writefln;
207         import std.typecons : tuple;
208 
209         version (Posix) alias getTimeZone = PosixTimeZone.getTimeZone;
210         else version (Windows) alias getTimeZone = WindowsTimeZone.getTimeZone;
211 
212         version (Posix) scope(exit) clearTZEnvVar();
213 
214         static immutable(TimeZone) testTZ(string tzName,
215                                           string stdName,
216                                           string dstName,
217                                           Duration utcOffset,
218                                           Duration dstOffset,
219                                           bool north = true)
220         {
221             scope(failure) writefln("Failed time zone: %s", tzName);
222 
223             version (Posix)
224             {
225                 immutable tz = PosixTimeZone.getTimeZone(tzName);
226                 assert(tz.name == tzName);
227             }
228             else version (Windows)
229             {
230                 immutable tz = WindowsTimeZone.getTimeZone(tzName);
231                 assert(tz.name == stdName);
232             }
233 
234             immutable hasDST = dstOffset != Duration.zero;
235 
236             //assert(tz.stdName == stdName);  //Locale-dependent
237             //assert(tz.dstName == dstName);  //Locale-dependent
238             assert(tz.hasDST == hasDST);
239 
240             import std.datetime.date : DateTime;
241             immutable stdDate = DateTime(2010, north ? 1 : 7, 1, 6, 0, 0);
242             immutable dstDate = DateTime(2010, north ? 7 : 1, 1, 6, 0, 0);
243             auto std = SysTime(stdDate, tz);
244             auto dst = SysTime(dstDate, tz);
245             auto stdUTC = SysTime(stdDate - utcOffset, UTC());
246             auto dstUTC = SysTime(stdDate - utcOffset + dstOffset, UTC());
247 
248             assert(!std.dstInEffect);
249             assert(dst.dstInEffect == hasDST);
250             assert(tz.utcOffsetAt(std.stdTime) == utcOffset);
251             assert(tz.utcOffsetAt(dst.stdTime) == utcOffset + dstOffset);
252 
253             assert(cast(DateTime) std == stdDate);
254             assert(cast(DateTime) dst == dstDate);
255             assert(std == stdUTC);
256 
257             version (Posix)
258             {
259                 setTZEnvVar(tzName);
260 
261                 static void testTM(scope const SysTime st)
262                 {
263                     import core.stdc.time : tm;
264                     import core.sys.posix.time : localtime_r;
265 
266                     time_t unixTime = st.toUnixTime();
267                     tm osTimeInfo = void;
268                     localtime_r(&unixTime, &osTimeInfo);
269                     tm ourTimeInfo = st.toTM();
270 
271                     assert(ourTimeInfo.tm_sec == osTimeInfo.tm_sec);
272                     assert(ourTimeInfo.tm_min == osTimeInfo.tm_min);
273                     assert(ourTimeInfo.tm_hour == osTimeInfo.tm_hour);
274                     assert(ourTimeInfo.tm_mday == osTimeInfo.tm_mday);
275                     assert(ourTimeInfo.tm_mon == osTimeInfo.tm_mon);
276                     assert(ourTimeInfo.tm_year == osTimeInfo.tm_year);
277                     assert(ourTimeInfo.tm_wday == osTimeInfo.tm_wday);
278                     assert(ourTimeInfo.tm_yday == osTimeInfo.tm_yday);
279                     assert(ourTimeInfo.tm_isdst == osTimeInfo.tm_isdst);
280                     assert(ourTimeInfo.tm_gmtoff == osTimeInfo.tm_gmtoff);
281                     assert(to!string(ourTimeInfo.tm_zone) == to!string(osTimeInfo.tm_zone));
282                 }
283 
284                 testTM(std);
285                 testTM(dst);
286 
287                 // Apparently, right/ does not exist on Mac OS X. I don't know
288                 // whether or not it exists on FreeBSD. It's rather pointless
289                 // normally, since the Posix standard requires that leap seconds
290                 // be ignored, so it does make some sense that right/ wouldn't
291                 // be there, but since PosixTimeZone _does_ use leap seconds if
292                 // the time zone file does, we'll test that functionality if the
293                 // appropriate files exist.
294                 if (chainPath(PosixTimeZone.defaultTZDatabaseDir, "right", tzName).exists)
295                 {
296                     auto leapTZ = PosixTimeZone.getTimeZone("right/" ~ tzName);
297 
298                     assert(leapTZ.name == "right/" ~ tzName);
299                     //assert(leapTZ.stdName == stdName);  //Locale-dependent
300                     //assert(leapTZ.dstName == dstName);  //Locale-dependent
301                     assert(leapTZ.hasDST == hasDST);
302 
303                     auto leapSTD = SysTime(std.stdTime, leapTZ);
304                     auto leapDST = SysTime(dst.stdTime, leapTZ);
305 
306                     assert(!leapSTD.dstInEffect);
307                     assert(leapDST.dstInEffect == hasDST);
308 
309                     assert(leapSTD.stdTime == std.stdTime);
310                     assert(leapDST.stdTime == dst.stdTime);
311 
312                     // Whenever a leap second is added/removed,
313                     // this will have to be adjusted.
314                     //enum leapDiff = convert!("seconds", "hnsecs")(25);
315                     //assert(leapSTD.adjTime - leapDiff == std.adjTime);
316                     //assert(leapDST.adjTime - leapDiff == dst.adjTime);
317                 }
318             }
319 
320             return tz;
321         }
322 
323         import std.datetime.date : DateTime;
324         auto dstSwitches = [/+America/Los_Angeles+/ tuple(DateTime(2012, 3, 11),  DateTime(2012, 11, 4), 2, 2),
325                             /+America/New_York+/    tuple(DateTime(2012, 3, 11),  DateTime(2012, 11, 4), 2, 2),
326                             ///+America/Santiago+/    tuple(DateTime(2011, 8, 21),  DateTime(2011, 5, 8), 0, 0),
327                             /+Europe/London+/       tuple(DateTime(2012, 3, 25),  DateTime(2012, 10, 28), 1, 2),
328                             /+Europe/Paris+/        tuple(DateTime(2012, 3, 25),  DateTime(2012, 10, 28), 2, 3),
329                             /+Australia/Adelaide+/  tuple(DateTime(2012, 10, 7),  DateTime(2012, 4, 1), 2, 3)];
330 
331         import std.datetime.date : DateTimeException;
332         version (Posix)
333         {
334             version (FreeBSD)            enum utcZone = "Etc/UTC";
335             else version (OpenBSD)       enum utcZone = "UTC";
336             else version (NetBSD)        enum utcZone = "UTC";
337             else version (DragonFlyBSD)  enum utcZone = "UTC";
338             else version (linux)         enum utcZone = "UTC";
339             else version (Darwin)        enum utcZone = "UTC";
340             else version (Solaris)       enum utcZone = "UTC";
341             else static assert(0, "The location of the UTC timezone file on this Posix platform must be set.");
342 
343             auto tzs = [testTZ("America/Los_Angeles", "PST", "PDT", dur!"hours"(-8), dur!"hours"(1)),
344                         testTZ("America/New_York", "EST", "EDT", dur!"hours"(-5), dur!"hours"(1)),
345                         //testTZ("America/Santiago", "CLT", "CLST", dur!"hours"(-4), dur!"hours"(1), false),
346                         testTZ("Europe/London", "GMT", "BST", dur!"hours"(0), dur!"hours"(1)),
347                         testTZ("Europe/Paris", "CET", "CEST", dur!"hours"(1), dur!"hours"(1)),
348                         // Per www.timeanddate.com, it should be "CST" and "CDT",
349                         // but the OS insists that it's "CST" for both. We should
350                         // probably figure out how to report an error in the TZ
351                         // database and report it.
352                         testTZ("Australia/Adelaide", "CST", "CST",
353                                dur!"hours"(9) + dur!"minutes"(30), dur!"hours"(1), false)];
354 
355             testTZ(utcZone, "UTC", "UTC", dur!"hours"(0), dur!"hours"(0));
356             assertThrown!DateTimeException(PosixTimeZone.getTimeZone("hello_world"));
357         }
358         else version (Windows)
359         {
360             auto tzs = [testTZ("Pacific Standard Time", "Pacific Standard Time",
361                                "Pacific Daylight Time", dur!"hours"(-8), dur!"hours"(1)),
362                         testTZ("Eastern Standard Time", "Eastern Standard Time",
363                                "Eastern Daylight Time", dur!"hours"(-5), dur!"hours"(1)),
364                         //testTZ("Pacific SA Standard Time", "Pacific SA Standard Time",
365                                //"Pacific SA Daylight Time", dur!"hours"(-4), dur!"hours"(1), false),
366                         testTZ("GMT Standard Time", "GMT Standard Time",
367                                "GMT Daylight Time", dur!"hours"(0), dur!"hours"(1)),
368                         testTZ("Romance Standard Time", "Romance Standard Time",
369                                "Romance Daylight Time", dur!"hours"(1), dur!"hours"(1)),
370                         testTZ("Cen. Australia Standard Time", "Cen. Australia Standard Time",
371                                "Cen. Australia Daylight Time",
372                                dur!"hours"(9) + dur!"minutes"(30), dur!"hours"(1), false)];
373 
374             testTZ("Greenwich Standard Time", "Greenwich Standard Time",
375                    "Greenwich Daylight Time", dur!"hours"(0), dur!"hours"(0));
376             assertThrown!DateTimeException(WindowsTimeZone.getTimeZone("hello_world"));
377         }
378         else
379             assert(0, "OS not supported.");
380 
381         foreach (i; 0 .. tzs.length)
382         {
383             auto tz = tzs[i];
384             immutable spring = dstSwitches[i][2];
385             immutable fall = dstSwitches[i][3];
386             auto stdOffset = SysTime(dstSwitches[i][0] + dur!"days"(-1), tz).utcOffset;
387             auto dstOffset = stdOffset + dur!"hours"(1);
388 
389             // Verify that creating a SysTime in the given time zone results
390             // in a SysTime with the correct std time during and surrounding
391             // a DST switch.
392             foreach (hour; -12 .. 13)
393             {
394                 import std.exception : enforce;
395                 auto st = SysTime(dstSwitches[i][0] + dur!"hours"(hour), tz);
396                 immutable targetHour = hour < 0 ? hour + 24 : hour;
397 
398                 static void testHour(SysTime st, int hour, string tzName, size_t line = __LINE__)
399                 {
400                     enforce(st.hour == hour,
401                             new AssertError(format("[%s] [%s]: [%s] [%s]", st, tzName, st.hour, hour),
402                                             __FILE__, line));
403                 }
404 
405                 void testOffset1(Duration offset, bool dstInEffect, size_t line = __LINE__)
406                 {
407                     AssertError msg(string tag)
408                     {
409                         return new AssertError(format("%s [%s] [%s]: [%s] [%s] [%s]",
410                                                       tag, st, tz.name, st.utcOffset, stdOffset, dstOffset),
411                                                __FILE__, line);
412                     }
413 
414                     enforce(st.dstInEffect == dstInEffect, msg("1"));
415                     enforce(st.utcOffset == offset, msg("2"));
416                     enforce((st + dur!"minutes"(1)).utcOffset == offset, msg("3"));
417                 }
418 
419                 if (hour == spring)
420                 {
421                     testHour(st, spring + 1, tz.name);
422                     testHour(st + dur!"minutes"(1), spring + 1, tz.name);
423                 }
424                 else
425                 {
426                     testHour(st, targetHour, tz.name);
427                     testHour(st + dur!"minutes"(1), targetHour, tz.name);
428                 }
429 
430                 if (hour < spring)
431                     testOffset1(stdOffset, false);
432                 else
433                     testOffset1(dstOffset, true);
434 
435                 st = SysTime(dstSwitches[i][1] + dur!"hours"(hour), tz);
436                 testHour(st, targetHour, tz.name);
437 
438                 // Verify that 01:00 is the first 01:00 (or whatever hour before the switch is).
439                 if (hour == fall - 1)
440                     testHour(st + dur!"hours"(1), targetHour, tz.name);
441 
442                 if (hour < fall)
443                     testOffset1(dstOffset, true);
444                 else
445                     testOffset1(stdOffset, false);
446             }
447 
448             // Verify that converting a time in UTC to a time in another
449             // time zone results in the correct time during and surrounding
450             // a DST switch.
451             bool first = true;
452             auto springSwitch = SysTime(dstSwitches[i][0] + dur!"hours"(spring), UTC()) - stdOffset;
453             auto fallSwitch = SysTime(dstSwitches[i][1] + dur!"hours"(fall), UTC()) - dstOffset;
454             // https://issues.dlang.org/show_bug.cgi?id=3659 makes this necessary.
455             auto fallSwitchMinus1 = fallSwitch - dur!"hours"(1);
456 
457             foreach (hour; -24 .. 25)
458             {
459                 auto utc = SysTime(dstSwitches[i][0] + dur!"hours"(hour), UTC());
460                 auto local = utc.toOtherTZ(tz);
461 
462                 void testOffset2(Duration offset, size_t line = __LINE__)
463                 {
464                     AssertError msg(string tag)
465                     {
466                         return new AssertError(format("%s [%s] [%s]: [%s] [%s]", tag, hour, tz.name, utc, local),
467                                                __FILE__, line);
468                     }
469 
470                     import std.exception : enforce;
471                     enforce((utc + offset).hour == local.hour, msg("1"));
472                     enforce((utc + offset + dur!"minutes"(1)).hour == local.hour, msg("2"));
473                 }
474 
475                 if (utc < springSwitch)
476                     testOffset2(stdOffset);
477                 else
478                     testOffset2(dstOffset);
479 
480                 utc = SysTime(dstSwitches[i][1] + dur!"hours"(hour), UTC());
481                 local = utc.toOtherTZ(tz);
482 
483                 if (utc == fallSwitch || utc == fallSwitchMinus1)
484                 {
485                     if (first)
486                     {
487                         testOffset2(dstOffset);
488                         first = false;
489                     }
490                     else
491                         testOffset2(stdOffset);
492                 }
493                 else if (utc > fallSwitch)
494                     testOffset2(stdOffset);
495                 else
496                     testOffset2(dstOffset);
497             }
498         }
499     }
500 
501 
502 protected:
503 
504     /++
505         Params:
506             name    = The name of the time zone.
507             stdName = The abbreviation for the time zone during std time.
508             dstName = The abbreviation for the time zone during DST.
509       +/
510     this(string name, string stdName, string dstName) @safe immutable pure
511     {
512         _name = name;
513         _stdName = stdName;
514         _dstName = dstName;
515     }
516 
517 
518 private:
519 
520     immutable string _name;
521     immutable string _stdName;
522     immutable string _dstName;
523 }
524 
525 
526 /++
527     A TimeZone which represents the current local time zone on
528     the system running your program.
529 
530     This uses the underlying C calls to adjust the time rather than using
531     specific D code based off of system settings to calculate the time such as
532     $(LREF PosixTimeZone) and $(LREF WindowsTimeZone) do. That also means that
533     it will use whatever the current time zone is on the system, even if the
534     system's time zone changes while the program is running.
535   +/
536 final class LocalTime : TimeZone
537 {
538 public:
539 
540     /++
541         $(LREF LocalTime) is a singleton class. $(LREF LocalTime) returns its
542         only instance.
543       +/
544     static immutable(LocalTime) opCall() @trusted pure nothrow
545     {
546         alias FuncType = immutable(LocalTime) function() @safe pure nothrow;
547         return (cast(FuncType)&singleton)();
548     }
549 
550 
551     version (StdDdoc)
552     {
553         /++
554             In principle, this is the name of the local time zone. However,
555             this always returns the empty string. This is because time zones
556             cannot be uniquely identified by the attributes given by the
557             OS (such as the `stdName` and `dstName`), and neither Posix systems
558             nor Windows systems provide an easy way to get the TZ Database name
559             of the local time zone.
560 
561             See_Also:
562                 $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
563                   Database)<br>
564                 $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List
565                   of Time Zones)
566           +/
567         @property override string name() @safe const nothrow;
568     }
569 
570 
571     /++
572         Typically, the abbreviation (generally 3 or 4 letters) for the time zone
573         when DST is $(I not) in effect (e.g. PST). It is not necessarily unique.
574 
575         However, on Windows, it may be the unabbreviated name (e.g. Pacific
576         Standard Time). Regardless, it is not the same as name.
577 
578         This property is overridden because the local time of the system could
579         change while the program is running and we need to determine it
580         dynamically rather than it being fixed like it would be with most time
581         zones.
582       +/
583     @property override string stdName() @trusted const scope nothrow
584     {
585         version (Posix)
586         {
587             import core.stdc.time : tzname;
588             import std.conv : to;
589             try
590                 return to!string(tzname[0]);
591             catch (Exception e)
592                 assert(0, "to!string(tzname[0]) failed.");
593         }
594         else version (Windows)
595         {
596             TIME_ZONE_INFORMATION tzInfo;
597             GetTimeZoneInformation(&tzInfo);
598 
599             // Cannot use to!string() like this should, probably due to bug
600             // https://issues.dlang.org/show_bug.cgi?id=5016
601             //return to!string(tzInfo.StandardName);
602 
603             wchar[32] str;
604 
605             foreach (i, ref wchar c; str)
606                 c = tzInfo.StandardName[i];
607 
608             string retval;
609 
610             try
611             {
612                 foreach (dchar c; str)
613                 {
614                     if (c == '\0')
615                         break;
616 
617                     retval ~= c;
618                 }
619 
620                 return retval;
621             }
622             catch (Exception e)
623                 assert(0, "GetTimeZoneInformation() returned invalid UTF-16.");
624         }
625     }
626 
627     @safe unittest
628     {
629         version (FreeBSD)
630         {
631             // A bug on FreeBSD 9+ makes it so that this test fails.
632             // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=168862
633         }
634         else version (NetBSD)
635         {
636             // The same bug on NetBSD 7+
637         }
638         else
639         {
640             assert(LocalTime().stdName !is null);
641 
642             version (Posix)
643             {
644                 scope(exit) clearTZEnvVar();
645 
646                 setTZEnvVar("America/Los_Angeles");
647                 assert(LocalTime().stdName == "PST");
648 
649                 setTZEnvVar("America/New_York");
650                 assert(LocalTime().stdName == "EST");
651             }
652         }
653     }
654 
655 
656     /++
657         Typically, the abbreviation (generally 3 or 4 letters) for the time zone
658         when DST $(I is) in effect (e.g. PDT). It is not necessarily unique.
659 
660         However, on Windows, it may be the unabbreviated name (e.g. Pacific
661         Daylight Time). Regardless, it is not the same as name.
662 
663         This property is overridden because the local time of the system could
664         change while the program is running and we need to determine it
665         dynamically rather than it being fixed like it would be with most time
666         zones.
667       +/
668     @property override string dstName() @trusted const scope nothrow
669     {
670         version (Posix)
671         {
672             import core.stdc.time : tzname;
673             import std.conv : to;
674             try
675                 return to!string(tzname[1]);
676             catch (Exception e)
677                 assert(0, "to!string(tzname[1]) failed.");
678         }
679         else version (Windows)
680         {
681             TIME_ZONE_INFORMATION tzInfo;
682             GetTimeZoneInformation(&tzInfo);
683 
684             // Cannot use to!string() like this should, probably due to bug
685             // https://issues.dlang.org/show_bug.cgi?id=5016
686             //return to!string(tzInfo.DaylightName);
687 
688             wchar[32] str;
689 
690             foreach (i, ref wchar c; str)
691                 c = tzInfo.DaylightName[i];
692 
693             string retval;
694 
695             try
696             {
697                 foreach (dchar c; str)
698                 {
699                     if (c == '\0')
700                         break;
701 
702                     retval ~= c;
703                 }
704 
705                 return retval;
706             }
707             catch (Exception e)
708                 assert(0, "GetTimeZoneInformation() returned invalid UTF-16.");
709         }
710     }
711 
712     @safe unittest
713     {
714         // tzname, called from dstName, isn't set by default for Musl.
715         version (CRuntime_Musl)
716             assert(LocalTime().dstName is null);
717         else
718             assert(LocalTime().dstName !is null);
719 
720         version (Posix)
721         {
722             scope(exit) clearTZEnvVar();
723 
724             version (FreeBSD)
725             {
726                 // A bug on FreeBSD 9+ makes it so that this test fails.
727                 // https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=168862
728             }
729             else version (NetBSD)
730             {
731                 // The same bug on NetBSD 7+
732             }
733             else
734             {
735                 setTZEnvVar("America/Los_Angeles");
736                 assert(LocalTime().dstName == "PDT");
737 
738                 setTZEnvVar("America/New_York");
739                 assert(LocalTime().dstName == "EDT");
740             }
741         }
742     }
743 
744 
745     /++
746         Whether this time zone has Daylight Savings Time at any point in time.
747         Note that for some time zone types it may not have DST for current
748         dates but will still return true for `hasDST` because the time zone
749         did at some point have DST.
750       +/
751     @property override bool hasDST() @trusted const nothrow
752     {
753         version (Posix)
754         {
755             static if (is(typeof(daylight)))
756                 return cast(bool)(daylight);
757             else
758             {
759                 try
760                 {
761                     import std.datetime.date : Date;
762                     auto currYear = (cast(Date) Clock.currTime()).year;
763                     auto janOffset = SysTime(Date(currYear, 1, 4), cast(immutable) this).stdTime -
764                                      SysTime(Date(currYear, 1, 4), UTC()).stdTime;
765                     auto julyOffset = SysTime(Date(currYear, 7, 4), cast(immutable) this).stdTime -
766                                       SysTime(Date(currYear, 7, 4), UTC()).stdTime;
767 
768                     return janOffset != julyOffset;
769                 }
770                 catch (Exception e)
771                     assert(0, "Clock.currTime() threw.");
772             }
773         }
774         else version (Windows)
775         {
776             TIME_ZONE_INFORMATION tzInfo;
777             GetTimeZoneInformation(&tzInfo);
778 
779             return tzInfo.DaylightDate.wMonth != 0;
780         }
781     }
782 
783     @safe unittest
784     {
785         LocalTime().hasDST;
786 
787         version (Posix)
788         {
789             scope(exit) clearTZEnvVar();
790 
791             setTZEnvVar("America/Los_Angeles");
792             assert(LocalTime().hasDST);
793 
794             setTZEnvVar("America/New_York");
795             assert(LocalTime().hasDST);
796 
797             setTZEnvVar("UTC");
798             assert(!LocalTime().hasDST);
799         }
800     }
801 
802 
803     /++
804         Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
805         in UTC time (i.e. std time) and returns whether DST is in effect in this
806         time zone at the given point in time.
807 
808         Params:
809             stdTime = The UTC time that needs to be checked for DST in this time
810                       zone.
811       +/
812     override bool dstInEffect(long stdTime) @trusted const scope nothrow
813     {
814         import core.stdc.time : tm;
815 
816         time_t unixTime = stdTimeToUnixTime(stdTime);
817 
818         version (Posix)
819         {
820             import core.sys.posix.time : localtime_r;
821 
822             tm timeInfo = void;
823             localtime_r(&unixTime, &timeInfo);
824 
825             return cast(bool)(timeInfo.tm_isdst);
826         }
827         else version (Windows)
828         {
829             import core.stdc.time : localtime;
830 
831             // Apparently Windows isn't smart enough to deal with negative time_t.
832             if (unixTime >= 0)
833             {
834                 tm* timeInfo = localtime(&unixTime);
835 
836                 if (timeInfo)
837                     return cast(bool)(timeInfo.tm_isdst);
838             }
839 
840             TIME_ZONE_INFORMATION tzInfo;
841             GetTimeZoneInformation(&tzInfo);
842 
843             return WindowsTimeZone._dstInEffect(&tzInfo, stdTime);
844         }
845     }
846 
847     @safe unittest
848     {
849         auto currTime = Clock.currStdTime;
850         LocalTime().dstInEffect(currTime);
851     }
852 
853 
854     /++
855         Returns hnsecs in the local time zone using the standard C function
856         calls on Posix systems and the standard Windows system calls on Windows
857         systems to adjust the time to the appropriate time zone from std time.
858 
859         Params:
860             stdTime = The UTC time that needs to be adjusted to this time zone's
861                       time.
862 
863         See_Also:
864             `TimeZone.utcToTZ`
865       +/
866     override long utcToTZ(long stdTime) @trusted const scope nothrow
867     {
868         version (Solaris)
869             return stdTime + convert!("seconds", "hnsecs")(tm_gmtoff(stdTime));
870         else version (Posix)
871         {
872             import core.stdc.time : tm;
873             import core.sys.posix.time : localtime_r;
874             time_t unixTime = stdTimeToUnixTime(stdTime);
875             tm timeInfo = void;
876             localtime_r(&unixTime, &timeInfo);
877 
878             return stdTime + convert!("seconds", "hnsecs")(timeInfo.tm_gmtoff);
879         }
880         else version (Windows)
881         {
882             TIME_ZONE_INFORMATION tzInfo;
883             GetTimeZoneInformation(&tzInfo);
884 
885             return WindowsTimeZone._utcToTZ(&tzInfo, stdTime, hasDST);
886         }
887     }
888 
889     @safe unittest
890     {
891         LocalTime().utcToTZ(0);
892     }
893 
894 
895     /++
896         Returns std time using the standard C function calls on Posix systems
897         and the standard Windows system calls on Windows systems to adjust the
898         time to UTC from the appropriate time zone.
899 
900         See_Also:
901             `TimeZone.tzToUTC`
902 
903         Params:
904             adjTime = The time in this time zone that needs to be adjusted to
905                       UTC time.
906       +/
907     override long tzToUTC(long adjTime) @trusted const scope nothrow
908     {
909         version (Posix)
910         {
911             import core.stdc.time : tm;
912             import core.sys.posix.time : localtime_r;
913             time_t unixTime = stdTimeToUnixTime(adjTime);
914 
915             immutable past = unixTime - cast(time_t) convert!("days", "seconds")(1);
916             tm timeInfo = void;
917             localtime_r(past < unixTime ? &past : &unixTime, &timeInfo);
918             immutable pastOffset = timeInfo.tm_gmtoff;
919 
920             immutable future = unixTime + cast(time_t) convert!("days", "seconds")(1);
921             localtime_r(future > unixTime ? &future : &unixTime, &timeInfo);
922             immutable futureOffset = timeInfo.tm_gmtoff;
923 
924             if (pastOffset == futureOffset)
925                 return adjTime - convert!("seconds", "hnsecs")(pastOffset);
926 
927             if (pastOffset < futureOffset)
928                 unixTime -= cast(time_t) convert!("hours", "seconds")(1);
929 
930             unixTime -= pastOffset;
931             localtime_r(&unixTime, &timeInfo);
932 
933             return adjTime - convert!("seconds", "hnsecs")(timeInfo.tm_gmtoff);
934         }
935         else version (Windows)
936         {
937             TIME_ZONE_INFORMATION tzInfo;
938             GetTimeZoneInformation(&tzInfo);
939 
940             return WindowsTimeZone._tzToUTC(&tzInfo, adjTime, hasDST);
941         }
942     }
943 
944     @safe unittest
945     {
946         import core.exception : AssertError;
947         import std.format : format;
948         import std.typecons : tuple;
949 
950         assert(LocalTime().tzToUTC(LocalTime().utcToTZ(0)) == 0);
951         assert(LocalTime().utcToTZ(LocalTime().tzToUTC(0)) == 0);
952 
953         assert(LocalTime().tzToUTC(LocalTime().utcToTZ(0)) == 0);
954         assert(LocalTime().utcToTZ(LocalTime().tzToUTC(0)) == 0);
955 
956         version (Posix)
957         {
958             scope(exit) clearTZEnvVar();
959 
960             import std.datetime.date : DateTime;
961             auto tzInfos = [tuple("America/Los_Angeles", DateTime(2012, 3, 11), DateTime(2012, 11, 4), 2, 2),
962                             tuple("America/New_York",    DateTime(2012, 3, 11), DateTime(2012, 11, 4), 2, 2),
963                             //tuple("America/Santiago",    DateTime(2011, 8, 21), DateTime(2011, 5, 8), 0, 0),
964                             tuple("Atlantic/Azores",     DateTime(2011, 3, 27), DateTime(2011, 10, 30), 0, 1),
965                             tuple("Europe/London",       DateTime(2012, 3, 25), DateTime(2012, 10, 28), 1, 2),
966                             tuple("Europe/Paris",        DateTime(2012, 3, 25), DateTime(2012, 10, 28), 2, 3),
967                             tuple("Australia/Adelaide",  DateTime(2012, 10, 7), DateTime(2012, 4, 1), 2, 3)];
968 
969             foreach (i; 0 .. tzInfos.length)
970             {
971                 import std.exception : enforce;
972                 auto tzName = tzInfos[i][0];
973                 setTZEnvVar(tzName);
974                 immutable spring = tzInfos[i][3];
975                 immutable fall = tzInfos[i][4];
976                 auto stdOffset = SysTime(tzInfos[i][1] + dur!"hours"(-12)).utcOffset;
977                 auto dstOffset = stdOffset + dur!"hours"(1);
978 
979                 // Verify that creating a SysTime in the given time zone results
980                 // in a SysTime with the correct std time during and surrounding
981                 // a DST switch.
982                 foreach (hour; -12 .. 13)
983                 {
984                     auto st = SysTime(tzInfos[i][1] + dur!"hours"(hour));
985                     immutable targetHour = hour < 0 ? hour + 24 : hour;
986 
987                     static void testHour(SysTime st, int hour, string tzName, size_t line = __LINE__)
988                     {
989                         enforce(st.hour == hour,
990                                 new AssertError(format("[%s] [%s]: [%s] [%s]", st, tzName, st.hour, hour),
991                                                 __FILE__, line));
992                     }
993 
994                     void testOffset1(Duration offset, bool dstInEffect, size_t line = __LINE__)
995                     {
996                         AssertError msg(string tag)
997                         {
998                             return new AssertError(format("%s [%s] [%s]: [%s] [%s] [%s]",
999                                                           tag, st, tzName, st.utcOffset, stdOffset, dstOffset),
1000                                                    __FILE__, line);
1001                         }
1002 
1003                         enforce(st.dstInEffect == dstInEffect, msg("1"));
1004                         enforce(st.utcOffset == offset, msg("2"));
1005                         enforce((st + dur!"minutes"(1)).utcOffset == offset, msg("3"));
1006                     }
1007 
1008                     if (hour == spring)
1009                     {
1010                         testHour(st, spring + 1, tzName);
1011                         testHour(st + dur!"minutes"(1), spring + 1, tzName);
1012                     }
1013                     else
1014                     {
1015                         testHour(st, targetHour, tzName);
1016                         testHour(st + dur!"minutes"(1), targetHour, tzName);
1017                     }
1018 
1019                     if (hour < spring)
1020                         testOffset1(stdOffset, false);
1021                     else
1022                         testOffset1(dstOffset, true);
1023 
1024                     st = SysTime(tzInfos[i][2] + dur!"hours"(hour));
1025                     testHour(st, targetHour, tzName);
1026 
1027                     // Verify that 01:00 is the first 01:00 (or whatever hour before the switch is).
1028                     if (hour == fall - 1)
1029                         testHour(st + dur!"hours"(1), targetHour, tzName);
1030 
1031                     if (hour < fall)
1032                         testOffset1(dstOffset, true);
1033                     else
1034                         testOffset1(stdOffset, false);
1035                 }
1036 
1037                 // Verify that converting a time in UTC to a time in another
1038                 // time zone results in the correct time during and surrounding
1039                 // a DST switch.
1040                 bool first = true;
1041                 auto springSwitch = SysTime(tzInfos[i][1] + dur!"hours"(spring), UTC()) - stdOffset;
1042                 auto fallSwitch = SysTime(tzInfos[i][2] + dur!"hours"(fall), UTC()) - dstOffset;
1043                 // https://issues.dlang.org/show_bug.cgi?id=3659 makes this necessary.
1044                 auto fallSwitchMinus1 = fallSwitch - dur!"hours"(1);
1045 
1046                 foreach (hour; -24 .. 25)
1047                 {
1048                     auto utc = SysTime(tzInfos[i][1] + dur!"hours"(hour), UTC());
1049                     auto local = utc.toLocalTime();
1050 
1051                     void testOffset2(Duration offset, size_t line = __LINE__)
1052                     {
1053                         AssertError msg(string tag)
1054                         {
1055                             return new AssertError(format("%s [%s] [%s]: [%s] [%s]", tag, hour, tzName, utc, local),
1056                                                    __FILE__, line);
1057                         }
1058 
1059                         enforce((utc + offset).hour == local.hour, msg("1"));
1060                         enforce((utc + offset + dur!"minutes"(1)).hour == local.hour, msg("2"));
1061                     }
1062 
1063                     if (utc < springSwitch)
1064                         testOffset2(stdOffset);
1065                     else
1066                         testOffset2(dstOffset);
1067 
1068                     utc = SysTime(tzInfos[i][2] + dur!"hours"(hour), UTC());
1069                     local = utc.toLocalTime();
1070 
1071                     if (utc == fallSwitch || utc == fallSwitchMinus1)
1072                     {
1073                         if (first)
1074                         {
1075                             testOffset2(dstOffset);
1076                             first = false;
1077                         }
1078                         else
1079                             testOffset2(stdOffset);
1080                     }
1081                     else if (utc > fallSwitch)
1082                         testOffset2(stdOffset);
1083                     else
1084                         testOffset2(dstOffset);
1085                 }
1086             }
1087         }
1088     }
1089 
1090 
1091 private:
1092 
1093     this() @safe immutable pure
1094     {
1095         super("", "", "");
1096     }
1097 
1098 
1099     // This is done so that we can maintain purity in spite of doing an impure
1100     // operation the first time that LocalTime() is called.
1101     static immutable(LocalTime) singleton() @trusted
1102     {
1103         import core.stdc.time : tzset;
1104         import std.concurrency : initOnce;
1105         static instance = new immutable(LocalTime)();
1106         static shared bool guard;
1107         initOnce!guard({tzset(); return true;}());
1108         return instance;
1109     }
1110 
1111 
1112     // The Solaris version of struct tm has no tm_gmtoff field, so do it here
1113     version (Solaris)
1114     {
1115         long tm_gmtoff(long stdTime) @trusted const nothrow
1116         {
1117             import core.stdc.time : tm;
1118             import core.sys.posix.time : localtime_r, gmtime_r;
1119 
1120             time_t unixTime = stdTimeToUnixTime(stdTime);
1121             tm timeInfo = void;
1122             localtime_r(&unixTime, &timeInfo);
1123             tm timeInfoGmt = void;
1124             gmtime_r(&unixTime, &timeInfoGmt);
1125 
1126             return timeInfo.tm_sec - timeInfoGmt.tm_sec +
1127                    convert!("minutes", "seconds")(timeInfo.tm_min - timeInfoGmt.tm_min) +
1128                    convert!("hours", "seconds")(timeInfo.tm_hour - timeInfoGmt.tm_hour);
1129         }
1130     }
1131 }
1132 
1133 
1134 /++
1135     A $(LREF TimeZone) which represents UTC.
1136   +/
1137 final class UTC : TimeZone
1138 {
1139 public:
1140 
1141     /++
1142         `UTC` is a singleton class. `UTC` returns its only instance.
1143       +/
1144     static immutable(UTC) opCall() @safe pure nothrow
1145     {
1146         return _utc;
1147     }
1148 
1149 
1150     /++
1151         Always returns false.
1152       +/
1153     @property override bool hasDST() @safe const nothrow
1154     {
1155         return false;
1156     }
1157 
1158 
1159     /++
1160         Always returns false.
1161       +/
1162     override bool dstInEffect(long stdTime) @safe const scope nothrow
1163     {
1164         return false;
1165     }
1166 
1167 
1168     /++
1169         Returns the given hnsecs without changing them at all.
1170 
1171         Params:
1172             stdTime = The UTC time that needs to be adjusted to this time zone's
1173                       time.
1174 
1175         See_Also:
1176             `TimeZone.utcToTZ`
1177       +/
1178     override long utcToTZ(long stdTime) @safe const scope nothrow
1179     {
1180         return stdTime;
1181     }
1182 
1183     @safe unittest
1184     {
1185         assert(UTC().utcToTZ(0) == 0);
1186 
1187         version (Posix)
1188         {
1189             scope(exit) clearTZEnvVar();
1190 
1191             setTZEnvVar("UTC");
1192             import std.datetime.date : Date;
1193             auto std = SysTime(Date(2010, 1, 1));
1194             auto dst = SysTime(Date(2010, 7, 1));
1195             assert(UTC().utcToTZ(std.stdTime) == std.stdTime);
1196             assert(UTC().utcToTZ(dst.stdTime) == dst.stdTime);
1197         }
1198     }
1199 
1200 
1201     /++
1202         Returns the given hnsecs without changing them at all.
1203 
1204         See_Also:
1205             `TimeZone.tzToUTC`
1206 
1207         Params:
1208             adjTime = The time in this time zone that needs to be adjusted to
1209                       UTC time.
1210       +/
1211     override long tzToUTC(long adjTime) @safe const scope nothrow
1212     {
1213         return adjTime;
1214     }
1215 
1216     @safe unittest
1217     {
1218         assert(UTC().tzToUTC(0) == 0);
1219 
1220         version (Posix)
1221         {
1222             scope(exit) clearTZEnvVar();
1223 
1224             setTZEnvVar("UTC");
1225             import std.datetime.date : Date;
1226             auto std = SysTime(Date(2010, 1, 1));
1227             auto dst = SysTime(Date(2010, 7, 1));
1228             assert(UTC().tzToUTC(std.stdTime) == std.stdTime);
1229             assert(UTC().tzToUTC(dst.stdTime) == dst.stdTime);
1230         }
1231     }
1232 
1233 
1234     /++
1235         Returns a $(REF Duration, core,time) of 0.
1236 
1237         Params:
1238             stdTime = The UTC time for which to get the offset from UTC for this
1239                       time zone.
1240       +/
1241     override Duration utcOffsetAt(long stdTime) @safe const scope nothrow
1242     {
1243         return dur!"hnsecs"(0);
1244     }
1245 
1246 
1247 private:
1248 
1249     this() @safe immutable pure
1250     {
1251         super("UTC", "UTC", "UTC");
1252     }
1253 
1254 
1255     static immutable UTC _utc = new immutable(UTC)();
1256 }
1257 
1258 
1259 /++
1260     Represents a time zone with an offset (in minutes, west is negative) from
1261     UTC but no DST.
1262 
1263     It's primarily used as the time zone in the result of
1264     $(REF SysTime,std,datetime,systime)'s `fromISOString`,
1265     `fromISOExtString`, and `fromSimpleString`.
1266 
1267     `name` and `dstName` are always the empty string since this time zone
1268     has no DST, and while it may be meant to represent a time zone which is in
1269     the TZ Database, obviously it's not likely to be following the exact rules
1270     of any of the time zones in the TZ Database, so it makes no sense to set it.
1271   +/
1272 final class SimpleTimeZone : TimeZone
1273 {
1274 public:
1275 
1276     /++
1277         Always returns false.
1278       +/
1279     @property override bool hasDST() @safe const nothrow
1280     {
1281         return false;
1282     }
1283 
1284 
1285     /++
1286         Always returns false.
1287       +/
1288     override bool dstInEffect(long stdTime) @safe const scope nothrow
1289     {
1290         return false;
1291     }
1292 
1293 
1294     /++
1295         Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
1296         in UTC time (i.e. std time) and converts it to this time zone's time.
1297 
1298         Params:
1299             stdTime = The UTC time that needs to be adjusted to this time zone's
1300                       time.
1301       +/
1302     override long utcToTZ(long stdTime) @safe const scope nothrow
1303     {
1304         return stdTime + _utcOffset.total!"hnsecs";
1305     }
1306 
1307     @safe unittest
1308     {
1309         auto west = new immutable SimpleTimeZone(dur!"hours"(-8));
1310         auto east = new immutable SimpleTimeZone(dur!"hours"(8));
1311 
1312         assert(west.utcToTZ(0) == -288_000_000_000L);
1313         assert(east.utcToTZ(0) == 288_000_000_000L);
1314         assert(west.utcToTZ(54_321_234_567_890L) == 54_033_234_567_890L);
1315 
1316         const cstz = west;
1317         assert(cstz.utcToTZ(50002) == west.utcToTZ(50002));
1318     }
1319 
1320 
1321     /++
1322         Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
1323         in this time zone's time and converts it to UTC (i.e. std time).
1324 
1325         Params:
1326             adjTime = The time in this time zone that needs to be adjusted to
1327                       UTC time.
1328       +/
1329     override long tzToUTC(long adjTime) @safe const scope nothrow
1330     {
1331         return adjTime - _utcOffset.total!"hnsecs";
1332     }
1333 
1334     @safe unittest
1335     {
1336         auto west = new immutable SimpleTimeZone(dur!"hours"(-8));
1337         auto east = new immutable SimpleTimeZone(dur!"hours"(8));
1338 
1339         assert(west.tzToUTC(-288_000_000_000L) == 0);
1340         assert(east.tzToUTC(288_000_000_000L) == 0);
1341         assert(west.tzToUTC(54_033_234_567_890L) == 54_321_234_567_890L);
1342 
1343         const cstz = west;
1344         assert(cstz.tzToUTC(20005) == west.tzToUTC(20005));
1345     }
1346 
1347 
1348     /++
1349         Returns utcOffset as a $(REF Duration, core,time).
1350 
1351         Params:
1352             stdTime = The UTC time for which to get the offset from UTC for this
1353                       time zone.
1354       +/
1355     override Duration utcOffsetAt(long stdTime) @safe const scope nothrow
1356     {
1357         return _utcOffset;
1358     }
1359 
1360 
1361     /++
1362         Params:
1363             utcOffset = This time zone's offset from UTC with west of UTC being
1364                         negative (it is added to UTC to get the adjusted time).
1365             stdName   = The `stdName` for this time zone.
1366       +/
1367     this(Duration utcOffset, string stdName = "") @safe immutable pure
1368     {
1369         // FIXME This probably needs to be changed to something like (-12 - 13).
1370         import std.datetime.date : DateTimeException;
1371         import std.exception : enforce;
1372         enforce!DateTimeException(abs(utcOffset) < dur!"minutes"(1440),
1373                                     "Offset from UTC must be within range (-24:00 - 24:00).");
1374         super("", stdName, "");
1375         this._utcOffset = utcOffset;
1376     }
1377 
1378     @safe unittest
1379     {
1380         auto stz = new immutable SimpleTimeZone(dur!"hours"(-8), "PST");
1381         assert(stz.name == "");
1382         assert(stz.stdName == "PST");
1383         assert(stz.dstName == "");
1384         assert(stz.utcOffset == dur!"hours"(-8));
1385     }
1386 
1387 
1388     /++
1389         The amount of time the offset from UTC is (negative is west of UTC,
1390         positive is east).
1391       +/
1392     @property Duration utcOffset() @safe const pure nothrow
1393     {
1394         return _utcOffset;
1395     }
1396 
1397 
1398 package:
1399 
1400     /+
1401         Returns a time zone as a string with an offset from UTC.
1402 
1403         Time zone offsets will be in the form +HHMM or -HHMM.
1404 
1405         Params:
1406             utcOffset = The number of minutes offset from UTC (negative means
1407                         west).
1408       +/
1409     static string toISOString(Duration utcOffset) @safe pure
1410     {
1411         import std.array : appender;
1412         auto w = appender!string();
1413         w.reserve(5);
1414         toISOString(w, utcOffset);
1415         return w.data;
1416     }
1417 
1418     // ditto
1419     static void toISOString(W)(ref W writer, Duration utcOffset)
1420     if (isOutputRange!(W, char))
1421     {
1422         import std.datetime.date : DateTimeException;
1423         import std.exception : enforce;
1424         import std.format.write : formattedWrite;
1425         immutable absOffset = abs(utcOffset);
1426         enforce!DateTimeException(absOffset < dur!"minutes"(1440),
1427                                   "Offset from UTC must be within range (-24:00 - 24:00).");
1428         int hours;
1429         int minutes;
1430         absOffset.split!("hours", "minutes")(hours, minutes);
1431         formattedWrite(
1432             writer,
1433             utcOffset < Duration.zero ? "-%02d%02d" : "+%02d%02d",
1434             hours,
1435             minutes
1436         );
1437     }
1438 
1439     @safe unittest
1440     {
1441         static string testSTZInvalid(Duration offset)
1442         {
1443             return SimpleTimeZone.toISOString(offset);
1444         }
1445 
1446         import std.datetime.date : DateTimeException;
1447         assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(1440)));
1448         assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(-1440)));
1449 
1450         assert(toISOString(dur!"minutes"(0)) == "+0000");
1451         assert(toISOString(dur!"minutes"(1)) == "+0001");
1452         assert(toISOString(dur!"minutes"(10)) == "+0010");
1453         assert(toISOString(dur!"minutes"(59)) == "+0059");
1454         assert(toISOString(dur!"minutes"(60)) == "+0100");
1455         assert(toISOString(dur!"minutes"(90)) == "+0130");
1456         assert(toISOString(dur!"minutes"(120)) == "+0200");
1457         assert(toISOString(dur!"minutes"(480)) == "+0800");
1458         assert(toISOString(dur!"minutes"(1439)) == "+2359");
1459 
1460         assert(toISOString(dur!"minutes"(-1)) == "-0001");
1461         assert(toISOString(dur!"minutes"(-10)) == "-0010");
1462         assert(toISOString(dur!"minutes"(-59)) == "-0059");
1463         assert(toISOString(dur!"minutes"(-60)) == "-0100");
1464         assert(toISOString(dur!"minutes"(-90)) == "-0130");
1465         assert(toISOString(dur!"minutes"(-120)) == "-0200");
1466         assert(toISOString(dur!"minutes"(-480)) == "-0800");
1467         assert(toISOString(dur!"minutes"(-1439)) == "-2359");
1468     }
1469 
1470 
1471     /+
1472         Returns a time zone as a string with an offset from UTC.
1473 
1474         Time zone offsets will be in the form +HH:MM or -HH:MM.
1475 
1476         Params:
1477             utcOffset = The number of minutes offset from UTC (negative means
1478                         west).
1479       +/
1480     static string toISOExtString(Duration utcOffset) @safe pure
1481     {
1482         import std.array : appender;
1483         auto w = appender!string();
1484         w.reserve(6);
1485         toISOExtString(w, utcOffset);
1486         return w.data;
1487     }
1488 
1489     // ditto
1490     static void toISOExtString(W)(ref W writer, Duration utcOffset)
1491     {
1492         import std.datetime.date : DateTimeException;
1493         import std.format.write : formattedWrite;
1494         import std.exception : enforce;
1495 
1496         immutable absOffset = abs(utcOffset);
1497         enforce!DateTimeException(absOffset < dur!"minutes"(1440),
1498                                   "Offset from UTC must be within range (-24:00 - 24:00).");
1499         int hours;
1500         int minutes;
1501         absOffset.split!("hours", "minutes")(hours, minutes);
1502         formattedWrite(
1503             writer,
1504             utcOffset < Duration.zero ? "-%02d:%02d" : "+%02d:%02d",
1505             hours,
1506             minutes
1507         );
1508     }
1509 
1510     @safe unittest
1511     {
1512         static string testSTZInvalid(Duration offset)
1513         {
1514             return SimpleTimeZone.toISOExtString(offset);
1515         }
1516 
1517         import std.datetime.date : DateTimeException;
1518         assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(1440)));
1519         assertThrown!DateTimeException(testSTZInvalid(dur!"minutes"(-1440)));
1520 
1521         assert(toISOExtString(dur!"minutes"(0)) == "+00:00");
1522         assert(toISOExtString(dur!"minutes"(1)) == "+00:01");
1523         assert(toISOExtString(dur!"minutes"(10)) == "+00:10");
1524         assert(toISOExtString(dur!"minutes"(59)) == "+00:59");
1525         assert(toISOExtString(dur!"minutes"(60)) == "+01:00");
1526         assert(toISOExtString(dur!"minutes"(90)) == "+01:30");
1527         assert(toISOExtString(dur!"minutes"(120)) == "+02:00");
1528         assert(toISOExtString(dur!"minutes"(480)) == "+08:00");
1529         assert(toISOExtString(dur!"minutes"(1439)) == "+23:59");
1530 
1531         assert(toISOExtString(dur!"minutes"(-1)) == "-00:01");
1532         assert(toISOExtString(dur!"minutes"(-10)) == "-00:10");
1533         assert(toISOExtString(dur!"minutes"(-59)) == "-00:59");
1534         assert(toISOExtString(dur!"minutes"(-60)) == "-01:00");
1535         assert(toISOExtString(dur!"minutes"(-90)) == "-01:30");
1536         assert(toISOExtString(dur!"minutes"(-120)) == "-02:00");
1537         assert(toISOExtString(dur!"minutes"(-480)) == "-08:00");
1538         assert(toISOExtString(dur!"minutes"(-1439)) == "-23:59");
1539     }
1540 
1541 
1542     /+
1543         Takes a time zone as a string with an offset from UTC and returns a
1544         $(LREF SimpleTimeZone) which matches.
1545 
1546         The accepted formats for time zone offsets are +HH, -HH, +HHMM, and
1547         -HHMM.
1548 
1549         Params:
1550             isoString = A string which represents a time zone in the ISO format.
1551       +/
1552     static immutable(SimpleTimeZone) fromISOString(S)(S isoString) @safe pure
1553         if (isSomeString!S)
1554     {
1555         import std.algorithm.searching : startsWith;
1556         import std.conv : text, to, ConvException;
1557         import std.datetime.date : DateTimeException;
1558         import std.exception : enforce;
1559 
1560         auto whichSign = isoString.startsWith('-', '+');
1561         enforce!DateTimeException(whichSign > 0, text("Invalid ISO String ", isoString));
1562 
1563         isoString = isoString[1 .. $];
1564         auto sign = whichSign == 1 ? -1 : 1;
1565         int hours;
1566         int minutes;
1567 
1568         try
1569         {
1570             // cast to int from uint is used because it checks for
1571             // non digits without extra loops
1572             if (isoString.length == 2)
1573             {
1574                 hours = cast(int) to!uint(isoString);
1575             }
1576             else if (isoString.length == 4)
1577             {
1578                 hours = cast(int) to!uint(isoString[0 .. 2]);
1579                 minutes = cast(int) to!uint(isoString[2 .. 4]);
1580             }
1581             else
1582             {
1583                 throw new DateTimeException(text("Invalid ISO String ", isoString));
1584             }
1585         }
1586         catch (ConvException)
1587         {
1588             throw new DateTimeException(text("Invalid ISO String ", isoString));
1589         }
1590 
1591         enforce!DateTimeException(hours < 24 && minutes < 60, text("Invalid ISO String ", isoString));
1592 
1593         return new immutable SimpleTimeZone(sign * (dur!"hours"(hours) + dur!"minutes"(minutes)));
1594     }
1595 
1596     @safe unittest
1597     {
1598         import core.exception : AssertError;
1599         import std.format : format;
1600 
1601         foreach (str; ["", "Z", "-", "+", "-:", "+:", "-1:", "+1:", "+1", "-1",
1602                        "-24:00", "+24:00", "-24", "+24", "-2400", "+2400",
1603                        "1", "+1", "-1", "+9", "-9",
1604                        "+1:0", "+01:0", "+1:00", "+01:000", "+01:60",
1605                        "-1:0", "-01:0", "-1:00", "-01:000", "-01:60",
1606                        "000", "00000", "0160", "-0160",
1607                        " +08:00", "+ 08:00", "+08 :00", "+08: 00", "+08:00 ",
1608                        " -08:00", "- 08:00", "-08 :00", "-08: 00", "-08:00 ",
1609                        " +0800", "+ 0800", "+08 00", "+08 00", "+0800 ",
1610                        " -0800", "- 0800", "-08 00", "-08 00", "-0800 ",
1611                        "+ab:cd", "+abcd", "+0Z:00", "+Z", "+00Z",
1612                        "-ab:cd", "+abcd", "-0Z:00", "-Z", "-00Z",
1613                        "01:00", "12:00", "23:59"])
1614         {
1615             import std.datetime.date : DateTimeException;
1616             assertThrown!DateTimeException(SimpleTimeZone.fromISOString(str), format("[%s]", str));
1617         }
1618 
1619         static void test(string str, Duration utcOffset, size_t line = __LINE__)
1620         {
1621             if (SimpleTimeZone.fromISOString(str).utcOffset != (new immutable SimpleTimeZone(utcOffset)).utcOffset)
1622                 throw new AssertError("unittest failure", __FILE__, line);
1623         }
1624 
1625         test("+0000", Duration.zero);
1626         test("+0001", minutes(1));
1627         test("+0010", minutes(10));
1628         test("+0059", minutes(59));
1629         test("+0100", hours(1));
1630         test("+0130", hours(1) + minutes(30));
1631         test("+0200", hours(2));
1632         test("+0800", hours(8));
1633         test("+2359", hours(23) + minutes(59));
1634 
1635         test("-0001", minutes(-1));
1636         test("-0010", minutes(-10));
1637         test("-0059", minutes(-59));
1638         test("-0100", hours(-1));
1639         test("-0130", hours(-1) - minutes(30));
1640         test("-0200", hours(-2));
1641         test("-0800", hours(-8));
1642         test("-2359", hours(-23) - minutes(59));
1643 
1644         test("+00", Duration.zero);
1645         test("+01", hours(1));
1646         test("+02", hours(2));
1647         test("+12", hours(12));
1648         test("+23", hours(23));
1649 
1650         test("-00", Duration.zero);
1651         test("-01", hours(-1));
1652         test("-02", hours(-2));
1653         test("-12", hours(-12));
1654         test("-23", hours(-23));
1655     }
1656 
1657     @safe unittest
1658     {
1659         import core.exception : AssertError;
1660         import std.format : format;
1661 
1662         static void test(scope const string isoString, int expectedOffset, size_t line = __LINE__)
1663         {
1664             auto stz = SimpleTimeZone.fromISOExtString(isoString);
1665             if (stz.utcOffset != dur!"minutes"(expectedOffset))
1666                 throw new AssertError(format("unittest failure: wrong offset [%s]", stz.utcOffset), __FILE__, line);
1667 
1668             auto result = SimpleTimeZone.toISOExtString(stz.utcOffset);
1669             if (result != isoString)
1670                 throw new AssertError(format("unittest failure: [%s] != [%s]", result, isoString), __FILE__, line);
1671         }
1672 
1673         test("+00:00", 0);
1674         test("+00:01", 1);
1675         test("+00:10", 10);
1676         test("+00:59", 59);
1677         test("+01:00", 60);
1678         test("+01:30", 90);
1679         test("+02:00", 120);
1680         test("+08:00", 480);
1681         test("+08:00", 480);
1682         test("+23:59", 1439);
1683 
1684         test("-00:01", -1);
1685         test("-00:10", -10);
1686         test("-00:59", -59);
1687         test("-01:00", -60);
1688         test("-01:30", -90);
1689         test("-02:00", -120);
1690         test("-08:00", -480);
1691         test("-08:00", -480);
1692         test("-23:59", -1439);
1693     }
1694 
1695 
1696     /+
1697         Takes a time zone as a string with an offset from UTC and returns a
1698         $(LREF SimpleTimeZone) which matches.
1699 
1700         The accepted formats for time zone offsets are +HH, -HH, +HH:MM, and
1701         -HH:MM.
1702 
1703         Params:
1704             isoExtString = A string which represents a time zone in the ISO format.
1705       +/
1706     static immutable(SimpleTimeZone) fromISOExtString(S)(scope S isoExtString) @safe pure
1707         if (isSomeString!S)
1708     {
1709         import std.algorithm.searching : startsWith;
1710         import std.conv : ConvException, to;
1711         import std.datetime.date : DateTimeException;
1712         import std.exception : enforce;
1713         import std.format : format;
1714         import std.string : indexOf;
1715 
1716         auto whichSign = isoExtString.startsWith('-', '+');
1717         enforce!DateTimeException(whichSign > 0, format("Invalid ISO String: %s", isoExtString));
1718         auto sign = whichSign == 1 ? -1 : 1;
1719 
1720         isoExtString = isoExtString[1 .. $];
1721         enforce!DateTimeException(!isoExtString.empty, format("Invalid ISO String: %s", isoExtString));
1722 
1723         immutable colon = isoExtString.indexOf(':');
1724         S hoursStr;
1725         S minutesStr;
1726         int hours, minutes;
1727 
1728         if (colon != -1)
1729         {
1730             hoursStr = isoExtString[0 .. colon];
1731             minutesStr = isoExtString[colon + 1 .. $];
1732             enforce!DateTimeException(minutesStr.length == 2, format("Invalid ISO String: %s", isoExtString));
1733         }
1734         else
1735         {
1736             hoursStr = isoExtString;
1737         }
1738 
1739         enforce!DateTimeException(hoursStr.length == 2, format("Invalid ISO String: %s", isoExtString));
1740 
1741         try
1742         {
1743             // cast to int from uint is used because it checks for
1744             // non digits without extra loops
1745             hours = cast(int) to!uint(hoursStr);
1746             minutes = cast(int) (minutesStr.empty ? 0 : to!uint(minutesStr));
1747         }
1748         catch (ConvException)
1749         {
1750             throw new DateTimeException(format("Invalid ISO String: %s", isoExtString));
1751         }
1752 
1753         enforce!DateTimeException(hours < 24 && minutes < 60, format("Invalid ISO String: %s", isoExtString));
1754 
1755         return new immutable SimpleTimeZone(sign * (dur!"hours"(hours) + dur!"minutes"(minutes)));
1756     }
1757 
1758     @safe unittest
1759     {
1760         import core.exception : AssertError;
1761         import std.format : format;
1762 
1763         foreach (str; ["", "Z", "-", "+", "-:", "+:", "-1:", "+1:", "+1", "-1",
1764                        "-24:00", "+24:00", "-24", "+24", "-2400", "-2400",
1765                        "1", "+1", "-1", "+9", "-9",
1766                        "+1:0", "+01:0", "+1:00", "+01:000", "+01:60",
1767                        "-1:0", "-01:0", "-1:00", "-01:000", "-01:60",
1768                        "000", "00000", "0160", "-0160",
1769                        " +08:00", "+ 08:00", "+08 :00", "+08: 00", "+08:00 ",
1770                        " -08:00", "- 08:00", "-08 :00", "-08: 00", "-08:00 ",
1771                        " +0800", "+ 0800", "+08 00", "+08 00", "+0800 ",
1772                        " -0800", "- 0800", "-08 00", "-08 00", "-0800 ",
1773                        "+ab:cd", "abcd", "+0Z:00", "+Z", "+00Z",
1774                        "-ab:cd", "abcd", "-0Z:00", "-Z", "-00Z",
1775                        "0100", "1200", "2359"])
1776         {
1777             import std.datetime.date : DateTimeException;
1778             assertThrown!DateTimeException(SimpleTimeZone.fromISOExtString(str), format("[%s]", str));
1779         }
1780 
1781         static void test(string str, Duration utcOffset, size_t line = __LINE__)
1782         {
1783             if (SimpleTimeZone.fromISOExtString(str).utcOffset != (new immutable SimpleTimeZone(utcOffset)).utcOffset)
1784                 throw new AssertError("unittest failure", __FILE__, line);
1785         }
1786 
1787         test("+00:00", Duration.zero);
1788         test("+00:01", minutes(1));
1789         test("+00:10", minutes(10));
1790         test("+00:59", minutes(59));
1791         test("+01:00", hours(1));
1792         test("+01:30", hours(1) + minutes(30));
1793         test("+02:00", hours(2));
1794         test("+08:00", hours(8));
1795         test("+23:59", hours(23) + minutes(59));
1796 
1797         test("-00:01", minutes(-1));
1798         test("-00:10", minutes(-10));
1799         test("-00:59", minutes(-59));
1800         test("-01:00", hours(-1));
1801         test("-01:30", hours(-1) - minutes(30));
1802         test("-02:00", hours(-2));
1803         test("-08:00", hours(-8));
1804         test("-23:59", hours(-23) - minutes(59));
1805 
1806         test("+00", Duration.zero);
1807         test("+01", hours(1));
1808         test("+02", hours(2));
1809         test("+12", hours(12));
1810         test("+23", hours(23));
1811 
1812         test("-00", Duration.zero);
1813         test("-01", hours(-1));
1814         test("-02", hours(-2));
1815         test("-12", hours(-12));
1816         test("-23", hours(-23));
1817     }
1818 
1819     @safe unittest
1820     {
1821         import core.exception : AssertError;
1822         import std.format : format;
1823 
1824         static void test(scope const string isoExtString, int expectedOffset, size_t line = __LINE__)
1825         {
1826             auto stz = SimpleTimeZone.fromISOExtString(isoExtString);
1827             if (stz.utcOffset != dur!"minutes"(expectedOffset))
1828                 throw new AssertError(format("unittest failure: wrong offset [%s]", stz.utcOffset), __FILE__, line);
1829 
1830             auto result = SimpleTimeZone.toISOExtString(stz.utcOffset);
1831             if (result != isoExtString)
1832                 throw new AssertError(format("unittest failure: [%s] != [%s]", result, isoExtString), __FILE__, line);
1833         }
1834 
1835         test("+00:00", 0);
1836         test("+00:01", 1);
1837         test("+00:10", 10);
1838         test("+00:59", 59);
1839         test("+01:00", 60);
1840         test("+01:30", 90);
1841         test("+02:00", 120);
1842         test("+08:00", 480);
1843         test("+08:00", 480);
1844         test("+23:59", 1439);
1845 
1846         test("-00:01", -1);
1847         test("-00:10", -10);
1848         test("-00:59", -59);
1849         test("-01:00", -60);
1850         test("-01:30", -90);
1851         test("-02:00", -120);
1852         test("-08:00", -480);
1853         test("-08:00", -480);
1854         test("-23:59", -1439);
1855     }
1856 
1857 
1858 private:
1859 
1860     immutable Duration _utcOffset;
1861 }
1862 
1863 
1864 /++
1865     Represents a time zone from a TZ Database time zone file. Files from the TZ
1866     Database are how Posix systems hold their time zone information.
1867     Unfortunately, Windows does not use the TZ Database. To use the TZ Database,
1868     use `PosixTimeZone` (which reads its information from the TZ Database
1869     files on disk) on Windows by providing the TZ Database files and telling
1870     `PosixTimeZone.getTimeZone` where the directory holding them is.
1871 
1872     To get a `PosixTimeZone`, call `PosixTimeZone.getTimeZone`
1873     (which allows specifying the location the time zone files).
1874 
1875     Note:
1876         Unless your system's local time zone deals with leap seconds (which is
1877         highly unlikely), then the only way to get a time zone which
1878         takes leap seconds into account is to use `PosixTimeZone` with a
1879         time zone whose name starts with "right/". Those time zone files do
1880         include leap seconds, and `PosixTimeZone` will take them into account
1881         (though posix systems which use a "right/" time zone as their local time
1882         zone will $(I not) take leap seconds into account even though they're
1883         in the file).
1884 
1885     See_Also:
1886         $(HTTP www.iana.org/time-zones, Home of the TZ Database files)<br>
1887         $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ Database)<br>
1888         $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of Time
1889           Zones)
1890   +/
1891 final class PosixTimeZone : TimeZone
1892 {
1893     import std.algorithm.searching : countUntil, canFind, startsWith;
1894     import std.file : isDir, isFile, exists, dirEntries, SpanMode, DirEntry;
1895     import std.path : extension;
1896     import std.stdio : File;
1897     import std.string : strip, representation;
1898     import std.traits : isArray, isSomeChar;
1899 public:
1900 
1901     /++
1902         Whether this time zone has Daylight Savings Time at any point in time.
1903         Note that for some time zone types it may not have DST for current
1904         dates but will still return true for `hasDST` because the time zone
1905         did at some point have DST.
1906       +/
1907     @property override bool hasDST() @safe const nothrow
1908     {
1909         return _hasDST;
1910     }
1911 
1912 
1913     /++
1914         Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
1915         in UTC time (i.e. std time) and returns whether DST is in effect in this
1916         time zone at the given point in time.
1917 
1918         Params:
1919             stdTime = The UTC time that needs to be checked for DST in this time
1920                       zone.
1921       +/
1922     override bool dstInEffect(long stdTime) @safe const scope nothrow
1923     {
1924         assert(!_transitions.empty);
1925 
1926         immutable unixTime = stdTimeToUnixTime(stdTime);
1927         immutable found = countUntil!"b < a.timeT"(_transitions, unixTime);
1928 
1929         if (found == -1)
1930             return _transitions.back.ttInfo.isDST;
1931 
1932         immutable transition = found == 0 ? _transitions[0] : _transitions[found - 1];
1933 
1934         return transition.ttInfo.isDST;
1935     }
1936 
1937 
1938     /++
1939         Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
1940         in UTC time (i.e. std time) and converts it to this time zone's time.
1941 
1942         Params:
1943             stdTime = The UTC time that needs to be adjusted to this time zone's
1944                       time.
1945       +/
1946     override long utcToTZ(long stdTime) @safe const scope nothrow
1947     {
1948         assert(!_transitions.empty);
1949 
1950         immutable leapSecs = calculateLeapSeconds(stdTime);
1951         immutable unixTime = stdTimeToUnixTime(stdTime);
1952         immutable found = countUntil!"b < a.timeT"(_transitions, unixTime);
1953 
1954         if (found == -1)
1955             return stdTime + convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs);
1956 
1957         immutable transition = found == 0 ? _transitions[0] : _transitions[found - 1];
1958 
1959         return stdTime + convert!("seconds", "hnsecs")(transition.ttInfo.utcOffset + leapSecs);
1960     }
1961 
1962 
1963     /++
1964         Takes the number of hnsecs (100 ns) since midnight, January 1st, 1 A.D.
1965         in this time zone's time and converts it to UTC (i.e. std time).
1966 
1967         Params:
1968             adjTime = The time in this time zone that needs to be adjusted to
1969                       UTC time.
1970       +/
1971     override long tzToUTC(long adjTime) @safe const scope nothrow
1972     {
1973         assert(!_transitions.empty, "UTC offset's not available");
1974 
1975         immutable leapSecs = calculateLeapSeconds(adjTime);
1976         time_t unixTime = stdTimeToUnixTime(adjTime);
1977         immutable past = unixTime - convert!("days", "seconds")(1);
1978         immutable future = unixTime + convert!("days", "seconds")(1);
1979 
1980         immutable pastFound = countUntil!"b < a.timeT"(_transitions, past);
1981 
1982         if (pastFound == -1)
1983             return adjTime - convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs);
1984 
1985         immutable futureFound = countUntil!"b < a.timeT"(_transitions[pastFound .. $], future);
1986         immutable pastTrans = pastFound == 0 ? _transitions[0] : _transitions[pastFound - 1];
1987 
1988         if (futureFound == 0)
1989             return adjTime - convert!("seconds", "hnsecs")(pastTrans.ttInfo.utcOffset + leapSecs);
1990 
1991         immutable futureTrans = futureFound == -1 ? _transitions.back
1992                                                   : _transitions[pastFound + futureFound - 1];
1993         immutable pastOffset = pastTrans.ttInfo.utcOffset;
1994 
1995         if (pastOffset < futureTrans.ttInfo.utcOffset)
1996             unixTime -= convert!("hours", "seconds")(1);
1997 
1998         immutable found = countUntil!"b < a.timeT"(_transitions[pastFound .. $], unixTime - pastOffset);
1999 
2000         if (found == -1)
2001             return adjTime - convert!("seconds", "hnsecs")(_transitions.back.ttInfo.utcOffset + leapSecs);
2002 
2003         immutable transition = found == 0 ? pastTrans : _transitions[pastFound + found - 1];
2004 
2005         return adjTime - convert!("seconds", "hnsecs")(transition.ttInfo.utcOffset + leapSecs);
2006     }
2007 
2008 
2009     version (StdDdoc)
2010     {
2011         /++
2012             The default directory where the TZ Database files are stored. It's
2013             empty for Windows, since Windows doesn't have them. You can also use
2014             the TZDatabaseDir version to pass an arbitrary path at compile-time,
2015             rather than hard-coding it here. Android concatenates all time zone
2016             data into a single file called tzdata and stores it in the directory
2017             below.
2018           +/
2019         enum defaultTZDatabaseDir = "";
2020     }
2021     else version (TZDatabaseDir)
2022     {
2023         import std.string : strip;
2024         enum defaultTZDatabaseDir = strip(import("TZDatabaseDirFile"));
2025     }
2026     else version (Android)
2027     {
2028         enum defaultTZDatabaseDir = "/system/usr/share/zoneinfo/";
2029     }
2030     else version (Solaris)
2031     {
2032         enum defaultTZDatabaseDir = "/usr/share/lib/zoneinfo/";
2033     }
2034     else version (Posix)
2035     {
2036         enum defaultTZDatabaseDir = "/usr/share/zoneinfo/";
2037     }
2038     else version (Windows)
2039     {
2040         enum defaultTZDatabaseDir = "";
2041     }
2042 
2043 
2044     /++
2045         Returns a $(LREF TimeZone) with the give name per the TZ Database. The
2046         time zone information is fetched from the TZ Database time zone files in
2047         the given directory.
2048 
2049         See_Also:
2050             $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
2051               Database)<br>
2052             $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of
2053               Time Zones)
2054 
2055         Params:
2056             name          = The TZ Database name of the desired time zone
2057             tzDatabaseDir = The directory where the TZ Database files are
2058                             located. Because these files are not located on
2059                             Windows systems, provide them
2060                             and give their location here to
2061                             use $(LREF PosixTimeZone)s.
2062 
2063         Throws:
2064             $(REF DateTimeException,std,datetime,date) if the given time zone
2065             could not be found or `FileException` if the TZ Database file
2066             could not be opened.
2067       +/
2068     // TODO make it possible for tzDatabaseDir to be gzipped tar file rather than an uncompressed
2069     //      directory.
2070     static immutable(PosixTimeZone) getTimeZone(string name, string tzDatabaseDir = defaultTZDatabaseDir) @trusted
2071     {
2072         import std.algorithm.sorting : sort;
2073         import std.conv : to;
2074         import std.datetime.date : DateTimeException;
2075         import std.exception : enforce;
2076         import std.format : format;
2077         import std.path : asNormalizedPath, chainPath;
2078         import std.range : retro;
2079 
2080         name = strip(name);
2081 
2082         enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir)));
2083         enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir)));
2084 
2085         version (Android)
2086         {
2087             auto tzfileOffset = name in tzdataIndex(tzDatabaseDir);
2088             enforce(tzfileOffset, new DateTimeException(format("The time zone %s is not listed.", name)));
2089             string tzFilename = separate_index ? "zoneinfo.dat" : "tzdata";
2090             const file = asNormalizedPath(chainPath(tzDatabaseDir, tzFilename)).to!string;
2091         }
2092         else
2093             const file = asNormalizedPath(chainPath(tzDatabaseDir, name)).to!string;
2094 
2095         enforce(file.exists(), new DateTimeException(format("File %s does not exist.", file)));
2096         enforce(file.isFile, new DateTimeException(format("%s is not a file.", file)));
2097 
2098         auto tzFile = File(file);
2099         version (Android) tzFile.seek(*tzfileOffset);
2100         immutable gmtZone = name.representation().canFind("GMT");
2101 
2102         import std.datetime.date : DateTimeException;
2103         try
2104         {
2105             _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif");
2106 
2107             immutable char tzFileVersion = readVal!char(tzFile);
2108             _enforceValidTZFile(tzFileVersion == '\0' || tzFileVersion == '2' || tzFileVersion == '3');
2109 
2110             {
2111                 auto zeroBlock = readVal!(ubyte[])(tzFile, 15);
2112                 bool allZeroes = true;
2113 
2114                 foreach (val; zeroBlock)
2115                 {
2116                     if (val != 0)
2117                     {
2118                         allZeroes = false;
2119                         break;
2120                     }
2121                 }
2122 
2123                 _enforceValidTZFile(allZeroes);
2124             }
2125 
2126 
2127             // The number of UTC/local indicators stored in the file.
2128             auto tzh_ttisgmtcnt = readVal!int(tzFile);
2129 
2130             // The number of standard/wall indicators stored in the file.
2131             auto tzh_ttisstdcnt = readVal!int(tzFile);
2132 
2133             // The number of leap seconds for which data is stored in the file.
2134             auto tzh_leapcnt = readVal!int(tzFile);
2135 
2136             // The number of "transition times" for which data is stored in the file.
2137             auto tzh_timecnt = readVal!int(tzFile);
2138 
2139             // The number of "local time types" for which data is stored in the file (must not be zero).
2140             auto tzh_typecnt = readVal!int(tzFile);
2141             _enforceValidTZFile(tzh_typecnt != 0);
2142 
2143             // The number of characters of "timezone abbreviation strings" stored in the file.
2144             auto tzh_charcnt = readVal!int(tzFile);
2145 
2146             // time_ts where DST transitions occur.
2147             auto transitionTimeTs = new long[](tzh_timecnt);
2148             foreach (ref transition; transitionTimeTs)
2149                 transition = readVal!int(tzFile);
2150 
2151             // Indices into ttinfo structs indicating the changes
2152             // to be made at the corresponding DST transition.
2153             auto ttInfoIndices = new ubyte[](tzh_timecnt);
2154             foreach (ref ttInfoIndex; ttInfoIndices)
2155                 ttInfoIndex = readVal!ubyte(tzFile);
2156 
2157             // ttinfos which give info on DST transitions.
2158             auto tempTTInfos = new TempTTInfo[](tzh_typecnt);
2159             foreach (ref ttInfo; tempTTInfos)
2160                 ttInfo = readVal!TempTTInfo(tzFile);
2161 
2162             // The array of time zone abbreviation characters.
2163             auto tzAbbrevChars = readVal!(char[])(tzFile, tzh_charcnt);
2164 
2165             auto leapSeconds = new LeapSecond[](tzh_leapcnt);
2166             foreach (ref leapSecond; leapSeconds)
2167             {
2168                 // The time_t when the leap second occurs.
2169                 auto timeT = readVal!int(tzFile);
2170 
2171                 // The total number of leap seconds to be applied after
2172                 // the corresponding leap second.
2173                 auto total = readVal!int(tzFile);
2174 
2175                 leapSecond = LeapSecond(timeT, total);
2176             }
2177 
2178             // Indicate whether each corresponding DST transition were specified
2179             // in standard time or wall clock time.
2180             auto transitionIsStd = new bool[](tzh_ttisstdcnt);
2181             foreach (ref isStd; transitionIsStd)
2182                 isStd = readVal!bool(tzFile);
2183 
2184             // Indicate whether each corresponding DST transition associated with
2185             // local time types are specified in UTC or local time.
2186             auto transitionInUTC = new bool[](tzh_ttisgmtcnt);
2187             foreach (ref inUTC; transitionInUTC)
2188                 inUTC = readVal!bool(tzFile);
2189 
2190             _enforceValidTZFile(!tzFile.eof);
2191 
2192             // If version 2 or 3, the information is duplicated in 64-bit.
2193             if (tzFileVersion == '2' || tzFileVersion == '3')
2194             {
2195                 _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif");
2196 
2197                 immutable char tzFileVersion2 = readVal!(char)(tzFile);
2198                 _enforceValidTZFile(tzFileVersion2 == '2' || tzFileVersion2 == '3');
2199 
2200                 {
2201                     auto zeroBlock = readVal!(ubyte[])(tzFile, 15);
2202                     bool allZeroes = true;
2203 
2204                     foreach (val; zeroBlock)
2205                     {
2206                         if (val != 0)
2207                         {
2208                             allZeroes = false;
2209                             break;
2210                         }
2211                     }
2212 
2213                     _enforceValidTZFile(allZeroes);
2214                 }
2215 
2216 
2217                 // The number of UTC/local indicators stored in the file.
2218                 tzh_ttisgmtcnt = readVal!int(tzFile);
2219 
2220                 // The number of standard/wall indicators stored in the file.
2221                 tzh_ttisstdcnt = readVal!int(tzFile);
2222 
2223                 // The number of leap seconds for which data is stored in the file.
2224                 tzh_leapcnt = readVal!int(tzFile);
2225 
2226                 // The number of "transition times" for which data is stored in the file.
2227                 tzh_timecnt = readVal!int(tzFile);
2228 
2229                 // The number of "local time types" for which data is stored in the file (must not be zero).
2230                 tzh_typecnt = readVal!int(tzFile);
2231                 _enforceValidTZFile(tzh_typecnt != 0);
2232 
2233                 // The number of characters of "timezone abbreviation strings" stored in the file.
2234                 tzh_charcnt = readVal!int(tzFile);
2235 
2236                 // time_ts where DST transitions occur.
2237                 transitionTimeTs = new long[](tzh_timecnt);
2238                 foreach (ref transition; transitionTimeTs)
2239                     transition = readVal!long(tzFile);
2240 
2241                 // Indices into ttinfo structs indicating the changes
2242                 // to be made at the corresponding DST transition.
2243                 ttInfoIndices = new ubyte[](tzh_timecnt);
2244                 foreach (ref ttInfoIndex; ttInfoIndices)
2245                     ttInfoIndex = readVal!ubyte(tzFile);
2246 
2247                 // ttinfos which give info on DST transitions.
2248                 tempTTInfos = new TempTTInfo[](tzh_typecnt);
2249                 foreach (ref ttInfo; tempTTInfos)
2250                     ttInfo = readVal!TempTTInfo(tzFile);
2251 
2252                 // The array of time zone abbreviation characters.
2253                 tzAbbrevChars = readVal!(char[])(tzFile, tzh_charcnt);
2254 
2255                 leapSeconds = new LeapSecond[](tzh_leapcnt);
2256                 foreach (ref leapSecond; leapSeconds)
2257                 {
2258                     // The time_t when the leap second occurs.
2259                     auto timeT = readVal!long(tzFile);
2260 
2261                     // The total number of leap seconds to be applied after
2262                     // the corresponding leap second.
2263                     auto total = readVal!int(tzFile);
2264 
2265                     leapSecond = LeapSecond(timeT, total);
2266                 }
2267 
2268                 // Indicate whether each corresponding DST transition were specified
2269                 // in standard time or wall clock time.
2270                 transitionIsStd = new bool[](tzh_ttisstdcnt);
2271                 foreach (ref isStd; transitionIsStd)
2272                     isStd = readVal!bool(tzFile);
2273 
2274                 // Indicate whether each corresponding DST transition associated with
2275                 // local time types are specified in UTC or local time.
2276                 transitionInUTC = new bool[](tzh_ttisgmtcnt);
2277                 foreach (ref inUTC; transitionInUTC)
2278                     inUTC = readVal!bool(tzFile);
2279             }
2280 
2281             _enforceValidTZFile(tzFile.readln().strip().empty);
2282 
2283             cast(void) tzFile.readln();
2284 
2285             version (Android)
2286             {
2287                 // Android uses a single file for all timezone data, so the file
2288                 // doesn't end here.
2289             }
2290             else
2291             {
2292                 _enforceValidTZFile(tzFile.readln().strip().empty);
2293                 _enforceValidTZFile(tzFile.eof);
2294             }
2295 
2296 
2297             auto transitionTypes = new TransitionType*[](tempTTInfos.length);
2298 
2299             foreach (i, ref ttype; transitionTypes)
2300             {
2301                 bool isStd = false;
2302 
2303                 if (i < transitionIsStd.length && !transitionIsStd.empty)
2304                     isStd = transitionIsStd[i];
2305 
2306                 bool inUTC = false;
2307 
2308                 if (i < transitionInUTC.length && !transitionInUTC.empty)
2309                     inUTC = transitionInUTC[i];
2310 
2311                 ttype = new TransitionType(isStd, inUTC);
2312             }
2313 
2314             auto ttInfos = new immutable(TTInfo)*[](tempTTInfos.length);
2315             foreach (i, ref ttInfo; ttInfos)
2316             {
2317                 auto tempTTInfo = tempTTInfos[i];
2318 
2319                 if (gmtZone)
2320                     tempTTInfo.tt_gmtoff = -tempTTInfo.tt_gmtoff;
2321 
2322                 auto abbrevChars = tzAbbrevChars[tempTTInfo.tt_abbrind .. $];
2323                 string abbrev = abbrevChars[0 .. abbrevChars.countUntil('\0')].idup;
2324 
2325                 ttInfo = new immutable(TTInfo)(tempTTInfos[i], abbrev);
2326             }
2327 
2328             auto tempTransitions = new TempTransition[](transitionTimeTs.length);
2329             foreach (i, ref tempTransition; tempTransitions)
2330             {
2331                 immutable ttiIndex = ttInfoIndices[i];
2332                 auto transitionTimeT = transitionTimeTs[i];
2333                 auto ttype = transitionTypes[ttiIndex];
2334                 auto ttInfo = ttInfos[ttiIndex];
2335 
2336                 tempTransition = TempTransition(transitionTimeT, ttInfo, ttype);
2337             }
2338 
2339             if (tempTransitions.empty)
2340             {
2341                 _enforceValidTZFile(ttInfos.length == 1 && transitionTypes.length == 1);
2342                 tempTransitions ~= TempTransition(0, ttInfos[0], transitionTypes[0]);
2343             }
2344 
2345             sort!"a.timeT < b.timeT"(tempTransitions);
2346             sort!"a.timeT < b.timeT"(leapSeconds);
2347 
2348             auto transitions = new Transition[](tempTransitions.length);
2349             foreach (i, ref transition; transitions)
2350             {
2351                 auto tempTransition = tempTransitions[i];
2352                 auto transitionTimeT = tempTransition.timeT;
2353                 auto ttInfo = tempTransition.ttInfo;
2354 
2355                 _enforceValidTZFile(i == 0 || transitionTimeT > tempTransitions[i - 1].timeT);
2356 
2357                 transition = Transition(transitionTimeT, ttInfo);
2358             }
2359 
2360             string stdName;
2361             string dstName;
2362             bool hasDST = false;
2363 
2364             foreach (transition; retro(transitions))
2365             {
2366                 auto ttInfo = transition.ttInfo;
2367 
2368                 if (ttInfo.isDST)
2369                 {
2370                     if (dstName.empty)
2371                         dstName = ttInfo.abbrev;
2372                     hasDST = true;
2373                 }
2374                 else
2375                 {
2376                     if (stdName.empty)
2377                         stdName = ttInfo.abbrev;
2378                 }
2379 
2380                 if (!stdName.empty && !dstName.empty)
2381                     break;
2382             }
2383 
2384             return new immutable PosixTimeZone(transitions.idup, leapSeconds.idup, name, stdName, dstName, hasDST);
2385         }
2386         catch (DateTimeException dte)
2387             throw dte;
2388         catch (Exception e)
2389             throw new DateTimeException("Not a valid TZ data file", __FILE__, __LINE__, e);
2390     }
2391 
2392     ///
2393     @safe unittest
2394     {
2395         version (Posix)
2396         {
2397             auto tz = PosixTimeZone.getTimeZone("America/Los_Angeles");
2398 
2399             assert(tz.name == "America/Los_Angeles");
2400             assert(tz.stdName == "PST");
2401             assert(tz.dstName == "PDT");
2402         }
2403     }
2404 
2405     /++
2406         Returns a list of the names of the time zones installed on the system.
2407 
2408         Providing a sub-name narrows down the list of time zones (which
2409         can number in the thousands). For example,
2410         passing in "America" as the sub-name returns only the time zones which
2411         begin with "America".
2412 
2413         Params:
2414             subName       = The first part of the desired time zones.
2415             tzDatabaseDir = The directory where the TZ Database files are
2416                             located.
2417 
2418         Throws:
2419             `FileException` if it fails to read from disk.
2420       +/
2421     static string[] getInstalledTZNames(string subName = "", string tzDatabaseDir = defaultTZDatabaseDir) @safe
2422     {
2423         import std.algorithm.sorting : sort;
2424         import std.array : appender;
2425         import std.exception : enforce;
2426         import std.format : format;
2427 
2428         version (Posix)
2429             subName = strip(subName);
2430         else version (Windows)
2431         {
2432             import std.array : replace;
2433             import std.path : dirSeparator;
2434             subName = replace(strip(subName), "/", dirSeparator);
2435         }
2436 
2437         import std.datetime.date : DateTimeException;
2438         enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir)));
2439         enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir)));
2440 
2441         auto timezones = appender!(string[])();
2442 
2443         version (Android)
2444         {
2445             import std.algorithm.iteration : filter;
2446             import std.algorithm.mutation : copy;
2447 
2448             const index = () @trusted { return tzdataIndex(tzDatabaseDir); }();
2449             index.byKey.filter!(a => a.startsWith(subName)).copy(timezones);
2450         }
2451         else
2452         {
2453             import std.path : baseName;
2454             // dirEntries is @system because it uses a DirIterator with a
2455             // RefCounted variable, but here, no references to the payload is
2456             // escaped to the outside, so this should be @trusted
2457             () @trusted {
2458                 foreach (DirEntry de; dirEntries(tzDatabaseDir, SpanMode.depth))
2459                 {
2460                     if (de.isFile)
2461                     {
2462                         auto tzName = de.name[tzDatabaseDir.length .. $];
2463 
2464                         if (!tzName.extension().empty ||
2465                             !tzName.startsWith(subName) ||
2466                             baseName(tzName) == "leapseconds" ||
2467                             tzName == "+VERSION" ||
2468                             tzName == "SECURITY")
2469                         {
2470                             continue;
2471                         }
2472 
2473                         timezones.put(tzName);
2474                     }
2475                 }
2476             }();
2477         }
2478 
2479         sort(timezones.data);
2480 
2481         return timezones.data;
2482     }
2483 
2484     version (Posix) @system unittest
2485     {
2486         import std.exception : assertNotThrown;
2487         import std.stdio : writefln;
2488         static void testPTZSuccess(string tzName)
2489         {
2490             scope(failure) writefln("TZName which threw: %s", tzName);
2491 
2492             PosixTimeZone.getTimeZone(tzName);
2493         }
2494 
2495         static void testPTZFailure(string tzName)
2496         {
2497             scope(success) writefln("TZName which was supposed to throw: %s", tzName);
2498 
2499             PosixTimeZone.getTimeZone(tzName);
2500         }
2501 
2502         auto tzNames = getInstalledTZNames();
2503 
2504         import std.datetime.date : DateTimeException;
2505         foreach (tzName; tzNames)
2506             assertNotThrown!DateTimeException(testPTZSuccess(tzName));
2507 
2508         // No timezone directories on Android, just a single tzdata file
2509         version (Android)
2510         {}
2511         else
2512         {
2513             foreach (DirEntry de; dirEntries(defaultTZDatabaseDir, SpanMode.depth))
2514             {
2515                 if (de.isFile)
2516                 {
2517                     auto tzName = de.name[defaultTZDatabaseDir.length .. $];
2518 
2519                     if (!canFind(tzNames, tzName))
2520                         assertThrown!DateTimeException(testPTZFailure(tzName));
2521                 }
2522             }
2523         }
2524     }
2525 
2526 
2527 private:
2528 
2529     /+
2530         Holds information on when a time transition occures (usually a
2531         transition to or from DST) as well as a pointer to the `TTInfo` which
2532         holds information on the utc offset past the transition.
2533       +/
2534     struct Transition
2535     {
2536         this(long timeT, immutable (TTInfo)* ttInfo) @safe pure
2537         {
2538             this.timeT = timeT;
2539             this.ttInfo = ttInfo;
2540         }
2541 
2542         long    timeT;
2543         immutable (TTInfo)* ttInfo;
2544     }
2545 
2546 
2547     /+
2548         Holds information on when a leap second occurs.
2549       +/
2550     struct LeapSecond
2551     {
2552         this(long timeT, int total) @safe pure
2553         {
2554             this.timeT = timeT;
2555             this.total = total;
2556         }
2557 
2558         long timeT;
2559         int total;
2560     }
2561 
2562     /+
2563         Holds information on the utc offset after a transition as well as
2564         whether DST is in effect after that transition.
2565       +/
2566     struct TTInfo
2567     {
2568         this(scope const TempTTInfo tempTTInfo, string abbrev) @safe immutable pure
2569         {
2570             utcOffset = tempTTInfo.tt_gmtoff;
2571             isDST = tempTTInfo.tt_isdst;
2572             this.abbrev = abbrev;
2573         }
2574 
2575         immutable int    utcOffset;  // Offset from UTC.
2576         immutable bool   isDST;      // Whether DST is in effect.
2577         immutable string abbrev;     // The current abbreviation for the time zone.
2578     }
2579 
2580 
2581     /+
2582         Struct used to hold information relating to `TTInfo` while organizing
2583         the time zone information prior to putting it in its final form.
2584       +/
2585     struct TempTTInfo
2586     {
2587         this(int gmtOff, bool isDST, ubyte abbrInd) @safe pure
2588         {
2589             tt_gmtoff = gmtOff;
2590             tt_isdst = isDST;
2591             tt_abbrind = abbrInd;
2592         }
2593 
2594         int   tt_gmtoff;
2595         bool  tt_isdst;
2596         ubyte tt_abbrind;
2597     }
2598 
2599 
2600     /+
2601         Struct used to hold information relating to `Transition` while
2602         organizing the time zone information prior to putting it in its final
2603         form.
2604       +/
2605     struct TempTransition
2606     {
2607         this(long timeT, immutable (TTInfo)* ttInfo, TransitionType* ttype) @safe pure
2608         {
2609             this.timeT = timeT;
2610             this.ttInfo = ttInfo;
2611             this.ttype = ttype;
2612         }
2613 
2614         long                timeT;
2615         immutable (TTInfo)* ttInfo;
2616         TransitionType*     ttype;
2617     }
2618 
2619 
2620     /+
2621         Struct used to hold information relating to `Transition` and
2622         `TTInfo` while organizing the time zone information prior to putting
2623         it in its final form.
2624       +/
2625     struct TransitionType
2626     {
2627         this(bool isStd, bool inUTC) @safe pure
2628         {
2629             this.isStd = isStd;
2630             this.inUTC = inUTC;
2631         }
2632 
2633         // Whether the transition is in std time (as opposed to wall clock time).
2634         bool isStd;
2635 
2636         // Whether the transition is in UTC (as opposed to local time).
2637         bool inUTC;
2638     }
2639 
2640 
2641     /+
2642         Reads an int from a TZ file.
2643       +/
2644     static T readVal(T)(ref File tzFile) @trusted
2645         if ((isIntegral!T || isSomeChar!T) || is(immutable T == immutable bool))
2646     {
2647         import std.bitmanip : bigEndianToNative;
2648         T[1] buff;
2649 
2650         _enforceValidTZFile(!tzFile.eof);
2651         tzFile.rawRead(buff);
2652 
2653         return bigEndianToNative!T(cast(ubyte[T.sizeof]) buff);
2654     }
2655 
2656     /+
2657         Reads an array of values from a TZ file.
2658       +/
2659     static T readVal(T)(ref File tzFile, size_t length) @trusted
2660         if (isArray!T)
2661     {
2662         auto buff = new T(length);
2663 
2664         _enforceValidTZFile(!tzFile.eof);
2665         tzFile.rawRead(buff);
2666 
2667         return buff;
2668     }
2669 
2670 
2671     /+
2672         Reads a `TempTTInfo` from a TZ file.
2673       +/
2674     static T readVal(T)(ref File tzFile) @safe
2675         if (is(T == TempTTInfo))
2676     {
2677         return TempTTInfo(readVal!int(tzFile),
2678                           readVal!bool(tzFile),
2679                           readVal!ubyte(tzFile));
2680     }
2681 
2682 
2683     /+
2684         Throws:
2685             $(REF DateTimeException,std,datetime,date) if `result` is false.
2686       +/
2687     static void _enforceValidTZFile(bool result, size_t line = __LINE__) @safe pure
2688     {
2689         import std.datetime.date : DateTimeException;
2690         if (!result)
2691             throw new DateTimeException("Not a valid tzdata file.", __FILE__, line);
2692     }
2693 
2694 
2695     int calculateLeapSeconds(long stdTime) @safe const scope pure nothrow
2696     {
2697         if (_leapSeconds.empty)
2698             return 0;
2699 
2700         immutable unixTime = stdTimeToUnixTime(stdTime);
2701 
2702         if (_leapSeconds.front.timeT >= unixTime)
2703             return 0;
2704 
2705         immutable found = countUntil!"b < a.timeT"(_leapSeconds, unixTime);
2706 
2707         if (found == -1)
2708             return _leapSeconds.back.total;
2709 
2710         immutable leapSecond = found == 0 ? _leapSeconds[0] : _leapSeconds[found - 1];
2711 
2712         return leapSecond.total;
2713     }
2714 
2715 
2716     this(immutable Transition[] transitions,
2717          immutable LeapSecond[] leapSeconds,
2718          string name,
2719          string stdName,
2720          string dstName,
2721          bool hasDST) @safe immutable pure
2722     {
2723         if (dstName.empty && !stdName.empty)
2724             dstName = stdName;
2725         else if (stdName.empty && !dstName.empty)
2726             stdName = dstName;
2727 
2728         super(name, stdName, dstName);
2729 
2730         if (!transitions.empty)
2731         {
2732             foreach (i, transition; transitions[0 .. $-1])
2733                 _enforceValidTZFile(transition.timeT < transitions[i + 1].timeT);
2734         }
2735 
2736         foreach (i, leapSecond; leapSeconds)
2737             _enforceValidTZFile(i == leapSeconds.length - 1 || leapSecond.timeT < leapSeconds[i + 1].timeT);
2738 
2739         _transitions = transitions;
2740         _leapSeconds = leapSeconds;
2741         _hasDST = hasDST;
2742     }
2743 
2744     // Android concatenates the usual timezone directories into a single file,
2745     // tzdata, along with an index to jump to each timezone's offset.  In older
2746     // versions of Android, the index was stored in a separate file, zoneinfo.idx,
2747     // whereas now it's stored at the beginning of tzdata.
2748     version (Android)
2749     {
2750         // Keep track of whether there's a separate index, zoneinfo.idx.  Only
2751         // check this after calling tzdataIndex, as it's initialized there.
2752         static shared bool separate_index;
2753 
2754         // Extracts the name of each time zone and the offset where its data is
2755         // located in the tzdata file from the index and caches it for later.
2756         static const(uint[string]) tzdataIndex(string tzDir)
2757         {
2758             import std.concurrency : initOnce;
2759 
2760             __gshared uint[string] _tzIndex;
2761 
2762             // _tzIndex is initialized once and then shared across all threads.
2763             initOnce!_tzIndex(
2764             {
2765                 import std.conv : to;
2766                 import std.datetime.date : DateTimeException;
2767                 import std.format : format;
2768                 import std.path : asNormalizedPath, chainPath;
2769 
2770                 enum indexEntrySize = 52;
2771                 const combinedFile = asNormalizedPath(chainPath(tzDir, "tzdata")).to!string;
2772                 const indexFile = asNormalizedPath(chainPath(tzDir, "zoneinfo.idx")).to!string;
2773                 File tzFile;
2774                 uint indexEntries, dataOffset;
2775                 uint[string] initIndex;
2776 
2777                 // Check for the combined file tzdata, which stores the index
2778                 // and the time zone data together.
2779                 if (combinedFile.exists() && combinedFile.isFile)
2780                 {
2781                     tzFile = File(combinedFile);
2782                     _enforceValidTZFile(readVal!(char[])(tzFile, 6) == "tzdata");
2783                     auto tzDataVersion = readVal!(char[])(tzFile, 6);
2784                     _enforceValidTZFile(tzDataVersion[5] == '\0');
2785 
2786                     uint indexOffset = readVal!uint(tzFile);
2787                     dataOffset = readVal!uint(tzFile);
2788                     readVal!uint(tzFile);
2789 
2790                     indexEntries = (dataOffset - indexOffset) / indexEntrySize;
2791                     separate_index = false;
2792                 }
2793                 else if (indexFile.exists() && indexFile.isFile)
2794                 {
2795                     tzFile = File(indexFile);
2796                     indexEntries = to!uint(tzFile.size/indexEntrySize);
2797                     separate_index = true;
2798                 }
2799                 else
2800                 {
2801                     throw new DateTimeException(format("Both timezone files %s and %s do not exist.",
2802                                                        combinedFile, indexFile));
2803                 }
2804 
2805                 foreach (_; 0 .. indexEntries)
2806                 {
2807                     string tzName = to!string(readVal!(char[])(tzFile, 40).ptr);
2808                     uint tzOffset = readVal!uint(tzFile);
2809                     readVal!(uint[])(tzFile, 2);
2810                     initIndex[tzName] = dataOffset + tzOffset;
2811                 }
2812                 initIndex.rehash;
2813                 return initIndex;
2814             }());
2815             return _tzIndex;
2816         }
2817     }
2818 
2819     // List of times when the utc offset changes.
2820     immutable Transition[] _transitions;
2821 
2822     // List of leap second occurrences.
2823     immutable LeapSecond[] _leapSeconds;
2824 
2825     // Whether DST is in effect for this time zone at any point in time.
2826     immutable bool _hasDST;
2827 }
2828 
2829 
2830 version (StdDdoc)
2831 {
2832     /++
2833         $(BLUE This class is Windows-Only.)
2834 
2835         Represents a time zone from the Windows registry. Unfortunately, Windows
2836         does not use the TZ Database. To use the TZ Database, use
2837         $(LREF PosixTimeZone) (which reads its information from the TZ Database
2838         files on disk) on Windows by providing the TZ Database files and telling
2839         `PosixTimeZone.getTimeZone` where the directory holding them is.
2840 
2841         The TZ Database files and Windows' time zone information frequently
2842         do not match. Windows has many errors with regards to when DST switches
2843         occur (especially for historical dates). Also, the TZ Database files
2844         include far more time zones than Windows does. So, for accurate
2845         time zone information, use the TZ Database files with
2846         $(LREF PosixTimeZone) rather than `WindowsTimeZone`. However, because
2847         `WindowsTimeZone` uses Windows system calls to deal with the time,
2848         it's far more likely to match the behavior of other Windows programs.
2849         Be aware of the differences when selecting a method.
2850 
2851         `WindowsTimeZone` does not exist on Posix systems.
2852 
2853         To get a `WindowsTimeZone`, call `WindowsTimeZone.getTimeZone`.
2854 
2855         See_Also:
2856             $(HTTP www.iana.org/time-zones, Home of the TZ Database files)
2857       +/
2858     final class WindowsTimeZone : TimeZone
2859     {
2860     public:
2861 
2862         /++
2863             Whether this time zone has Daylight Savings Time at any point in
2864             time. Note that for some time zone types it may not have DST for
2865             current dates but will still return true for `hasDST` because the
2866             time zone did at some point have DST.
2867           +/
2868         @property override bool hasDST() @safe const scope nothrow;
2869 
2870 
2871         /++
2872             Takes the number of hnsecs (100 ns) since midnight, January 1st,
2873             1 A.D. in UTC time (i.e. std time) and returns whether DST is in
2874             effect in this time zone at the given point in time.
2875 
2876             Params:
2877                 stdTime = The UTC time that needs to be checked for DST in this
2878                           time zone.
2879           +/
2880         override bool dstInEffect(long stdTime) @safe const scope nothrow;
2881 
2882 
2883         /++
2884             Takes the number of hnsecs (100 ns) since midnight, January 1st,
2885             1 A.D. in UTC time (i.e. std time) and converts it to this time
2886                 zone's time.
2887 
2888             Params:
2889                 stdTime = The UTC time that needs to be adjusted to this time
2890                           zone's time.
2891           +/
2892         override long utcToTZ(long stdTime) @safe const scope nothrow;
2893 
2894 
2895         /++
2896             Takes the number of hnsecs (100 ns) since midnight, January 1st,
2897             1 A.D. in this time zone's time and converts it to UTC (i.e. std
2898             time).
2899 
2900             Params:
2901                 adjTime = The time in this time zone that needs to be adjusted
2902                           to UTC time.
2903           +/
2904         override long tzToUTC(long adjTime) @safe const scope nothrow;
2905 
2906 
2907         /++
2908             Returns a $(LREF TimeZone) with the given name per the Windows time
2909             zone names. The time zone information is fetched from the Windows
2910             registry.
2911 
2912             See_Also:
2913                 $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ
2914                   Database)<br>
2915                 $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List
2916                   of Time Zones)
2917 
2918             Params:
2919                 name = The TZ Database name of the desired time zone.
2920 
2921             Throws:
2922                 $(REF DateTimeException,std,datetime,date) if the given time
2923                 zone could not be found.
2924 
2925             Example:
2926     --------------------
2927     auto tz = WindowsTimeZone.getTimeZone("Pacific Standard Time");
2928     --------------------
2929           +/
2930         static immutable(WindowsTimeZone) getTimeZone(string name) @safe;
2931 
2932 
2933         /++
2934             Returns a list of the names of the time zones installed on the
2935             system. The list returned by WindowsTimeZone contains the Windows
2936             TZ names, not the TZ Database names. However,
2937             `TimeZone.getinstalledTZNames` will return the TZ Database names
2938             which are equivalent to the Windows TZ names.
2939           +/
2940         static string[] getInstalledTZNames() @safe;
2941 
2942     private:
2943 
2944         version (Windows)
2945         {}
2946         else
2947             alias TIME_ZONE_INFORMATION = void*;
2948 
2949         static bool _dstInEffect(const scope TIME_ZONE_INFORMATION* tzInfo, long stdTime) nothrow;
2950         static long _utcToTZ(const scope TIME_ZONE_INFORMATION* tzInfo, long stdTime, bool hasDST) nothrow;
2951         static long _tzToUTC(const scope TIME_ZONE_INFORMATION* tzInfo, long adjTime, bool hasDST) nothrow;
2952 
2953         this() immutable pure
2954         {
2955             super("", "", "");
2956         }
2957     }
2958 
2959 }
2960 else version (Windows)
2961 {
2962     final class WindowsTimeZone : TimeZone
2963     {
2964         import std.algorithm.sorting : sort;
2965         import std.array : appender;
2966         import std.conv : to;
2967         import std.format : format;
2968 
2969     public:
2970 
2971         @property override bool hasDST() @safe const scope nothrow
2972         {
2973             return _tzInfo.DaylightDate.wMonth != 0;
2974         }
2975 
2976 
2977         override bool dstInEffect(long stdTime) @safe const scope nothrow
2978         {
2979             return _dstInEffect(&_tzInfo, stdTime);
2980         }
2981 
2982 
2983         override long utcToTZ(long stdTime) @safe const scope nothrow
2984         {
2985             return _utcToTZ(&_tzInfo, stdTime, hasDST);
2986         }
2987 
2988 
2989         override long tzToUTC(long adjTime) @safe const scope nothrow
2990         {
2991             return _tzToUTC(&_tzInfo, adjTime, hasDST);
2992         }
2993 
2994 
2995         static immutable(WindowsTimeZone) getTimeZone(string name) @trusted
2996         {
2997             scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`);
2998 
2999             foreach (tzKeyName; baseKey.keyNames)
3000             {
3001                 if (tzKeyName != name)
3002                     continue;
3003 
3004                 scope tzKey = baseKey.getKey(tzKeyName);
3005 
3006                 scope stdVal = tzKey.getValue("Std");
3007                 auto stdName = stdVal.value_SZ;
3008 
3009                 scope dstVal = tzKey.getValue("Dlt");
3010                 auto dstName = dstVal.value_SZ;
3011 
3012                 scope tziVal = tzKey.getValue("TZI");
3013                 auto binVal = tziVal.value_BINARY;
3014                 assert(binVal.length == REG_TZI_FORMAT.sizeof,
3015                         "Unexpected size while getTimeZone with name " ~ name);
3016                 auto tziFmt = cast(REG_TZI_FORMAT*) binVal.ptr;
3017 
3018                 TIME_ZONE_INFORMATION tzInfo;
3019 
3020                 auto wstdName = stdName.to!wstring;
3021                 auto wdstName = dstName.to!wstring;
3022                 auto wstdNameLen = wstdName.length > 32 ? 32 : wstdName.length;
3023                 auto wdstNameLen = wdstName.length > 32 ? 32 : wdstName.length;
3024 
3025                 tzInfo.Bias = tziFmt.Bias;
3026                 tzInfo.StandardName[0 .. wstdNameLen] = wstdName[0 .. wstdNameLen];
3027                 tzInfo.StandardName[wstdNameLen .. $] = '\0';
3028                 tzInfo.StandardDate = tziFmt.StandardDate;
3029                 tzInfo.StandardBias = tziFmt.StandardBias;
3030                 tzInfo.DaylightName[0 .. wdstNameLen] = wdstName[0 .. wdstNameLen];
3031                 tzInfo.DaylightName[wdstNameLen .. $] = '\0';
3032                 tzInfo.DaylightDate = tziFmt.DaylightDate;
3033                 tzInfo.DaylightBias = tziFmt.DaylightBias;
3034 
3035                 return new immutable WindowsTimeZone(name, tzInfo);
3036             }
3037             import std.datetime.date : DateTimeException;
3038             throw new DateTimeException(format("Failed to find time zone: %s", name));
3039         }
3040 
3041         static string[] getInstalledTZNames() @trusted
3042         {
3043             auto timezones = appender!(string[])();
3044 
3045             scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`);
3046 
3047             foreach (tzKeyName; baseKey.keyNames)
3048                 timezones.put(tzKeyName);
3049             sort(timezones.data);
3050 
3051             return timezones.data;
3052         }
3053 
3054         @safe unittest
3055         {
3056             import std.exception : assertNotThrown;
3057             import std.stdio : writefln;
3058             static void testWTZSuccess(string tzName)
3059             {
3060                 scope(failure) writefln("TZName which threw: %s", tzName);
3061 
3062                 WindowsTimeZone.getTimeZone(tzName);
3063             }
3064 
3065             auto tzNames = getInstalledTZNames();
3066 
3067             import std.datetime.date : DateTimeException;
3068             foreach (tzName; tzNames)
3069                 assertNotThrown!DateTimeException(testWTZSuccess(tzName));
3070         }
3071 
3072 
3073     private:
3074 
3075         static bool _dstInEffect(const scope TIME_ZONE_INFORMATION* tzInfo, long stdTime) @trusted nothrow
3076         {
3077             try
3078             {
3079                 if (tzInfo.DaylightDate.wMonth == 0)
3080                     return false;
3081 
3082                 import std.datetime.date : DateTime, Month;
3083                 auto utcDateTime = cast(DateTime) SysTime(stdTime, UTC());
3084 
3085                 //The limits of what SystemTimeToTzSpecificLocalTime will accept.
3086                 if (utcDateTime.year < 1601)
3087                 {
3088                     import std.datetime.date : Month;
3089                     if (utcDateTime.month == Month.feb && utcDateTime.day == 29)
3090                         utcDateTime.day = 28;
3091                     utcDateTime.year = 1601;
3092                 }
3093                 else if (utcDateTime.year > 30_827)
3094                 {
3095                     if (utcDateTime.month == Month.feb && utcDateTime.day == 29)
3096                         utcDateTime.day = 28;
3097                     utcDateTime.year = 30_827;
3098                 }
3099 
3100                 //SystemTimeToTzSpecificLocalTime doesn't act correctly at the
3101                 //beginning or end of the year (bleh). Unless some bizarre time
3102                 //zone changes DST on January 1st or December 31st, this should
3103                 //fix the problem.
3104                 if (utcDateTime.month == Month.jan)
3105                 {
3106                     if (utcDateTime.day == 1)
3107                         utcDateTime.day = 2;
3108                 }
3109                 else if (utcDateTime.month == Month.dec && utcDateTime.day == 31)
3110                     utcDateTime.day = 30;
3111 
3112                 SYSTEMTIME utcTime = void;
3113                 SYSTEMTIME otherTime = void;
3114 
3115                 utcTime.wYear = utcDateTime.year;
3116                 utcTime.wMonth = utcDateTime.month;
3117                 utcTime.wDay = utcDateTime.day;
3118                 utcTime.wHour = utcDateTime.hour;
3119                 utcTime.wMinute = utcDateTime.minute;
3120                 utcTime.wSecond = utcDateTime.second;
3121                 utcTime.wMilliseconds = 0;
3122 
3123                 immutable result = SystemTimeToTzSpecificLocalTime(cast(TIME_ZONE_INFORMATION*) tzInfo,
3124                                                                    &utcTime,
3125                                                                    &otherTime);
3126                 assert(result, "Failed to create SystemTimeToTzSpecificLocalTime");
3127 
3128                 immutable otherDateTime = DateTime(otherTime.wYear,
3129                                                    otherTime.wMonth,
3130                                                    otherTime.wDay,
3131                                                    otherTime.wHour,
3132                                                    otherTime.wMinute,
3133                                                    otherTime.wSecond);
3134                 immutable diff = utcDateTime - otherDateTime;
3135                 immutable minutes = diff.total!"minutes" - tzInfo.Bias;
3136 
3137                 if (minutes == tzInfo.DaylightBias)
3138                     return true;
3139 
3140                 assert(minutes == tzInfo.StandardBias, "Unexpected difference");
3141 
3142                 return false;
3143             }
3144             catch (Exception e)
3145                 assert(0, "DateTime's constructor threw.");
3146         }
3147 
3148         @system unittest
3149         {
3150             TIME_ZONE_INFORMATION tzInfo;
3151             GetTimeZoneInformation(&tzInfo);
3152 
3153             import std.datetime.date : DateTime;
3154             foreach (year; [1600, 1601, 30_827, 30_828])
3155                 WindowsTimeZone._dstInEffect(&tzInfo, SysTime(DateTime(year, 1, 1)).stdTime);
3156         }
3157 
3158 
3159         static long _utcToTZ(const scope TIME_ZONE_INFORMATION* tzInfo, long stdTime, bool hasDST) @safe nothrow
3160         {
3161             if (hasDST && WindowsTimeZone._dstInEffect(tzInfo, stdTime))
3162                 return stdTime - convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.DaylightBias);
3163 
3164             return stdTime - convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.StandardBias);
3165         }
3166 
3167 
3168         static long _tzToUTC(const scope TIME_ZONE_INFORMATION* tzInfo, long adjTime, bool hasDST) @trusted nothrow
3169         {
3170             if (hasDST)
3171             {
3172                 try
3173                 {
3174                     import std.datetime.date : DateTime, Month;
3175                     bool dstInEffectForLocalDateTime(DateTime localDateTime)
3176                     {
3177                         // The limits of what SystemTimeToTzSpecificLocalTime will accept.
3178                         if (localDateTime.year < 1601)
3179                         {
3180                             if (localDateTime.month == Month.feb && localDateTime.day == 29)
3181                                 localDateTime.day = 28;
3182 
3183                             localDateTime.year = 1601;
3184                         }
3185                         else if (localDateTime.year > 30_827)
3186                         {
3187                             if (localDateTime.month == Month.feb && localDateTime.day == 29)
3188                                 localDateTime.day = 28;
3189 
3190                             localDateTime.year = 30_827;
3191                         }
3192 
3193                         // SystemTimeToTzSpecificLocalTime doesn't act correctly at the
3194                         // beginning or end of the year (bleh). Unless some bizarre time
3195                         // zone changes DST on January 1st or December 31st, this should
3196                         // fix the problem.
3197                         if (localDateTime.month == Month.jan)
3198                         {
3199                             if (localDateTime.day == 1)
3200                                 localDateTime.day = 2;
3201                         }
3202                         else if (localDateTime.month == Month.dec && localDateTime.day == 31)
3203                             localDateTime.day = 30;
3204 
3205                         SYSTEMTIME utcTime = void;
3206                         SYSTEMTIME localTime = void;
3207 
3208                         localTime.wYear = localDateTime.year;
3209                         localTime.wMonth = localDateTime.month;
3210                         localTime.wDay = localDateTime.day;
3211                         localTime.wHour = localDateTime.hour;
3212                         localTime.wMinute = localDateTime.minute;
3213                         localTime.wSecond = localDateTime.second;
3214                         localTime.wMilliseconds = 0;
3215 
3216                         immutable result = TzSpecificLocalTimeToSystemTime(cast(TIME_ZONE_INFORMATION*) tzInfo,
3217                                                                            &localTime,
3218                                                                            &utcTime);
3219                         assert(result);
3220                         assert(result, "Failed to create _tzToUTC");
3221 
3222                         immutable utcDateTime = DateTime(utcTime.wYear,
3223                                                          utcTime.wMonth,
3224                                                          utcTime.wDay,
3225                                                          utcTime.wHour,
3226                                                          utcTime.wMinute,
3227                                                          utcTime.wSecond);
3228 
3229                         immutable diff = localDateTime - utcDateTime;
3230                         immutable minutes = -tzInfo.Bias - diff.total!"minutes";
3231 
3232                         if (minutes == tzInfo.DaylightBias)
3233                             return true;
3234 
3235                         assert(minutes == tzInfo.StandardBias, "Unexpected difference");
3236 
3237                         return false;
3238                     }
3239 
3240                     import std.datetime.date : DateTime;
3241                     auto localDateTime = cast(DateTime) SysTime(adjTime, UTC());
3242                     auto localDateTimeBefore = localDateTime - dur!"hours"(1);
3243                     auto localDateTimeAfter = localDateTime + dur!"hours"(1);
3244 
3245                     auto dstInEffectNow = dstInEffectForLocalDateTime(localDateTime);
3246                     auto dstInEffectBefore = dstInEffectForLocalDateTime(localDateTimeBefore);
3247                     auto dstInEffectAfter = dstInEffectForLocalDateTime(localDateTimeAfter);
3248 
3249                     bool isDST;
3250 
3251                     if (dstInEffectBefore && dstInEffectNow && dstInEffectAfter)
3252                         isDST = true;
3253                     else if (!dstInEffectBefore && !dstInEffectNow && !dstInEffectAfter)
3254                         isDST = false;
3255                     else if (!dstInEffectBefore && dstInEffectAfter)
3256                         isDST = false;
3257                     else if (dstInEffectBefore && !dstInEffectAfter)
3258                         isDST = dstInEffectNow;
3259                     else
3260                         assert(0, "Bad Logic.");
3261 
3262                     if (isDST)
3263                         return adjTime + convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.DaylightBias);
3264                 }
3265                 catch (Exception e)
3266                     assert(0, "SysTime's constructor threw.");
3267             }
3268 
3269             return adjTime + convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.StandardBias);
3270         }
3271 
3272 
3273         this(string name, TIME_ZONE_INFORMATION tzInfo) @trusted immutable pure
3274         {
3275             super(name, to!string(tzInfo.StandardName.ptr), to!string(tzInfo.DaylightName.ptr));
3276             _tzInfo = tzInfo;
3277         }
3278 
3279 
3280         TIME_ZONE_INFORMATION _tzInfo;
3281     }
3282 }
3283 
3284 
3285 version (StdDdoc)
3286 {
3287     /++
3288         $(BLUE This function is Posix-Only.)
3289 
3290         Sets the local time zone on Posix systems with the TZ
3291         Database name by setting the TZ environment variable.
3292 
3293         Unfortunately, there is no way to do it on Windows using the TZ
3294         Database name, so this function only exists on Posix systems.
3295       +/
3296     void setTZEnvVar(string tzDatabaseName) @safe nothrow;
3297 
3298 
3299     /++
3300         $(BLUE This function is Posix-Only.)
3301 
3302         Clears the TZ environment variable.
3303       +/
3304     void clearTZEnvVar() @safe nothrow;
3305 }
3306 else version (Posix)
3307 {
3308     void setTZEnvVar(string tzDatabaseName) @trusted nothrow
3309     {
3310         import core.stdc.time : tzset;
3311         import core.sys.posix.stdlib : setenv;
3312         import std.internal.cstring : tempCString;
3313         import std.path : asNormalizedPath, chainPath;
3314 
3315         version (Android)
3316             auto value = asNormalizedPath(tzDatabaseName);
3317         else
3318             auto value = asNormalizedPath(chainPath(PosixTimeZone.defaultTZDatabaseDir, tzDatabaseName));
3319         setenv("TZ", value.tempCString(), 1);
3320         tzset();
3321     }
3322 
3323 
3324     void clearTZEnvVar() @trusted nothrow
3325     {
3326         import core.stdc.time : tzset;
3327         import core.sys.posix.stdlib : unsetenv;
3328 
3329         unsetenv("TZ");
3330         tzset();
3331     }
3332 }
3333 
3334 
3335 /++
3336     Provides the conversions between the IANA time zone database time zone names
3337     (which POSIX systems use) and the time zone names that Windows uses.
3338 
3339     Windows uses a different set of time zone names than the IANA time zone
3340     database does, and how they correspond to one another changes over time
3341     (particularly when Microsoft updates Windows).
3342     $(HTTP github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml, windowsZones.xml)
3343     provides the current conversions (which may or may not match up with what's
3344     on a particular Windows box depending on how up-to-date it is), and
3345     parseTZConversions reads in those conversions from windowsZones.xml so that
3346     a D program can use those conversions.
3347 
3348     However, it should be noted that the time zone information on Windows is
3349     frequently less accurate than that in the IANA time zone database, and if
3350     someone really wants accurate time zone information, they should use the
3351     IANA time zone database files with $(LREF PosixTimeZone) on Windows rather
3352     than $(LREF WindowsTimeZone), whereas $(LREF WindowsTimeZone) makes more
3353     sense when trying to match what Windows will think the time is in a specific
3354     time zone.
3355 
3356     Also, the IANA time zone database has a lot more time zones than Windows
3357     does.
3358 
3359     Params:
3360         windowsZonesXMLText = The text from
3361         $(HTTP github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml, windowsZones.xml)
3362 
3363     Throws:
3364         Exception if there is an error while parsing the given XML.
3365 
3366 --------------------
3367     // Parse the conversions from a local file.
3368     auto text = std.file.readText("path/to/windowsZones.xml");
3369     auto conversions = parseTZConversions(text);
3370 
3371     // Alternatively, grab the XML file from the web at runtime
3372     // and parse it so that it's guaranteed to be up-to-date, though
3373     // that has the downside that the code needs to worry about the
3374     // site being down or unicode.org changing the URL.
3375     auto url = "https://raw.githubusercontent.com/unicode-org/cldr/main/common/supplemental/windowsZones.xml";
3376     auto conversions2 = parseTZConversions(std.net.curl.get(url));
3377 --------------------
3378   +/
3379 struct TZConversions
3380 {
3381     /++
3382         The key is the Windows time zone name, and the value is a list of
3383         IANA TZ database names which are close (currently only ever one, but
3384         it allows for multiple in case it's ever necessary).
3385       +/
3386     string[][string] toWindows;
3387 
3388     /++
3389         The key is the IANA time zone database name, and the value is a list of
3390         Windows time zone names which are close (usually only one, but it could
3391         be multiple).
3392       +/
3393     string[][string] fromWindows;
3394 }
3395 
3396 /++ ditto +/
3397 TZConversions parseTZConversions(string windowsZonesXMLText) @safe pure
3398 {
3399     // This is a bit hacky, since it doesn't properly read XML, but it avoids
3400     // needing to pull in an xml parsing module.
3401     import std.algorithm.iteration : uniq;
3402     import std.algorithm.searching : find;
3403     import std.algorithm.sorting : sort;
3404     import std.array : array, split;
3405     import std.string : lineSplitter;
3406 
3407     string[][string] win2Nix;
3408     string[][string] nix2Win;
3409 
3410     immutable f1 = `<mapZone other="`;
3411     immutable f2 = `type="`;
3412 
3413     foreach (line; windowsZonesXMLText.lineSplitter())
3414     {
3415         import std.exception : enforce;
3416         // Sample line:
3417         // <mapZone other="Canada Central Standard Time" territory="CA" type="America/Regina America/Swift_Current"/>
3418 
3419         line = line.find(f1);
3420         if (line.empty)
3421             continue;
3422         line = line[f1.length .. $];
3423         auto next = line.find('"');
3424         enforce(!next.empty, "Error parsing. Text does not appear to be from windowsZones.xml");
3425         auto win = line[0 .. $ - next.length];
3426         line = next.find(f2);
3427         enforce(!line.empty, "Error parsing. Text does not appear to be from windowsZones.xml");
3428         line = line[f2.length .. $];
3429         next = line.find('"');
3430         enforce(!next.empty, "Error parsing. Text does not appear to be from windowsZones.xml");
3431         auto nixes = line[0 .. $ - next.length].split();
3432 
3433         if (auto n = win in win2Nix)
3434             *n ~= nixes;
3435         else
3436             win2Nix[win] = nixes;
3437 
3438         foreach (nix; nixes)
3439         {
3440             if (auto w = nix in nix2Win)
3441                 *w ~= win;
3442             else
3443                 nix2Win[nix] = [win];
3444         }
3445     }
3446 
3447     foreach (key, ref value; nix2Win)
3448         value = value.sort().uniq().array();
3449     foreach (key, ref value; win2Nix)
3450         value = value.sort().uniq().array();
3451 
3452     return TZConversions(nix2Win, win2Nix);
3453 }
3454 
3455 @safe unittest
3456 {
3457     import std.algorithm.comparison : equal;
3458     import std.algorithm.iteration : uniq;
3459     import std.algorithm.sorting : isSorted;
3460 
3461     // Reduced text from https://github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml
3462     auto sampleFileText =
3463 `<?xml version="1.0" encoding="UTF-8" ?>
3464 <!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd">
3465 <!--
3466 Copyright © 1991-2013 Unicode, Inc.
3467 CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/)
3468 For terms of use, see http://www.unicode.org/copyright.html
3469 -->
3470 
3471 <supplementalData>
3472     <version number="$Revision$"/>
3473 
3474     <windowsZones>
3475         <mapTimezones otherVersion="7df0005" typeVersion="2015g">
3476 
3477             <!-- (UTC-12:00) International Date Line West -->
3478             <mapZone other="Dateline Standard Time" territory="001" type="Etc/GMT+12"/>
3479             <mapZone other="Dateline Standard Time" territory="ZZ" type="Etc/GMT+12"/>
3480 
3481             <!-- (UTC-11:00) Coordinated Universal Time-11 -->
3482             <mapZone other="UTC-11" territory="001" type="Etc/GMT+11"/>
3483             <mapZone other="UTC-11" territory="AS" type="Pacific/Pago_Pago"/>
3484             <mapZone other="UTC-11" territory="NU" type="Pacific/Niue"/>
3485             <mapZone other="UTC-11" territory="UM" type="Pacific/Midway"/>
3486             <mapZone other="UTC-11" territory="ZZ" type="Etc/GMT+11"/>
3487 
3488             <!-- (UTC-10:00) Hawaii -->
3489             <mapZone other="Hawaiian Standard Time" territory="001" type="Pacific/Honolulu"/>
3490             <mapZone other="Hawaiian Standard Time" territory="CK" type="Pacific/Rarotonga"/>
3491             <mapZone other="Hawaiian Standard Time" territory="PF" type="Pacific/Tahiti"/>
3492             <mapZone other="Hawaiian Standard Time" territory="UM" type="Pacific/Johnston"/>
3493             <mapZone other="Hawaiian Standard Time" territory="US" type="Pacific/Honolulu"/>
3494             <mapZone other="Hawaiian Standard Time" territory="ZZ" type="Etc/GMT+10"/>
3495 
3496             <!-- (UTC-09:00) Alaska -->
3497             <mapZone other="Alaskan Standard Time" territory="001" type="America/Anchorage"/>
3498             <mapZone other="Alaskan Standard Time" territory="US" `
3499                 ~ `type="America/Anchorage America/Juneau America/Nome America/Sitka America/Yakutat"/>
3500         </mapTimezones>
3501     </windowsZones>
3502 </supplementalData>`;
3503 
3504     auto tzConversions = parseTZConversions(sampleFileText);
3505     assert(tzConversions.toWindows.length == 15);
3506     assert(tzConversions.toWindows["America/Anchorage"] == ["Alaskan Standard Time"]);
3507     assert(tzConversions.toWindows["America/Juneau"] == ["Alaskan Standard Time"]);
3508     assert(tzConversions.toWindows["America/Nome"] == ["Alaskan Standard Time"]);
3509     assert(tzConversions.toWindows["America/Sitka"] == ["Alaskan Standard Time"]);
3510     assert(tzConversions.toWindows["America/Yakutat"] == ["Alaskan Standard Time"]);
3511     assert(tzConversions.toWindows["Etc/GMT+10"] == ["Hawaiian Standard Time"]);
3512     assert(tzConversions.toWindows["Etc/GMT+11"] == ["UTC-11"]);
3513     assert(tzConversions.toWindows["Etc/GMT+12"] == ["Dateline Standard Time"]);
3514     assert(tzConversions.toWindows["Pacific/Honolulu"] == ["Hawaiian Standard Time"]);
3515     assert(tzConversions.toWindows["Pacific/Johnston"] == ["Hawaiian Standard Time"]);
3516     assert(tzConversions.toWindows["Pacific/Midway"] == ["UTC-11"]);
3517     assert(tzConversions.toWindows["Pacific/Niue"] == ["UTC-11"]);
3518     assert(tzConversions.toWindows["Pacific/Pago_Pago"] == ["UTC-11"]);
3519     assert(tzConversions.toWindows["Pacific/Rarotonga"] == ["Hawaiian Standard Time"]);
3520     assert(tzConversions.toWindows["Pacific/Tahiti"] == ["Hawaiian Standard Time"]);
3521 
3522     assert(tzConversions.fromWindows.length == 4);
3523     assert(tzConversions.fromWindows["Alaskan Standard Time"] ==
3524            ["America/Anchorage", "America/Juneau", "America/Nome", "America/Sitka", "America/Yakutat"]);
3525     assert(tzConversions.fromWindows["Dateline Standard Time"] == ["Etc/GMT+12"]);
3526     assert(tzConversions.fromWindows["Hawaiian Standard Time"] ==
3527            ["Etc/GMT+10", "Pacific/Honolulu", "Pacific/Johnston", "Pacific/Rarotonga", "Pacific/Tahiti"]);
3528     assert(tzConversions.fromWindows["UTC-11"] ==
3529            ["Etc/GMT+11", "Pacific/Midway", "Pacific/Niue", "Pacific/Pago_Pago"]);
3530 
3531     foreach (key, value; tzConversions.fromWindows)
3532     {
3533         assert(value.isSorted, key);
3534         assert(equal(value.uniq(), value), key);
3535     }
3536 }