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 }