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.getDefaultTZDatabaseDir(), "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. If the TZDIR environment variable is set, it is consulted 2018 before this constant. 2019 +/ 2020 enum defaultTZDatabaseDir = ""; 2021 } 2022 else version (TZDatabaseDir) 2023 { 2024 import std.string : strip; 2025 enum defaultTZDatabaseDir = strip(import("TZDatabaseDirFile")); 2026 } 2027 else version (Android) 2028 { 2029 enum defaultTZDatabaseDir = "/system/usr/share/zoneinfo/"; 2030 } 2031 else version (Solaris) 2032 { 2033 enum defaultTZDatabaseDir = "/usr/share/lib/zoneinfo/"; 2034 } 2035 else version (Posix) 2036 { 2037 enum defaultTZDatabaseDir = "/usr/share/zoneinfo/"; 2038 } 2039 else version (Windows) 2040 { 2041 enum defaultTZDatabaseDir = ""; 2042 } 2043 2044 private static string getDefaultTZDatabaseDir() 2045 { 2046 import core.stdc.stdlib : getenv; 2047 import std.string : fromStringz; 2048 2049 auto dir = getenv("TZDIR"); 2050 if (dir) 2051 return fromStringz(dir).idup; 2052 2053 return defaultTZDatabaseDir; 2054 } 2055 2056 2057 /++ 2058 Returns a $(LREF TimeZone) with the give name per the TZ Database. The 2059 time zone information is fetched from the TZ Database time zone files in 2060 the given directory. 2061 2062 See_Also: 2063 $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ 2064 Database)<br> 2065 $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List of 2066 Time Zones) 2067 2068 Params: 2069 name = The TZ Database name of the desired time zone 2070 tzDatabaseDir = The directory where the TZ Database files are 2071 located. Because these files are not located on 2072 Windows systems, provide them 2073 and give their location here to 2074 use $(LREF PosixTimeZone)s. 2075 2076 Throws: 2077 $(REF DateTimeException,std,datetime,date) if the given time zone 2078 could not be found or `FileException` if the TZ Database file 2079 could not be opened. 2080 +/ 2081 // TODO make it possible for tzDatabaseDir to be gzipped tar file rather than an uncompressed 2082 // directory. 2083 static immutable(PosixTimeZone) getTimeZone(string name, string tzDatabaseDir = getDefaultTZDatabaseDir()) @trusted 2084 { 2085 import std.algorithm.sorting : sort; 2086 import std.conv : to; 2087 import std.datetime.date : DateTimeException; 2088 import std.exception : enforce; 2089 import std.format : format; 2090 import std.path : asNormalizedPath, chainPath; 2091 import std.range : retro; 2092 2093 name = strip(name); 2094 2095 enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir))); 2096 enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir))); 2097 2098 version (Android) 2099 { 2100 auto tzfileOffset = name in tzdataIndex(tzDatabaseDir); 2101 enforce(tzfileOffset, new DateTimeException(format("The time zone %s is not listed.", name))); 2102 string tzFilename = separate_index ? "zoneinfo.dat" : "tzdata"; 2103 const file = asNormalizedPath(chainPath(tzDatabaseDir, tzFilename)).to!string; 2104 } 2105 else 2106 const file = asNormalizedPath(chainPath(tzDatabaseDir, name)).to!string; 2107 2108 enforce(file.exists(), new DateTimeException(format("File %s does not exist.", file))); 2109 enforce(file.isFile, new DateTimeException(format("%s is not a file.", file))); 2110 2111 auto tzFile = File(file); 2112 version (Android) tzFile.seek(*tzfileOffset); 2113 immutable gmtZone = name.representation().canFind("GMT"); 2114 2115 import std.datetime.date : DateTimeException; 2116 try 2117 { 2118 _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif"); 2119 2120 immutable char tzFileVersion = readVal!char(tzFile); 2121 _enforceValidTZFile(tzFileVersion == '\0' || tzFileVersion == '2' || tzFileVersion == '3'); 2122 2123 { 2124 auto zeroBlock = readVal!(ubyte[])(tzFile, 15); 2125 bool allZeroes = true; 2126 2127 foreach (val; zeroBlock) 2128 { 2129 if (val != 0) 2130 { 2131 allZeroes = false; 2132 break; 2133 } 2134 } 2135 2136 _enforceValidTZFile(allZeroes); 2137 } 2138 2139 2140 // The number of UTC/local indicators stored in the file. 2141 auto tzh_ttisgmtcnt = readVal!int(tzFile); 2142 2143 // The number of standard/wall indicators stored in the file. 2144 auto tzh_ttisstdcnt = readVal!int(tzFile); 2145 2146 // The number of leap seconds for which data is stored in the file. 2147 auto tzh_leapcnt = readVal!int(tzFile); 2148 2149 // The number of "transition times" for which data is stored in the file. 2150 auto tzh_timecnt = readVal!int(tzFile); 2151 2152 // The number of "local time types" for which data is stored in the file (must not be zero). 2153 auto tzh_typecnt = readVal!int(tzFile); 2154 _enforceValidTZFile(tzh_typecnt != 0); 2155 2156 // The number of characters of "timezone abbreviation strings" stored in the file. 2157 auto tzh_charcnt = readVal!int(tzFile); 2158 2159 // time_ts where DST transitions occur. 2160 auto transitionTimeTs = new long[](tzh_timecnt); 2161 foreach (ref transition; transitionTimeTs) 2162 transition = readVal!int(tzFile); 2163 2164 // Indices into ttinfo structs indicating the changes 2165 // to be made at the corresponding DST transition. 2166 auto ttInfoIndices = new ubyte[](tzh_timecnt); 2167 foreach (ref ttInfoIndex; ttInfoIndices) 2168 ttInfoIndex = readVal!ubyte(tzFile); 2169 2170 // ttinfos which give info on DST transitions. 2171 auto tempTTInfos = new TempTTInfo[](tzh_typecnt); 2172 foreach (ref ttInfo; tempTTInfos) 2173 ttInfo = readVal!TempTTInfo(tzFile); 2174 2175 // The array of time zone abbreviation characters. 2176 auto tzAbbrevChars = readVal!(char[])(tzFile, tzh_charcnt); 2177 2178 auto leapSeconds = new LeapSecond[](tzh_leapcnt); 2179 foreach (ref leapSecond; leapSeconds) 2180 { 2181 // The time_t when the leap second occurs. 2182 auto timeT = readVal!int(tzFile); 2183 2184 // The total number of leap seconds to be applied after 2185 // the corresponding leap second. 2186 auto total = readVal!int(tzFile); 2187 2188 leapSecond = LeapSecond(timeT, total); 2189 } 2190 2191 // Indicate whether each corresponding DST transition were specified 2192 // in standard time or wall clock time. 2193 auto transitionIsStd = new bool[](tzh_ttisstdcnt); 2194 foreach (ref isStd; transitionIsStd) 2195 isStd = readVal!bool(tzFile); 2196 2197 // Indicate whether each corresponding DST transition associated with 2198 // local time types are specified in UTC or local time. 2199 auto transitionInUTC = new bool[](tzh_ttisgmtcnt); 2200 foreach (ref inUTC; transitionInUTC) 2201 inUTC = readVal!bool(tzFile); 2202 2203 _enforceValidTZFile(!tzFile.eof); 2204 2205 // If version 2 or 3, the information is duplicated in 64-bit. 2206 if (tzFileVersion == '2' || tzFileVersion == '3') 2207 { 2208 _enforceValidTZFile(readVal!(char[])(tzFile, 4) == "TZif"); 2209 2210 immutable char tzFileVersion2 = readVal!(char)(tzFile); 2211 _enforceValidTZFile(tzFileVersion2 == '2' || tzFileVersion2 == '3'); 2212 2213 { 2214 auto zeroBlock = readVal!(ubyte[])(tzFile, 15); 2215 bool allZeroes = true; 2216 2217 foreach (val; zeroBlock) 2218 { 2219 if (val != 0) 2220 { 2221 allZeroes = false; 2222 break; 2223 } 2224 } 2225 2226 _enforceValidTZFile(allZeroes); 2227 } 2228 2229 2230 // The number of UTC/local indicators stored in the file. 2231 tzh_ttisgmtcnt = readVal!int(tzFile); 2232 2233 // The number of standard/wall indicators stored in the file. 2234 tzh_ttisstdcnt = readVal!int(tzFile); 2235 2236 // The number of leap seconds for which data is stored in the file. 2237 tzh_leapcnt = readVal!int(tzFile); 2238 2239 // The number of "transition times" for which data is stored in the file. 2240 tzh_timecnt = readVal!int(tzFile); 2241 2242 // The number of "local time types" for which data is stored in the file (must not be zero). 2243 tzh_typecnt = readVal!int(tzFile); 2244 _enforceValidTZFile(tzh_typecnt != 0); 2245 2246 // The number of characters of "timezone abbreviation strings" stored in the file. 2247 tzh_charcnt = readVal!int(tzFile); 2248 2249 // time_ts where DST transitions occur. 2250 transitionTimeTs = new long[](tzh_timecnt); 2251 foreach (ref transition; transitionTimeTs) 2252 transition = readVal!long(tzFile); 2253 2254 // Indices into ttinfo structs indicating the changes 2255 // to be made at the corresponding DST transition. 2256 ttInfoIndices = new ubyte[](tzh_timecnt); 2257 foreach (ref ttInfoIndex; ttInfoIndices) 2258 ttInfoIndex = readVal!ubyte(tzFile); 2259 2260 // ttinfos which give info on DST transitions. 2261 tempTTInfos = new TempTTInfo[](tzh_typecnt); 2262 foreach (ref ttInfo; tempTTInfos) 2263 ttInfo = readVal!TempTTInfo(tzFile); 2264 2265 // The array of time zone abbreviation characters. 2266 tzAbbrevChars = readVal!(char[])(tzFile, tzh_charcnt); 2267 2268 leapSeconds = new LeapSecond[](tzh_leapcnt); 2269 foreach (ref leapSecond; leapSeconds) 2270 { 2271 // The time_t when the leap second occurs. 2272 auto timeT = readVal!long(tzFile); 2273 2274 // The total number of leap seconds to be applied after 2275 // the corresponding leap second. 2276 auto total = readVal!int(tzFile); 2277 2278 leapSecond = LeapSecond(timeT, total); 2279 } 2280 2281 // Indicate whether each corresponding DST transition were specified 2282 // in standard time or wall clock time. 2283 transitionIsStd = new bool[](tzh_ttisstdcnt); 2284 foreach (ref isStd; transitionIsStd) 2285 isStd = readVal!bool(tzFile); 2286 2287 // Indicate whether each corresponding DST transition associated with 2288 // local time types are specified in UTC or local time. 2289 transitionInUTC = new bool[](tzh_ttisgmtcnt); 2290 foreach (ref inUTC; transitionInUTC) 2291 inUTC = readVal!bool(tzFile); 2292 } 2293 2294 _enforceValidTZFile(tzFile.readln().strip().empty); 2295 2296 cast(void) tzFile.readln(); 2297 2298 version (Android) 2299 { 2300 // Android uses a single file for all timezone data, so the file 2301 // doesn't end here. 2302 } 2303 else 2304 { 2305 _enforceValidTZFile(tzFile.readln().strip().empty); 2306 _enforceValidTZFile(tzFile.eof); 2307 } 2308 2309 2310 auto transitionTypes = new TransitionType*[](tempTTInfos.length); 2311 2312 foreach (i, ref ttype; transitionTypes) 2313 { 2314 bool isStd = false; 2315 2316 if (i < transitionIsStd.length && !transitionIsStd.empty) 2317 isStd = transitionIsStd[i]; 2318 2319 bool inUTC = false; 2320 2321 if (i < transitionInUTC.length && !transitionInUTC.empty) 2322 inUTC = transitionInUTC[i]; 2323 2324 ttype = new TransitionType(isStd, inUTC); 2325 } 2326 2327 auto ttInfos = new immutable(TTInfo)*[](tempTTInfos.length); 2328 foreach (i, ref ttInfo; ttInfos) 2329 { 2330 auto tempTTInfo = tempTTInfos[i]; 2331 2332 if (gmtZone) 2333 tempTTInfo.tt_gmtoff = -tempTTInfo.tt_gmtoff; 2334 2335 auto abbrevChars = tzAbbrevChars[tempTTInfo.tt_abbrind .. $]; 2336 string abbrev = abbrevChars[0 .. abbrevChars.countUntil('\0')].idup; 2337 2338 ttInfo = new immutable(TTInfo)(tempTTInfos[i], abbrev); 2339 } 2340 2341 auto tempTransitions = new TempTransition[](transitionTimeTs.length); 2342 foreach (i, ref tempTransition; tempTransitions) 2343 { 2344 immutable ttiIndex = ttInfoIndices[i]; 2345 auto transitionTimeT = transitionTimeTs[i]; 2346 auto ttype = transitionTypes[ttiIndex]; 2347 auto ttInfo = ttInfos[ttiIndex]; 2348 2349 tempTransition = TempTransition(transitionTimeT, ttInfo, ttype); 2350 } 2351 2352 if (tempTransitions.empty) 2353 { 2354 _enforceValidTZFile(ttInfos.length == 1 && transitionTypes.length == 1); 2355 tempTransitions ~= TempTransition(0, ttInfos[0], transitionTypes[0]); 2356 } 2357 2358 sort!"a.timeT < b.timeT"(tempTransitions); 2359 sort!"a.timeT < b.timeT"(leapSeconds); 2360 2361 auto transitions = new Transition[](tempTransitions.length); 2362 foreach (i, ref transition; transitions) 2363 { 2364 auto tempTransition = tempTransitions[i]; 2365 auto transitionTimeT = tempTransition.timeT; 2366 auto ttInfo = tempTransition.ttInfo; 2367 2368 _enforceValidTZFile(i == 0 || transitionTimeT > tempTransitions[i - 1].timeT); 2369 2370 transition = Transition(transitionTimeT, ttInfo); 2371 } 2372 2373 string stdName; 2374 string dstName; 2375 bool hasDST = false; 2376 2377 foreach (transition; retro(transitions)) 2378 { 2379 auto ttInfo = transition.ttInfo; 2380 2381 if (ttInfo.isDST) 2382 { 2383 if (dstName.empty) 2384 dstName = ttInfo.abbrev; 2385 hasDST = true; 2386 } 2387 else 2388 { 2389 if (stdName.empty) 2390 stdName = ttInfo.abbrev; 2391 } 2392 2393 if (!stdName.empty && !dstName.empty) 2394 break; 2395 } 2396 2397 return new immutable PosixTimeZone(transitions.idup, leapSeconds.idup, name, stdName, dstName, hasDST); 2398 } 2399 catch (DateTimeException dte) 2400 throw dte; 2401 catch (Exception e) 2402 throw new DateTimeException("Not a valid TZ data file", __FILE__, __LINE__, e); 2403 } 2404 2405 /// 2406 @safe unittest 2407 { 2408 version (Posix) 2409 { 2410 auto tz = PosixTimeZone.getTimeZone("America/Los_Angeles"); 2411 2412 assert(tz.name == "America/Los_Angeles"); 2413 assert(tz.stdName == "PST"); 2414 assert(tz.dstName == "PDT"); 2415 } 2416 } 2417 2418 /++ 2419 Returns a list of the names of the time zones installed on the system. 2420 2421 Providing a sub-name narrows down the list of time zones (which 2422 can number in the thousands). For example, 2423 passing in "America" as the sub-name returns only the time zones which 2424 begin with "America". 2425 2426 Params: 2427 subName = The first part of the desired time zones. 2428 tzDatabaseDir = The directory where the TZ Database files are 2429 located. 2430 2431 Throws: 2432 `FileException` if it fails to read from disk. 2433 +/ 2434 static string[] getInstalledTZNames(string subName = "", string tzDatabaseDir = getDefaultTZDatabaseDir()) @safe 2435 { 2436 import std.algorithm.sorting : sort; 2437 import std.array : appender; 2438 import std.exception : enforce; 2439 import std.format : format; 2440 2441 version (Posix) 2442 subName = strip(subName); 2443 else version (Windows) 2444 { 2445 import std.array : replace; 2446 import std.path : dirSeparator; 2447 subName = replace(strip(subName), "/", dirSeparator); 2448 } 2449 2450 import std.datetime.date : DateTimeException; 2451 enforce(tzDatabaseDir.exists(), new DateTimeException(format("Directory %s does not exist.", tzDatabaseDir))); 2452 enforce(tzDatabaseDir.isDir, new DateTimeException(format("%s is not a directory.", tzDatabaseDir))); 2453 2454 auto timezones = appender!(string[])(); 2455 2456 version (Android) 2457 { 2458 import std.algorithm.iteration : filter; 2459 import std.algorithm.mutation : copy; 2460 2461 const index = () @trusted { return tzdataIndex(tzDatabaseDir); }(); 2462 index.byKey.filter!(a => a.startsWith(subName)).copy(timezones); 2463 } 2464 else 2465 { 2466 import std.path : baseName; 2467 // dirEntries is @system because it uses a DirIterator with a 2468 // RefCounted variable, but here, no references to the payload is 2469 // escaped to the outside, so this should be @trusted 2470 () @trusted { 2471 foreach (DirEntry de; dirEntries(tzDatabaseDir, SpanMode.depth)) 2472 { 2473 if (de.isFile) 2474 { 2475 auto tzName = de.name[tzDatabaseDir.length .. $]; 2476 2477 if (!tzName.extension().empty || 2478 !tzName.startsWith(subName) || 2479 baseName(tzName) == "leapseconds" || 2480 tzName == "+VERSION" || 2481 tzName == "SECURITY") 2482 { 2483 continue; 2484 } 2485 2486 timezones.put(tzName); 2487 } 2488 } 2489 }(); 2490 } 2491 2492 sort(timezones.data); 2493 2494 return timezones.data; 2495 } 2496 2497 version (Posix) @system unittest 2498 { 2499 import std.exception : assertNotThrown; 2500 import std.stdio : writefln; 2501 static void testPTZSuccess(string tzName) 2502 { 2503 scope(failure) writefln("TZName which threw: %s", tzName); 2504 2505 PosixTimeZone.getTimeZone(tzName); 2506 } 2507 2508 static void testPTZFailure(string tzName) 2509 { 2510 scope(success) writefln("TZName which was supposed to throw: %s", tzName); 2511 2512 PosixTimeZone.getTimeZone(tzName); 2513 } 2514 2515 auto tzNames = getInstalledTZNames(); 2516 2517 import std.datetime.date : DateTimeException; 2518 foreach (tzName; tzNames) 2519 assertNotThrown!DateTimeException(testPTZSuccess(tzName)); 2520 2521 // No timezone directories on Android, just a single tzdata file 2522 version (Android) 2523 {} 2524 else 2525 { 2526 string tzDatabaseDir = getDefaultTZDatabaseDir(); 2527 foreach (DirEntry de; dirEntries(tzDatabaseDir, SpanMode.depth)) 2528 { 2529 if (de.isFile) 2530 { 2531 auto tzName = de.name[tzDatabaseDir.length .. $]; 2532 2533 if (!canFind(tzNames, tzName)) 2534 assertThrown!DateTimeException(testPTZFailure(tzName)); 2535 } 2536 } 2537 } 2538 } 2539 2540 2541 private: 2542 2543 /+ 2544 Holds information on when a time transition occures (usually a 2545 transition to or from DST) as well as a pointer to the `TTInfo` which 2546 holds information on the utc offset past the transition. 2547 +/ 2548 struct Transition 2549 { 2550 this(long timeT, immutable (TTInfo)* ttInfo) @safe pure 2551 { 2552 this.timeT = timeT; 2553 this.ttInfo = ttInfo; 2554 } 2555 2556 long timeT; 2557 immutable (TTInfo)* ttInfo; 2558 } 2559 2560 2561 /+ 2562 Holds information on when a leap second occurs. 2563 +/ 2564 struct LeapSecond 2565 { 2566 this(long timeT, int total) @safe pure 2567 { 2568 this.timeT = timeT; 2569 this.total = total; 2570 } 2571 2572 long timeT; 2573 int total; 2574 } 2575 2576 /+ 2577 Holds information on the utc offset after a transition as well as 2578 whether DST is in effect after that transition. 2579 +/ 2580 struct TTInfo 2581 { 2582 this(scope const TempTTInfo tempTTInfo, string abbrev) @safe immutable pure 2583 { 2584 utcOffset = tempTTInfo.tt_gmtoff; 2585 isDST = tempTTInfo.tt_isdst; 2586 this.abbrev = abbrev; 2587 } 2588 2589 immutable int utcOffset; // Offset from UTC. 2590 immutable bool isDST; // Whether DST is in effect. 2591 immutable string abbrev; // The current abbreviation for the time zone. 2592 } 2593 2594 2595 /+ 2596 Struct used to hold information relating to `TTInfo` while organizing 2597 the time zone information prior to putting it in its final form. 2598 +/ 2599 struct TempTTInfo 2600 { 2601 this(int gmtOff, bool isDST, ubyte abbrInd) @safe pure 2602 { 2603 tt_gmtoff = gmtOff; 2604 tt_isdst = isDST; 2605 tt_abbrind = abbrInd; 2606 } 2607 2608 int tt_gmtoff; 2609 bool tt_isdst; 2610 ubyte tt_abbrind; 2611 } 2612 2613 2614 /+ 2615 Struct used to hold information relating to `Transition` while 2616 organizing the time zone information prior to putting it in its final 2617 form. 2618 +/ 2619 struct TempTransition 2620 { 2621 this(long timeT, immutable (TTInfo)* ttInfo, TransitionType* ttype) @safe pure 2622 { 2623 this.timeT = timeT; 2624 this.ttInfo = ttInfo; 2625 this.ttype = ttype; 2626 } 2627 2628 long timeT; 2629 immutable (TTInfo)* ttInfo; 2630 TransitionType* ttype; 2631 } 2632 2633 2634 /+ 2635 Struct used to hold information relating to `Transition` and 2636 `TTInfo` while organizing the time zone information prior to putting 2637 it in its final form. 2638 +/ 2639 struct TransitionType 2640 { 2641 this(bool isStd, bool inUTC) @safe pure 2642 { 2643 this.isStd = isStd; 2644 this.inUTC = inUTC; 2645 } 2646 2647 // Whether the transition is in std time (as opposed to wall clock time). 2648 bool isStd; 2649 2650 // Whether the transition is in UTC (as opposed to local time). 2651 bool inUTC; 2652 } 2653 2654 2655 /+ 2656 Reads an int from a TZ file. 2657 +/ 2658 static T readVal(T)(ref File tzFile) @trusted 2659 if ((isIntegral!T || isSomeChar!T) || is(immutable T == immutable bool)) 2660 { 2661 import std.bitmanip : bigEndianToNative; 2662 T[1] buff; 2663 2664 _enforceValidTZFile(!tzFile.eof); 2665 tzFile.rawRead(buff); 2666 2667 return bigEndianToNative!T(cast(ubyte[T.sizeof]) buff); 2668 } 2669 2670 /+ 2671 Reads an array of values from a TZ file. 2672 +/ 2673 static T readVal(T)(ref File tzFile, size_t length) @trusted 2674 if (isArray!T) 2675 { 2676 auto buff = new T(length); 2677 2678 _enforceValidTZFile(!tzFile.eof); 2679 tzFile.rawRead(buff); 2680 2681 return buff; 2682 } 2683 2684 2685 /+ 2686 Reads a `TempTTInfo` from a TZ file. 2687 +/ 2688 static T readVal(T)(ref File tzFile) @safe 2689 if (is(T == TempTTInfo)) 2690 { 2691 return TempTTInfo(readVal!int(tzFile), 2692 readVal!bool(tzFile), 2693 readVal!ubyte(tzFile)); 2694 } 2695 2696 2697 /+ 2698 Throws: 2699 $(REF DateTimeException,std,datetime,date) if `result` is false. 2700 +/ 2701 static void _enforceValidTZFile(bool result, size_t line = __LINE__) @safe pure 2702 { 2703 import std.datetime.date : DateTimeException; 2704 if (!result) 2705 throw new DateTimeException("Not a valid tzdata file.", __FILE__, line); 2706 } 2707 2708 2709 int calculateLeapSeconds(long stdTime) @safe const scope pure nothrow 2710 { 2711 if (_leapSeconds.empty) 2712 return 0; 2713 2714 immutable unixTime = stdTimeToUnixTime(stdTime); 2715 2716 if (_leapSeconds.front.timeT >= unixTime) 2717 return 0; 2718 2719 immutable found = countUntil!"b < a.timeT"(_leapSeconds, unixTime); 2720 2721 if (found == -1) 2722 return _leapSeconds.back.total; 2723 2724 immutable leapSecond = found == 0 ? _leapSeconds[0] : _leapSeconds[found - 1]; 2725 2726 return leapSecond.total; 2727 } 2728 2729 2730 this(immutable Transition[] transitions, 2731 immutable LeapSecond[] leapSeconds, 2732 string name, 2733 string stdName, 2734 string dstName, 2735 bool hasDST) @safe immutable pure 2736 { 2737 if (dstName.empty && !stdName.empty) 2738 dstName = stdName; 2739 else if (stdName.empty && !dstName.empty) 2740 stdName = dstName; 2741 2742 super(name, stdName, dstName); 2743 2744 if (!transitions.empty) 2745 { 2746 foreach (i, transition; transitions[0 .. $-1]) 2747 _enforceValidTZFile(transition.timeT < transitions[i + 1].timeT); 2748 } 2749 2750 foreach (i, leapSecond; leapSeconds) 2751 _enforceValidTZFile(i == leapSeconds.length - 1 || leapSecond.timeT < leapSeconds[i + 1].timeT); 2752 2753 _transitions = transitions; 2754 _leapSeconds = leapSeconds; 2755 _hasDST = hasDST; 2756 } 2757 2758 // Android concatenates the usual timezone directories into a single file, 2759 // tzdata, along with an index to jump to each timezone's offset. In older 2760 // versions of Android, the index was stored in a separate file, zoneinfo.idx, 2761 // whereas now it's stored at the beginning of tzdata. 2762 version (Android) 2763 { 2764 // Keep track of whether there's a separate index, zoneinfo.idx. Only 2765 // check this after calling tzdataIndex, as it's initialized there. 2766 static shared bool separate_index; 2767 2768 // Extracts the name of each time zone and the offset where its data is 2769 // located in the tzdata file from the index and caches it for later. 2770 static const(uint[string]) tzdataIndex(string tzDir) 2771 { 2772 import std.concurrency : initOnce; 2773 2774 __gshared uint[string] _tzIndex; 2775 2776 // _tzIndex is initialized once and then shared across all threads. 2777 initOnce!_tzIndex( 2778 { 2779 import std.conv : to; 2780 import std.datetime.date : DateTimeException; 2781 import std.format : format; 2782 import std.path : asNormalizedPath, chainPath; 2783 2784 enum indexEntrySize = 52; 2785 const combinedFile = asNormalizedPath(chainPath(tzDir, "tzdata")).to!string; 2786 const indexFile = asNormalizedPath(chainPath(tzDir, "zoneinfo.idx")).to!string; 2787 File tzFile; 2788 uint indexEntries, dataOffset; 2789 uint[string] initIndex; 2790 2791 // Check for the combined file tzdata, which stores the index 2792 // and the time zone data together. 2793 if (combinedFile.exists() && combinedFile.isFile) 2794 { 2795 tzFile = File(combinedFile); 2796 _enforceValidTZFile(readVal!(char[])(tzFile, 6) == "tzdata"); 2797 auto tzDataVersion = readVal!(char[])(tzFile, 6); 2798 _enforceValidTZFile(tzDataVersion[5] == '\0'); 2799 2800 uint indexOffset = readVal!uint(tzFile); 2801 dataOffset = readVal!uint(tzFile); 2802 readVal!uint(tzFile); 2803 2804 indexEntries = (dataOffset - indexOffset) / indexEntrySize; 2805 separate_index = false; 2806 } 2807 else if (indexFile.exists() && indexFile.isFile) 2808 { 2809 tzFile = File(indexFile); 2810 indexEntries = to!uint(tzFile.size/indexEntrySize); 2811 separate_index = true; 2812 } 2813 else 2814 { 2815 throw new DateTimeException(format("Both timezone files %s and %s do not exist.", 2816 combinedFile, indexFile)); 2817 } 2818 2819 foreach (_; 0 .. indexEntries) 2820 { 2821 string tzName = to!string(readVal!(char[])(tzFile, 40).ptr); 2822 uint tzOffset = readVal!uint(tzFile); 2823 readVal!(uint[])(tzFile, 2); 2824 initIndex[tzName] = dataOffset + tzOffset; 2825 } 2826 initIndex.rehash; 2827 return initIndex; 2828 }()); 2829 return _tzIndex; 2830 } 2831 } 2832 2833 // List of times when the utc offset changes. 2834 immutable Transition[] _transitions; 2835 2836 // List of leap second occurrences. 2837 immutable LeapSecond[] _leapSeconds; 2838 2839 // Whether DST is in effect for this time zone at any point in time. 2840 immutable bool _hasDST; 2841 } 2842 2843 2844 version (StdDdoc) 2845 { 2846 /++ 2847 $(BLUE This class is Windows-Only.) 2848 2849 Represents a time zone from the Windows registry. Unfortunately, Windows 2850 does not use the TZ Database. To use the TZ Database, use 2851 $(LREF PosixTimeZone) (which reads its information from the TZ Database 2852 files on disk) on Windows by providing the TZ Database files and telling 2853 `PosixTimeZone.getTimeZone` where the directory holding them is. 2854 2855 The TZ Database files and Windows' time zone information frequently 2856 do not match. Windows has many errors with regards to when DST switches 2857 occur (especially for historical dates). Also, the TZ Database files 2858 include far more time zones than Windows does. So, for accurate 2859 time zone information, use the TZ Database files with 2860 $(LREF PosixTimeZone) rather than `WindowsTimeZone`. However, because 2861 `WindowsTimeZone` uses Windows system calls to deal with the time, 2862 it's far more likely to match the behavior of other Windows programs. 2863 Be aware of the differences when selecting a method. 2864 2865 `WindowsTimeZone` does not exist on Posix systems. 2866 2867 To get a `WindowsTimeZone`, call `WindowsTimeZone.getTimeZone`. 2868 2869 See_Also: 2870 $(HTTP www.iana.org/time-zones, Home of the TZ Database files) 2871 +/ 2872 final class WindowsTimeZone : TimeZone 2873 { 2874 public: 2875 2876 /++ 2877 Whether this time zone has Daylight Savings Time at any point in 2878 time. Note that for some time zone types it may not have DST for 2879 current dates but will still return true for `hasDST` because the 2880 time zone did at some point have DST. 2881 +/ 2882 @property override bool hasDST() @safe const scope nothrow; 2883 2884 2885 /++ 2886 Takes the number of hnsecs (100 ns) since midnight, January 1st, 2887 1 A.D. in UTC time (i.e. std time) and returns whether DST is in 2888 effect in this time zone at the given point in time. 2889 2890 Params: 2891 stdTime = The UTC time that needs to be checked for DST in this 2892 time zone. 2893 +/ 2894 override bool dstInEffect(long stdTime) @safe const scope nothrow; 2895 2896 2897 /++ 2898 Takes the number of hnsecs (100 ns) since midnight, January 1st, 2899 1 A.D. in UTC time (i.e. std time) and converts it to this time 2900 zone's time. 2901 2902 Params: 2903 stdTime = The UTC time that needs to be adjusted to this time 2904 zone's time. 2905 +/ 2906 override long utcToTZ(long stdTime) @safe const scope nothrow; 2907 2908 2909 /++ 2910 Takes the number of hnsecs (100 ns) since midnight, January 1st, 2911 1 A.D. in this time zone's time and converts it to UTC (i.e. std 2912 time). 2913 2914 Params: 2915 adjTime = The time in this time zone that needs to be adjusted 2916 to UTC time. 2917 +/ 2918 override long tzToUTC(long adjTime) @safe const scope nothrow; 2919 2920 2921 /++ 2922 Returns a $(LREF TimeZone) with the given name per the Windows time 2923 zone names. The time zone information is fetched from the Windows 2924 registry. 2925 2926 See_Also: 2927 $(HTTP en.wikipedia.org/wiki/Tz_database, Wikipedia entry on TZ 2928 Database)<br> 2929 $(HTTP en.wikipedia.org/wiki/List_of_tz_database_time_zones, List 2930 of Time Zones) 2931 2932 Params: 2933 name = The TZ Database name of the desired time zone. 2934 2935 Throws: 2936 $(REF DateTimeException,std,datetime,date) if the given time 2937 zone could not be found. 2938 2939 Example: 2940 -------------------- 2941 auto tz = WindowsTimeZone.getTimeZone("Pacific Standard Time"); 2942 -------------------- 2943 +/ 2944 static immutable(WindowsTimeZone) getTimeZone(string name) @safe; 2945 2946 2947 /++ 2948 Returns a list of the names of the time zones installed on the 2949 system. The list returned by WindowsTimeZone contains the Windows 2950 TZ names, not the TZ Database names. However, 2951 `TimeZone.getinstalledTZNames` will return the TZ Database names 2952 which are equivalent to the Windows TZ names. 2953 +/ 2954 static string[] getInstalledTZNames() @safe; 2955 2956 private: 2957 2958 version (Windows) 2959 {} 2960 else 2961 alias TIME_ZONE_INFORMATION = void*; 2962 2963 static bool _dstInEffect(const scope TIME_ZONE_INFORMATION* tzInfo, long stdTime) nothrow; 2964 static long _utcToTZ(const scope TIME_ZONE_INFORMATION* tzInfo, long stdTime, bool hasDST) nothrow; 2965 static long _tzToUTC(const scope TIME_ZONE_INFORMATION* tzInfo, long adjTime, bool hasDST) nothrow; 2966 2967 this() immutable pure 2968 { 2969 super("", "", ""); 2970 } 2971 } 2972 2973 } 2974 else version (Windows) 2975 { 2976 final class WindowsTimeZone : TimeZone 2977 { 2978 import std.algorithm.sorting : sort; 2979 import std.array : appender; 2980 import std.conv : to; 2981 import std.format : format; 2982 2983 public: 2984 2985 @property override bool hasDST() @safe const scope nothrow 2986 { 2987 return _tzInfo.DaylightDate.wMonth != 0; 2988 } 2989 2990 2991 override bool dstInEffect(long stdTime) @safe const scope nothrow 2992 { 2993 return _dstInEffect(&_tzInfo, stdTime); 2994 } 2995 2996 2997 override long utcToTZ(long stdTime) @safe const scope nothrow 2998 { 2999 return _utcToTZ(&_tzInfo, stdTime, hasDST); 3000 } 3001 3002 3003 override long tzToUTC(long adjTime) @safe const scope nothrow 3004 { 3005 return _tzToUTC(&_tzInfo, adjTime, hasDST); 3006 } 3007 3008 3009 static immutable(WindowsTimeZone) getTimeZone(string name) @trusted 3010 { 3011 scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`); 3012 3013 foreach (tzKeyName; baseKey.keyNames) 3014 { 3015 if (tzKeyName != name) 3016 continue; 3017 3018 scope tzKey = baseKey.getKey(tzKeyName); 3019 3020 scope stdVal = tzKey.getValue("Std"); 3021 auto stdName = stdVal.value_SZ; 3022 3023 scope dstVal = tzKey.getValue("Dlt"); 3024 auto dstName = dstVal.value_SZ; 3025 3026 scope tziVal = tzKey.getValue("TZI"); 3027 auto binVal = tziVal.value_BINARY; 3028 assert(binVal.length == REG_TZI_FORMAT.sizeof, 3029 "Unexpected size while getTimeZone with name " ~ name); 3030 auto tziFmt = cast(REG_TZI_FORMAT*) binVal.ptr; 3031 3032 TIME_ZONE_INFORMATION tzInfo; 3033 3034 auto wstdName = stdName.to!wstring; 3035 auto wdstName = dstName.to!wstring; 3036 auto wstdNameLen = wstdName.length > 32 ? 32 : wstdName.length; 3037 auto wdstNameLen = wdstName.length > 32 ? 32 : wdstName.length; 3038 3039 tzInfo.Bias = tziFmt.Bias; 3040 tzInfo.StandardName[0 .. wstdNameLen] = wstdName[0 .. wstdNameLen]; 3041 tzInfo.StandardName[wstdNameLen .. $] = '\0'; 3042 tzInfo.StandardDate = tziFmt.StandardDate; 3043 tzInfo.StandardBias = tziFmt.StandardBias; 3044 tzInfo.DaylightName[0 .. wdstNameLen] = wdstName[0 .. wdstNameLen]; 3045 tzInfo.DaylightName[wdstNameLen .. $] = '\0'; 3046 tzInfo.DaylightDate = tziFmt.DaylightDate; 3047 tzInfo.DaylightBias = tziFmt.DaylightBias; 3048 3049 return new immutable WindowsTimeZone(name, tzInfo); 3050 } 3051 import std.datetime.date : DateTimeException; 3052 throw new DateTimeException(format("Failed to find time zone: %s", name)); 3053 } 3054 3055 static string[] getInstalledTZNames() @trusted 3056 { 3057 auto timezones = appender!(string[])(); 3058 3059 scope baseKey = Registry.localMachine.getKey(`Software\Microsoft\Windows NT\CurrentVersion\Time Zones`); 3060 3061 foreach (tzKeyName; baseKey.keyNames) 3062 timezones.put(tzKeyName); 3063 sort(timezones.data); 3064 3065 return timezones.data; 3066 } 3067 3068 @safe unittest 3069 { 3070 import std.exception : assertNotThrown; 3071 import std.stdio : writefln; 3072 static void testWTZSuccess(string tzName) 3073 { 3074 scope(failure) writefln("TZName which threw: %s", tzName); 3075 3076 WindowsTimeZone.getTimeZone(tzName); 3077 } 3078 3079 auto tzNames = getInstalledTZNames(); 3080 3081 import std.datetime.date : DateTimeException; 3082 foreach (tzName; tzNames) 3083 assertNotThrown!DateTimeException(testWTZSuccess(tzName)); 3084 } 3085 3086 3087 private: 3088 3089 static bool _dstInEffect(const scope TIME_ZONE_INFORMATION* tzInfo, long stdTime) @trusted nothrow 3090 { 3091 try 3092 { 3093 if (tzInfo.DaylightDate.wMonth == 0) 3094 return false; 3095 3096 import std.datetime.date : DateTime, Month; 3097 auto utcDateTime = cast(DateTime) SysTime(stdTime, UTC()); 3098 3099 //The limits of what SystemTimeToTzSpecificLocalTime will accept. 3100 if (utcDateTime.year < 1601) 3101 { 3102 import std.datetime.date : Month; 3103 if (utcDateTime.month == Month.feb && utcDateTime.day == 29) 3104 utcDateTime.day = 28; 3105 utcDateTime.year = 1601; 3106 } 3107 else if (utcDateTime.year > 30_827) 3108 { 3109 if (utcDateTime.month == Month.feb && utcDateTime.day == 29) 3110 utcDateTime.day = 28; 3111 utcDateTime.year = 30_827; 3112 } 3113 3114 //SystemTimeToTzSpecificLocalTime doesn't act correctly at the 3115 //beginning or end of the year (bleh). Unless some bizarre time 3116 //zone changes DST on January 1st or December 31st, this should 3117 //fix the problem. 3118 if (utcDateTime.month == Month.jan) 3119 { 3120 if (utcDateTime.day == 1) 3121 utcDateTime.day = 2; 3122 } 3123 else if (utcDateTime.month == Month.dec && utcDateTime.day == 31) 3124 utcDateTime.day = 30; 3125 3126 SYSTEMTIME utcTime = void; 3127 SYSTEMTIME otherTime = void; 3128 3129 utcTime.wYear = utcDateTime.year; 3130 utcTime.wMonth = utcDateTime.month; 3131 utcTime.wDay = utcDateTime.day; 3132 utcTime.wHour = utcDateTime.hour; 3133 utcTime.wMinute = utcDateTime.minute; 3134 utcTime.wSecond = utcDateTime.second; 3135 utcTime.wMilliseconds = 0; 3136 3137 immutable result = SystemTimeToTzSpecificLocalTime(cast(TIME_ZONE_INFORMATION*) tzInfo, 3138 &utcTime, 3139 &otherTime); 3140 assert(result, "Failed to create SystemTimeToTzSpecificLocalTime"); 3141 3142 immutable otherDateTime = DateTime(otherTime.wYear, 3143 otherTime.wMonth, 3144 otherTime.wDay, 3145 otherTime.wHour, 3146 otherTime.wMinute, 3147 otherTime.wSecond); 3148 immutable diff = utcDateTime - otherDateTime; 3149 immutable minutes = diff.total!"minutes" - tzInfo.Bias; 3150 3151 if (minutes == tzInfo.DaylightBias) 3152 return true; 3153 3154 assert(minutes == tzInfo.StandardBias, "Unexpected difference"); 3155 3156 return false; 3157 } 3158 catch (Exception e) 3159 assert(0, "DateTime's constructor threw."); 3160 } 3161 3162 @system unittest 3163 { 3164 TIME_ZONE_INFORMATION tzInfo; 3165 GetTimeZoneInformation(&tzInfo); 3166 3167 import std.datetime.date : DateTime; 3168 foreach (year; [1600, 1601, 30_827, 30_828]) 3169 WindowsTimeZone._dstInEffect(&tzInfo, SysTime(DateTime(year, 1, 1)).stdTime); 3170 } 3171 3172 3173 static long _utcToTZ(const scope TIME_ZONE_INFORMATION* tzInfo, long stdTime, bool hasDST) @safe nothrow 3174 { 3175 if (hasDST && WindowsTimeZone._dstInEffect(tzInfo, stdTime)) 3176 return stdTime - convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.DaylightBias); 3177 3178 return stdTime - convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.StandardBias); 3179 } 3180 3181 3182 static long _tzToUTC(const scope TIME_ZONE_INFORMATION* tzInfo, long adjTime, bool hasDST) @trusted nothrow 3183 { 3184 if (hasDST) 3185 { 3186 try 3187 { 3188 import std.datetime.date : DateTime, Month; 3189 bool dstInEffectForLocalDateTime(DateTime localDateTime) 3190 { 3191 // The limits of what SystemTimeToTzSpecificLocalTime will accept. 3192 if (localDateTime.year < 1601) 3193 { 3194 if (localDateTime.month == Month.feb && localDateTime.day == 29) 3195 localDateTime.day = 28; 3196 3197 localDateTime.year = 1601; 3198 } 3199 else if (localDateTime.year > 30_827) 3200 { 3201 if (localDateTime.month == Month.feb && localDateTime.day == 29) 3202 localDateTime.day = 28; 3203 3204 localDateTime.year = 30_827; 3205 } 3206 3207 // SystemTimeToTzSpecificLocalTime doesn't act correctly at the 3208 // beginning or end of the year (bleh). Unless some bizarre time 3209 // zone changes DST on January 1st or December 31st, this should 3210 // fix the problem. 3211 if (localDateTime.month == Month.jan) 3212 { 3213 if (localDateTime.day == 1) 3214 localDateTime.day = 2; 3215 } 3216 else if (localDateTime.month == Month.dec && localDateTime.day == 31) 3217 localDateTime.day = 30; 3218 3219 SYSTEMTIME utcTime = void; 3220 SYSTEMTIME localTime = void; 3221 3222 localTime.wYear = localDateTime.year; 3223 localTime.wMonth = localDateTime.month; 3224 localTime.wDay = localDateTime.day; 3225 localTime.wHour = localDateTime.hour; 3226 localTime.wMinute = localDateTime.minute; 3227 localTime.wSecond = localDateTime.second; 3228 localTime.wMilliseconds = 0; 3229 3230 immutable result = TzSpecificLocalTimeToSystemTime(cast(TIME_ZONE_INFORMATION*) tzInfo, 3231 &localTime, 3232 &utcTime); 3233 assert(result); 3234 assert(result, "Failed to create _tzToUTC"); 3235 3236 immutable utcDateTime = DateTime(utcTime.wYear, 3237 utcTime.wMonth, 3238 utcTime.wDay, 3239 utcTime.wHour, 3240 utcTime.wMinute, 3241 utcTime.wSecond); 3242 3243 immutable diff = localDateTime - utcDateTime; 3244 immutable minutes = -tzInfo.Bias - diff.total!"minutes"; 3245 3246 if (minutes == tzInfo.DaylightBias) 3247 return true; 3248 3249 assert(minutes == tzInfo.StandardBias, "Unexpected difference"); 3250 3251 return false; 3252 } 3253 3254 import std.datetime.date : DateTime; 3255 auto localDateTime = cast(DateTime) SysTime(adjTime, UTC()); 3256 auto localDateTimeBefore = localDateTime - dur!"hours"(1); 3257 auto localDateTimeAfter = localDateTime + dur!"hours"(1); 3258 3259 auto dstInEffectNow = dstInEffectForLocalDateTime(localDateTime); 3260 auto dstInEffectBefore = dstInEffectForLocalDateTime(localDateTimeBefore); 3261 auto dstInEffectAfter = dstInEffectForLocalDateTime(localDateTimeAfter); 3262 3263 bool isDST; 3264 3265 if (dstInEffectBefore && dstInEffectNow && dstInEffectAfter) 3266 isDST = true; 3267 else if (!dstInEffectBefore && !dstInEffectNow && !dstInEffectAfter) 3268 isDST = false; 3269 else if (!dstInEffectBefore && dstInEffectAfter) 3270 isDST = false; 3271 else if (dstInEffectBefore && !dstInEffectAfter) 3272 isDST = dstInEffectNow; 3273 else 3274 assert(0, "Bad Logic."); 3275 3276 if (isDST) 3277 return adjTime + convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.DaylightBias); 3278 } 3279 catch (Exception e) 3280 assert(0, "SysTime's constructor threw."); 3281 } 3282 3283 return adjTime + convert!("minutes", "hnsecs")(tzInfo.Bias + tzInfo.StandardBias); 3284 } 3285 3286 3287 this(string name, TIME_ZONE_INFORMATION tzInfo) @trusted immutable pure 3288 { 3289 super(name, to!string(tzInfo.StandardName.ptr), to!string(tzInfo.DaylightName.ptr)); 3290 _tzInfo = tzInfo; 3291 } 3292 3293 3294 TIME_ZONE_INFORMATION _tzInfo; 3295 } 3296 } 3297 3298 3299 version (StdDdoc) 3300 { 3301 /++ 3302 $(BLUE This function is Posix-Only.) 3303 3304 Sets the local time zone on Posix systems with the TZ 3305 Database name by setting the TZ environment variable. 3306 3307 Unfortunately, there is no way to do it on Windows using the TZ 3308 Database name, so this function only exists on Posix systems. 3309 +/ 3310 void setTZEnvVar(string tzDatabaseName) @safe nothrow; 3311 3312 3313 /++ 3314 $(BLUE This function is Posix-Only.) 3315 3316 Clears the TZ environment variable. 3317 +/ 3318 void clearTZEnvVar() @safe nothrow; 3319 } 3320 else version (Posix) 3321 { 3322 void setTZEnvVar(string tzDatabaseName) @trusted nothrow 3323 { 3324 import core.stdc.time : tzset; 3325 import core.sys.posix.stdlib : setenv; 3326 import std.internal.cstring : tempCString; 3327 import std.path : asNormalizedPath, chainPath; 3328 3329 version (Android) 3330 auto value = asNormalizedPath(tzDatabaseName); 3331 else 3332 auto value = asNormalizedPath(chainPath(PosixTimeZone.defaultTZDatabaseDir, tzDatabaseName)); 3333 setenv("TZ", value.tempCString(), 1); 3334 tzset(); 3335 } 3336 3337 3338 void clearTZEnvVar() @trusted nothrow 3339 { 3340 import core.stdc.time : tzset; 3341 import core.sys.posix.stdlib : unsetenv; 3342 3343 unsetenv("TZ"); 3344 tzset(); 3345 } 3346 } 3347 3348 3349 /++ 3350 Provides the conversions between the IANA time zone database time zone names 3351 (which POSIX systems use) and the time zone names that Windows uses. 3352 3353 Windows uses a different set of time zone names than the IANA time zone 3354 database does, and how they correspond to one another changes over time 3355 (particularly when Microsoft updates Windows). 3356 $(HTTP github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml, windowsZones.xml) 3357 provides the current conversions (which may or may not match up with what's 3358 on a particular Windows box depending on how up-to-date it is), and 3359 parseTZConversions reads in those conversions from windowsZones.xml so that 3360 a D program can use those conversions. 3361 3362 However, it should be noted that the time zone information on Windows is 3363 frequently less accurate than that in the IANA time zone database, and if 3364 someone really wants accurate time zone information, they should use the 3365 IANA time zone database files with $(LREF PosixTimeZone) on Windows rather 3366 than $(LREF WindowsTimeZone), whereas $(LREF WindowsTimeZone) makes more 3367 sense when trying to match what Windows will think the time is in a specific 3368 time zone. 3369 3370 Also, the IANA time zone database has a lot more time zones than Windows 3371 does. 3372 3373 Params: 3374 windowsZonesXMLText = The text from 3375 $(HTTP github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml, windowsZones.xml) 3376 3377 Throws: 3378 Exception if there is an error while parsing the given XML. 3379 3380 -------------------- 3381 // Parse the conversions from a local file. 3382 auto text = std.file.readText("path/to/windowsZones.xml"); 3383 auto conversions = parseTZConversions(text); 3384 3385 // Alternatively, grab the XML file from the web at runtime 3386 // and parse it so that it's guaranteed to be up-to-date, though 3387 // that has the downside that the code needs to worry about the 3388 // site being down or unicode.org changing the URL. 3389 auto url = "https://raw.githubusercontent.com/unicode-org/cldr/main/common/supplemental/windowsZones.xml"; 3390 auto conversions2 = parseTZConversions(std.net.curl.get(url)); 3391 -------------------- 3392 +/ 3393 struct TZConversions 3394 { 3395 /++ 3396 The key is the Windows time zone name, and the value is a list of 3397 IANA TZ database names which are close (currently only ever one, but 3398 it allows for multiple in case it's ever necessary). 3399 +/ 3400 string[][string] toWindows; 3401 3402 /++ 3403 The key is the IANA time zone database name, and the value is a list of 3404 Windows time zone names which are close (usually only one, but it could 3405 be multiple). 3406 +/ 3407 string[][string] fromWindows; 3408 } 3409 3410 /++ ditto +/ 3411 TZConversions parseTZConversions(string windowsZonesXMLText) @safe pure 3412 { 3413 // This is a bit hacky, since it doesn't properly read XML, but it avoids 3414 // needing to pull in an xml parsing module. 3415 import std.algorithm.iteration : uniq; 3416 import std.algorithm.searching : find; 3417 import std.algorithm.sorting : sort; 3418 import std.array : array, split; 3419 import std.string : lineSplitter; 3420 3421 string[][string] win2Nix; 3422 string[][string] nix2Win; 3423 3424 immutable f1 = `<mapZone other="`; 3425 immutable f2 = `type="`; 3426 3427 foreach (line; windowsZonesXMLText.lineSplitter()) 3428 { 3429 import std.exception : enforce; 3430 // Sample line: 3431 // <mapZone other="Canada Central Standard Time" territory="CA" type="America/Regina America/Swift_Current"/> 3432 3433 line = line.find(f1); 3434 if (line.empty) 3435 continue; 3436 line = line[f1.length .. $]; 3437 auto next = line.find('"'); 3438 enforce(!next.empty, "Error parsing. Text does not appear to be from windowsZones.xml"); 3439 auto win = line[0 .. $ - next.length]; 3440 line = next.find(f2); 3441 enforce(!line.empty, "Error parsing. Text does not appear to be from windowsZones.xml"); 3442 line = line[f2.length .. $]; 3443 next = line.find('"'); 3444 enforce(!next.empty, "Error parsing. Text does not appear to be from windowsZones.xml"); 3445 auto nixes = line[0 .. $ - next.length].split(); 3446 3447 if (auto n = win in win2Nix) 3448 *n ~= nixes; 3449 else 3450 win2Nix[win] = nixes; 3451 3452 foreach (nix; nixes) 3453 { 3454 if (auto w = nix in nix2Win) 3455 *w ~= win; 3456 else 3457 nix2Win[nix] = [win]; 3458 } 3459 } 3460 3461 foreach (key, ref value; nix2Win) 3462 value = value.sort().uniq().array(); 3463 foreach (key, ref value; win2Nix) 3464 value = value.sort().uniq().array(); 3465 3466 return TZConversions(nix2Win, win2Nix); 3467 } 3468 3469 @safe unittest 3470 { 3471 import std.algorithm.comparison : equal; 3472 import std.algorithm.iteration : uniq; 3473 import std.algorithm.sorting : isSorted; 3474 3475 // Reduced text from https://github.com/unicode-org/cldr/blob/main/common/supplemental/windowsZones.xml 3476 auto sampleFileText = 3477 `<?xml version="1.0" encoding="UTF-8" ?> 3478 <!DOCTYPE supplementalData SYSTEM "../../common/dtd/ldmlSupplemental.dtd"> 3479 <!-- 3480 Copyright © 1991-2013 Unicode, Inc. 3481 CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/) 3482 For terms of use, see http://www.unicode.org/copyright.html 3483 --> 3484 3485 <supplementalData> 3486 <version number="$Revision$"/> 3487 3488 <windowsZones> 3489 <mapTimezones otherVersion="7df0005" typeVersion="2015g"> 3490 3491 <!-- (UTC-12:00) International Date Line West --> 3492 <mapZone other="Dateline Standard Time" territory="001" type="Etc/GMT+12"/> 3493 <mapZone other="Dateline Standard Time" territory="ZZ" type="Etc/GMT+12"/> 3494 3495 <!-- (UTC-11:00) Coordinated Universal Time-11 --> 3496 <mapZone other="UTC-11" territory="001" type="Etc/GMT+11"/> 3497 <mapZone other="UTC-11" territory="AS" type="Pacific/Pago_Pago"/> 3498 <mapZone other="UTC-11" territory="NU" type="Pacific/Niue"/> 3499 <mapZone other="UTC-11" territory="UM" type="Pacific/Midway"/> 3500 <mapZone other="UTC-11" territory="ZZ" type="Etc/GMT+11"/> 3501 3502 <!-- (UTC-10:00) Hawaii --> 3503 <mapZone other="Hawaiian Standard Time" territory="001" type="Pacific/Honolulu"/> 3504 <mapZone other="Hawaiian Standard Time" territory="CK" type="Pacific/Rarotonga"/> 3505 <mapZone other="Hawaiian Standard Time" territory="PF" type="Pacific/Tahiti"/> 3506 <mapZone other="Hawaiian Standard Time" territory="UM" type="Pacific/Johnston"/> 3507 <mapZone other="Hawaiian Standard Time" territory="US" type="Pacific/Honolulu"/> 3508 <mapZone other="Hawaiian Standard Time" territory="ZZ" type="Etc/GMT+10"/> 3509 3510 <!-- (UTC-09:00) Alaska --> 3511 <mapZone other="Alaskan Standard Time" territory="001" type="America/Anchorage"/> 3512 <mapZone other="Alaskan Standard Time" territory="US" ` 3513 ~ `type="America/Anchorage America/Juneau America/Nome America/Sitka America/Yakutat"/> 3514 </mapTimezones> 3515 </windowsZones> 3516 </supplementalData>`; 3517 3518 auto tzConversions = parseTZConversions(sampleFileText); 3519 assert(tzConversions.toWindows.length == 15); 3520 assert(tzConversions.toWindows["America/Anchorage"] == ["Alaskan Standard Time"]); 3521 assert(tzConversions.toWindows["America/Juneau"] == ["Alaskan Standard Time"]); 3522 assert(tzConversions.toWindows["America/Nome"] == ["Alaskan Standard Time"]); 3523 assert(tzConversions.toWindows["America/Sitka"] == ["Alaskan Standard Time"]); 3524 assert(tzConversions.toWindows["America/Yakutat"] == ["Alaskan Standard Time"]); 3525 assert(tzConversions.toWindows["Etc/GMT+10"] == ["Hawaiian Standard Time"]); 3526 assert(tzConversions.toWindows["Etc/GMT+11"] == ["UTC-11"]); 3527 assert(tzConversions.toWindows["Etc/GMT+12"] == ["Dateline Standard Time"]); 3528 assert(tzConversions.toWindows["Pacific/Honolulu"] == ["Hawaiian Standard Time"]); 3529 assert(tzConversions.toWindows["Pacific/Johnston"] == ["Hawaiian Standard Time"]); 3530 assert(tzConversions.toWindows["Pacific/Midway"] == ["UTC-11"]); 3531 assert(tzConversions.toWindows["Pacific/Niue"] == ["UTC-11"]); 3532 assert(tzConversions.toWindows["Pacific/Pago_Pago"] == ["UTC-11"]); 3533 assert(tzConversions.toWindows["Pacific/Rarotonga"] == ["Hawaiian Standard Time"]); 3534 assert(tzConversions.toWindows["Pacific/Tahiti"] == ["Hawaiian Standard Time"]); 3535 3536 assert(tzConversions.fromWindows.length == 4); 3537 assert(tzConversions.fromWindows["Alaskan Standard Time"] == 3538 ["America/Anchorage", "America/Juneau", "America/Nome", "America/Sitka", "America/Yakutat"]); 3539 assert(tzConversions.fromWindows["Dateline Standard Time"] == ["Etc/GMT+12"]); 3540 assert(tzConversions.fromWindows["Hawaiian Standard Time"] == 3541 ["Etc/GMT+10", "Pacific/Honolulu", "Pacific/Johnston", "Pacific/Rarotonga", "Pacific/Tahiti"]); 3542 assert(tzConversions.fromWindows["UTC-11"] == 3543 ["Etc/GMT+11", "Pacific/Midway", "Pacific/Niue", "Pacific/Pago_Pago"]); 3544 3545 foreach (key, value; tzConversions.fromWindows) 3546 { 3547 assert(value.isSorted, key); 3548 assert(equal(value.uniq(), value), key); 3549 } 3550 }