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 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 } 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 +/ 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 +/ 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 +/ 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 +/ 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 190 version (Posix) 191 { 192 immutable tz = PosixTimeZone.getTimeZone(tzName); 193 assert(tz.name == tzName); 194 } 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 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 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 } 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 { 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 { 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 +/ 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 +/ 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 +/ 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 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 +/ 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 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 +/ 1181 override Duration utcOffsetAt(long stdTime) @safe const nothrow 1182 { 1183 return dur!"hnsecs"(0); 1184 } 1185 1186 1187 private: 1188 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 +/ 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 +/ 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 +/ 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 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 } 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 } 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 } 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 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 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); 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 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); 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 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 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); 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); 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); 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 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 { 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); 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 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 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 { 2398 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 { 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 { 2430 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 { 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 { 2469 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 { 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 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 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. 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 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 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 } 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 +/ 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