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