1 module hunt.time.format.ZoneTextPrinterParser;
2 
3 import hunt.time.Instant;
4 import hunt.time.LocalDate;
5 import hunt.time.LocalDateTime;
6 import hunt.time.LocalTime;
7 
8 import hunt.time.format.DateTimeParseContext;
9 import hunt.time.format.DateTimePrinterParser;
10 import hunt.time.format.DateTimePrintContext;
11 import hunt.time.format.PrefixTree;
12 import hunt.time.format.TextStyle;
13 import hunt.time.format.ZoneIdPrinterParser;
14 import hunt.time.temporal.ChronoField;
15 import hunt.time.temporal.TemporalAccessor;
16 import hunt.time.temporal.TemporalField;
17 import hunt.time.temporal.TemporalQueries;
18 import hunt.time.util.Common;
19 import hunt.time.ZoneId;
20 import hunt.time.ZoneOffset;
21 import hunt.time.zone.ZoneRulesProvider;
22 
23 import hunt.Exceptions;
24 import hunt.collection.HashMap;
25 import hunt.collection.HashSet;
26 import hunt.collection.Map;
27 import hunt.collection.Set;
28 import hunt.Integer;
29 import hunt.util.StringBuilder;
30 import hunt.util.Locale;
31 
32 //-----------------------------------------------------------------------
33 /**
34 * Prints or parses a zone ID.
35 */
36 static final class ZoneTextPrinterParser : ZoneIdPrinterParser
37 {
38 
39     /** The text style to output. */
40     private TextStyle textStyle;
41 
42     /** The preferred zoneid map */
43     private Set!(string) preferredZones;
44 
45     /**  Display _in generic time-zone format. True _in case of pattern letter 'v' */
46     private bool isGeneric;
47 
48     this(TextStyle textStyle, Set!(ZoneId) preferredZones, bool isGeneric)
49     {
50         cachedTree = new HashMap!(Locale, MapEntry!(Integer, PrefixTree))();
51         cachedTreeCI = new HashMap!(Locale, MapEntry!(Integer, PrefixTree))();
52         super(TemporalQueries.zone(), "ZoneText(" ~ textStyle.toString ~ ")");
53         this.textStyle = textStyle;
54         this.isGeneric = isGeneric;
55         if (preferredZones !is null && preferredZones.size() != 0)
56         {
57             this.preferredZones = new HashSet!(string)();
58             foreach (ZoneId id; preferredZones)
59             {
60                 this.preferredZones.add(id.getId());
61             }
62         }
63     }
64 
65     private enum int STD = 0;
66     private enum int DST = 1;
67     private enum int GENERIC = 2;
68     // __gshared Map!(string, Map!(Locale, string[])) cache;
69 
70     // shared static this()
71     // {
72     //     cache = new HashMap!(string, Map!(Locale, string[]))();
73         mixin(MakeGlobalVar!(Map!(string, Map!(Locale, string[])))("cache",`new HashMap!(string, Map!(Locale, string[]))()`));
74     // }
75 
76     private string getDisplayName(string id, int type, Locale locale)
77     {
78         if (textStyle == TextStyle.NARROW)
79         {
80             return null;
81         }
82         string[] names;
83         Map!(Locale, string[]) _ref = cache.get(id);
84         Map!(Locale, string[]) perLocale = null;
85         if (_ref is null || (perLocale = _ref) is null
86                 || (names = perLocale.get(locale)) is null)
87         {
88             // names = TimeZoneNameUtility.retrieveDisplayNames(id, locale);
89             // if (names is null) {
90             //     return null;
91             // }
92             // auto tmp = names[0 .. 7];
93             // names = tmp;
94             // names[5] =
95             //     TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.LONG, locale);
96             // if (names[5] is null) {
97             //     names[5] = names[0]; // use the id
98             // }
99             // names[6] =
100             //     TimeZoneNameUtility.retrieveGenericDisplayName(id, TimeZone.SHORT, locale);
101             // if (names[6] is null) {
102             //     names[6] = names[0];
103             // }
104             // if (perLocale is null) {
105             //     perLocale = new HashMap!(Locale, string[])();
106             // }
107             // perLocale.put(locale, names);
108             // cache.put(id, perLocale);
109             implementationMissing();
110 
111         }
112         switch (type)
113         {
114         case STD:
115             return names[textStyle.zoneNameStyleIndex() + 1];
116         case DST:
117             return names[textStyle.zoneNameStyleIndex() + 3];
118         default:
119             break;
120         }
121         return names[textStyle.zoneNameStyleIndex() + 5];
122     }
123 
124     override public bool format(DateTimePrintContext context, StringBuilder buf)
125     {
126         ZoneId zone = context.getValue(TemporalQueries.zoneId());
127         if (zone is null)
128         {
129             return false;
130         }
131         string zname = zone.getId();
132         if (!(cast(ZoneOffset)(zone) !is null))
133         {
134             TemporalAccessor dt = context.getTemporal();
135             int type = GENERIC;
136             if (!isGeneric)
137             {
138                 if (dt.isSupported(ChronoField.INSTANT_SECONDS))
139                 {
140                     type = zone.getRules().isDaylightSavings(Instant.from(dt)) ? DST : STD;
141                 }
142                 else if (dt.isSupported(ChronoField.EPOCH_DAY)
143                         && dt.isSupported(ChronoField.NANO_OF_DAY))
144                 {
145                     LocalDate date = LocalDate.ofEpochDay(
146                             dt.getLong(ChronoField.EPOCH_DAY));
147                     LocalTime time = LocalTime.ofNanoOfDay(
148                             dt.getLong(ChronoField.NANO_OF_DAY));
149                     LocalDateTime ldt = date.atTime_s(time);
150                     if (zone.getRules().getTransition(ldt) is null)
151                     {
152                         type = zone.getRules().isDaylightSavings(ldt.atZone(zone)
153                                 .toInstant()) ? DST : STD;
154                     }
155                 }
156             }
157             string name = getDisplayName(zname, type, context.getLocale());
158             if (name !is null)
159             {
160                 zname = name;
161             }
162         }
163         buf.append(zname);
164         return true;
165     }
166 
167     // cache per instance for now
168     private Map!(Locale, MapEntry!(Integer, PrefixTree)) cachedTree;
169     private Map!(Locale, MapEntry!(Integer, PrefixTree)) cachedTreeCI;
170 
171     override protected PrefixTree getTree(DateTimeParseContext context)
172     {
173         if (textStyle == TextStyle.NARROW)
174         {
175             return super.getTree(context);
176         }
177         Locale locale = context.getLocale();
178         bool isCaseSensitive = context.isCaseSensitive();
179         Set!(string) regionIds = ZoneRulesProvider.getAvailableZoneIds();
180         int regionIdsSize = regionIds.size();
181 
182         Map!(Locale, MapEntry!(Integer, PrefixTree)) cached = isCaseSensitive ? cachedTree
183             : cachedTreeCI;
184 
185         MapEntry!(Integer, PrefixTree) entry = null;
186         PrefixTree tree = null;
187         string[][] zoneStrings = null;
188         if ((entry = cached.get(locale)) is null
189                 || (entry.getKey() != regionIdsSize || (tree = entry.getValue() /* .get() */ ) is null))
190         {
191             tree = PrefixTree.newTree(context);
192             // zoneStrings = TimeZoneNameUtility.getZoneStrings(locale);
193             // foreach(string[] names ; zoneStrings) {
194             //     string zid = names[0];
195             //     if (!regionIds.contains(zid)) {
196             //         continue;
197             //     }
198             //     tree.add(zid, zid);    // don't convert zid -> metazone
199             //     zid = ZoneName.toZid(zid, locale);
200             //     int i = textStyle == TextStyle.FULL ? 1 : 2;
201             //     for (; i < names.length; i += 2) {
202             //         tree.add(names[i], zid);
203             //     }
204             // }
205 
206             // // if we have a set of preferred zones, need a copy and
207             // // add the preferred zones again to overwrite
208             // if (preferredZones !is null) {
209             //     foreach(string[] names ; zoneStrings) {
210             //         string zid = names[0];
211             //         if (!preferredZones.contains(zid) || !regionIds.contains(zid)) {
212             //             continue;
213             //         }
214             //         int i = textStyle == TextStyle.FULL ? 1 : 2;
215             //         for (; i < names.length; i += 2) {
216             //             tree.add(names[i], zid);
217             //        }
218             //     }
219             // }
220             // cached.put(locale, new SimpleImmutableEntry!(Integer, PrefixTree)(new Integer(regionIdsSize), tree));
221             
222 
223         }
224 
225         implementationMissing(false);
226         return tree;
227     }
228 }