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