1 // Written in the D programming language.
2 /**
3 Timezone realted data and code.
4 
5 Copyright: Copyright (c) 2017, Radu Racariu <radu.racariu@gmail.com>
6 License:   $(LINK2 www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7 Authors:   Radu Racariu
8 **/
9 module haystack.util.tzdata;
10 
11 import std.uni      : sicmp;
12 import std.datetime : TimeZone, UTC;
13 
14 private string cityName(string fullName) pure
15 {
16     import std.string : lastIndexOf;
17     if (fullName.sicmp("STD") == 0)
18         return "UTC";
19     return fullName[fullName.lastIndexOf('/') + 1 .. $];
20 }
21 
22 version(Posix)
23 {
24     import std.datetime : PosixTimeZone;
25 
26     immutable bool hasTzData;
27     immutable string[string] shortNames;
28 
29     shared static this()
30     {
31         import std.string   : lineSplitter;
32         import std.stdio    : writeln;
33         try
34         {
35             if (PosixTimeZone.getInstalledTZNames().length == 0)
36             {
37                 writeln("Warning, no timezone data detected! Falling back to UTC.");
38             }
39             else
40             {
41                 hasTzData = true;
42                 // data from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
43                 foreach (tzName; import("timzoneList.txt").lineSplitter())
44                 {
45                     shortNames[cityName(tzName)] = tzName;
46                 }
47             }
48         }
49         catch (Exception e)
50         {
51             hasTzData = false;
52             writeln("Warning, no timezone data detected! Falling back to UTC. Details: ", e);
53         }
54     }
55 
56     static immutable(TimeZone) timeZone(string name)
57     {
58         import std.string : indexOf;
59 
60         if (!hasTzData || name.sicmp("UTC") == 0 || name.sicmp("STD") == 0)
61             return UTC();
62         if (name.indexOf('/') != -1)
63         {
64             return PosixTimeZone.getTimeZone(name);
65         }
66         else
67         {
68             const fullName = shortNames[name];
69             return PosixTimeZone.getTimeZone(fullName);
70         }
71     }
72 
73     static string getTimeZoneName(immutable(TimeZone) tz)
74     {
75         return cityName(tz.name.length ? tz.name : tz.stdName);
76     }
77 }
78 
79 version (Windows)
80 {
81     import std.datetime : WindowsTimeZone,
82                           parseTZConversions,
83                           TZConversions;
84 
85     /**
86     Implemenst the `TimeZone` interface by adding the accurate time zone info
87     */
88     final class HaystackTimeZone : TimeZone
89     {
90         immutable this(string tzName, immutable(WindowsTimeZone) windowsTz)
91         {
92             super(windowsTz.name, windowsTz.stdName, windowsTz.dstName);
93             this.cityName   = .cityName(tzName);
94             _tzName         = tzName;
95             _tz             = windowsTz;
96         }
97 
98         override @property string name() @safe const nothrow
99         {
100             return _tzName;
101         }
102 
103         override bool hasDST() const nothrow @property @safe
104         {
105             return _tz.hasDST();
106         }
107         
108         override bool dstInEffect(long stdTime) const nothrow @safe
109         {
110             return _tz.dstInEffect(stdTime);
111         }
112         
113         override long utcToTZ(long stdTime) const nothrow @safe
114         {
115             return _tz.utcToTZ(stdTime);
116         }
117         
118         override long tzToUTC(long adjTime) const nothrow @safe
119         {
120             return _tz.tzToUTC(adjTime);
121         }
122 
123         immutable(string) cityName;
124 
125     private:
126         immutable(string) _tzName;
127         immutable(WindowsTimeZone) _tz;
128     }
129 
130     immutable(TimeZone) timeZone(string name)
131     {
132         if (name.sicmp("UTC") == 0)
133             return UTC();
134 
135         if (name in conv.toWindows)
136             return new immutable HaystackTimeZone(name, WindowsTimeZone.getTimeZone(conv.toWindows[name][0]));
137         else
138             return getTimeZone(name);
139     }
140 
141     string getTimeZoneName(immutable(TimeZone) tz)
142     {
143         immutable name = tz.name;
144         if (!name.length || name == "Coordinated Universal Time")
145             return "UTC";
146         if (cast(HaystackTimeZone) tz)
147             return (cast(HaystackTimeZone) tz).cityName;
148         return cityName(name);
149     }
150 
151     private immutable(TimeZone) getTimeZone(string name)
152     {
153         immutable tz = name in shortNames;
154         return tz !is null ? *tz : null;
155     }
156 
157     private immutable TZConversions conv;
158     private immutable TimeZone[string] shortNames;
159 
160     shared static this()
161     {
162         // Source of the definitions:
163         // https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml
164         conv = parseTZConversions(import("windowsZones.xml"));
165         foreach (timeZones; conv.fromWindows.byValue)
166         {
167             foreach (timeZoneName; timeZones)
168             {
169                 const shortName         = cityName(timeZoneName);
170                 const windowsTzNames    = conv.toWindows[timeZoneName];
171                 const windowsTzName     = windowsTzNames[0];
172                 const windowsTz         = WindowsTimeZone.getTimeZone(windowsTzName);
173                 const tz                = new immutable HaystackTimeZone(timeZoneName, windowsTz);
174                 shortNames[shortName]   = tz;
175             }
176         }
177     }
178 }