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