1 /*
2  * hunt-time: A time library for D programming language.
3  *
4  * Copyright (C) 2015-2018 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.time.zone.TzdbZoneRulesProvider;
13 
14 
15 import hunt.stream.ByteArrayInputStream;
16 import hunt.stream.BufferedInputStream;
17 import hunt.stream.DataInputStream;
18 import hunt.String;
19 import hunt.stream.FileInputStream;
20 // import hunt.io.StreamCorruptedException;
21 import hunt.time.zone.ZoneRulesException;
22 import hunt.collection;
23 import hunt.time.util;
24 import hunt.time.zone.ZoneRulesProvider;
25 import hunt.time.zone.ZoneRules;
26 import hunt.time.zone.Ser;
27 
28 import hunt.logging.ConsoleLogger;
29 
30 import std.conv;
31 import std.file;
32 import std.path;
33 import std.stdio;
34 
35 /**
36  * Loads time-zone rules for 'TZDB'.
37  *
38  * @since 1.8
39  */
40 final class TzdbZoneRulesProvider : ZoneRulesProvider {
41 
42     // mixin MakeServiceLoader!ZoneRulesProvider;
43     /**
44      * All the regions that are available.
45      */
46     private List!(string) regionIds;
47     /**
48      * Version Id of this tzdb rules
49      */
50     private string versionId;
51     /**
52      * Region to rules mapping
53      */
54     private  Map!(string, Object) regionToRules ;
55 
56 
57 
58     /**
59      * Creates an instance.
60      * Created by the {@code ServiceLoader}.
61      *
62      * @throws ZoneRulesException if unable to load
63      */
64     public this() {
65         regionIds = new ArrayList!string();
66 
67         regionToRules = new HashMap!(string, Object)();
68         //@gxc load timezone database
69         
70         try {
71             string resourcePath = dirName(thisExePath()) ~ "/resources";
72             string resourceName = buildPath(resourcePath, "tzdb.dat");
73             version(HUNT_DEBUG) infof("loading timezone from: %s", resourceName);
74             if(!exists(resourceName)) {
75                 version(HUNT_DEBUG) warningf("File does not exist: %s", resourceName);
76             } else {
77                 File f = File(resourceName,"r");
78                 scope(exit) f.close();
79                 DataInputStream dis = new DataInputStream(
80                      new BufferedInputStream(new FileInputStream(f)));
81                 load(dis);
82             }
83         } catch (Exception ex) {
84             throw new ZoneRulesException("Unable to load TZDB time-zone rules", ex);
85         } 
86        
87 
88     }
89 
90     override
91     protected Set!(string) provideZoneIds() {
92         return new HashSet!(string)(regionIds);
93     }
94 
95     override
96     protected ZoneRules provideRules(string zoneId, bool forCaching) {
97         // forCaching flag is ignored because this is not a dynamic provider
98         auto obj = regionToRules.get(zoneId);
99         if (obj is null) {
100             throw new ZoneRulesException("Unknown time-zone ID: " ~ zoneId);
101         }
102         try {
103             if (cast(String)(obj) !is null) {
104                 byte[] bytes = cast(byte[]) ((cast(String)obj).get());
105                 DataInputStream dis = new DataInputStream(new ByteArrayInputStream(bytes));
106                 obj = Ser.read(dis);
107                 regionToRules.put(zoneId, obj);
108             }
109             return cast(ZoneRules) obj;
110         } catch (Exception ex) {
111             warning(ex.msg);
112             throw new ZoneRulesException("Invalid binary time-zone data: TZDB:" ~ 
113                 zoneId ~ ", version: " ~ versionId, ex);
114         }
115     }
116 
117     override
118     protected NavigableMap!(string, ZoneRules) provideVersions(string zoneId) {
119         TreeMap!(string, ZoneRules) map = new TreeMap!(string, ZoneRules)();
120         ZoneRules rules = getRules(zoneId, false);
121         if (rules !is null) {
122             map.put(versionId, rules);
123         }
124         return map;
125     }
126 
127     /**
128      * Loads the rules from a DateInputStream, often _in a jar file.
129      *
130      * @param dis  the DateInputStream to load, not null
131      * @ if an error occurs
132      */
133     private void load(DataInputStream dis)  {
134         if (dis.readByte() != 1) {
135             throw new Exception("File format not recognised");
136         }
137         // group
138         string groupId = dis.readUTF();
139         if (("TZDB" == groupId) == false) {
140             throw new Exception("File format not recognised");
141         }
142         // versions
143         int versionCount = dis.readShort();
144         for (int i = 0; i < versionCount; i++) {
145             versionId = dis.readUTF();
146         }
147         // regions
148         int regionCount = dis.readShort();
149         string[] regionArray = new string[regionCount];
150         for (int i = 0; i < regionCount; i++) {
151             regionArray[i] = dis.readUTF();
152         }
153         regionIds = new ArrayList!string();
154         foreach(d ; regionArray)
155             regionIds.add(d);
156         // rules
157         int ruleCount = dis.readShort();
158         Object[] ruleArray = new Object[ruleCount];
159         for (int i = 0; i < ruleCount; i++) {
160             byte[] bytes = new byte[dis.readShort()];
161             dis.readFully(bytes);
162             // import hunt.logging;
163             // trace(" idx : ",i," rule : ",bytes);
164             ruleArray[i] = new String(cast(string)bytes);
165         }
166         // link version-region-rules
167         for (int i = 0; i < versionCount; i++) {
168             int versionRegionCount = dis.readShort();
169             regionToRules.clear();
170             for (int j = 0; j < versionRegionCount; j++) {
171                 string region = regionArray[dis.readShort()];
172                 auto rule = ruleArray[dis.readShort() & 0xffff];
173                 regionToRules.put(region, rule);
174             }
175         }
176 
177         // import hunt.logging;
178         // trace("regionIds :  ",regionIds);
179         // foreach(r , o; regionToRules )
180         // {
181         //     trace("id :  ",r," rule : ",o.toString);
182         // }
183     }
184 
185     override
186     public string toString() {
187         return "TZDB[" ~ versionId ~ "]";
188     }
189 }