• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (C) 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 *******************************************************************************
5 *
6 *   Copyright (C) 2007-2016, International Business Machines
7 *   Corporation and others.  All Rights Reserved.
8 *
9 *******************************************************************************
10 *   file name:  icuzdump.cpp
11 *   encoding:   US-ASCII
12 *   tab size:   8 (not used)
13 *   indentation:4
14 *
15 *   created on: 2007-04-02
16 *   created by: Yoshito Umaoka
17 *
18 *   This tool write out timezone transitions for ICU timezone.  This tool
19 *   is used as a part of tzdata update process to check if ICU timezone
20 *   code works as well as the corresponding Olson stock localtime/zdump.
21 */
22 
23 #include <cstdlib>
24 #include <cstring>
25 #include <fstream>
26 #include <sstream>
27 #include <iostream>
28 
29 #include "unicode/utypes.h"
30 #include "unicode/ustring.h"
31 #include "unicode/timezone.h"
32 #include "unicode/simpletz.h"
33 #include "unicode/smpdtfmt.h"
34 #include "unicode/decimfmt.h"
35 #include "unicode/gregocal.h"
36 #include "unicode/ustream.h"
37 #include "unicode/putil.h"
38 
39 #include "cmemory.h"
40 #include "uoptions.h"
41 
42 using namespace std;
43 
44 class DumpFormatter {
45 public:
DumpFormatter()46     DumpFormatter() {
47         UErrorCode status = U_ZERO_ERROR;
48         stz = new SimpleTimeZone(0, "");
49         sdf = new SimpleDateFormat((UnicodeString)"yyyy-MM-dd EEE HH:mm:ss", Locale::getEnglish(), status);
50         DecimalFormatSymbols *symbols = new DecimalFormatSymbols(Locale::getEnglish(), status);
51         decf = new DecimalFormat("00", symbols, status);
52     }
~DumpFormatter()53     ~DumpFormatter() {
54     }
55 
format(UDate time,int32_t offset,UBool isDst,UnicodeString & appendTo)56     UnicodeString& format(UDate time, int32_t offset, UBool isDst, UnicodeString& appendTo) {
57         stz->setRawOffset(offset);
58         sdf->setTimeZone(*stz);
59         UnicodeString str = sdf->format(time, appendTo);
60         if (offset < 0) {
61             appendTo += "-";
62             offset = -offset;
63         } else {
64             appendTo += "+";
65         }
66 
67         int32_t hour, min, sec;
68 
69         offset /= 1000;
70         sec = offset % 60;
71         offset = (offset - sec) / 60;
72         min = offset % 60;
73         hour = offset / 60;
74 
75         decf->format(hour, appendTo);
76         decf->format(min, appendTo);
77         decf->format(sec, appendTo);
78         appendTo += "[DST=";
79         if (isDst) {
80             appendTo += "1";
81         } else {
82             appendTo += "0";
83         }
84         appendTo += "]";
85         return appendTo;
86     }
87 private:
88     SimpleTimeZone*     stz;
89     SimpleDateFormat*   sdf;
90     DecimalFormat*      decf;
91 };
92 
93 class ICUZDump {
94 public:
ICUZDump()95     ICUZDump() {
96         formatter = new DumpFormatter();
97         loyear = 1902;
98         hiyear = 2050;
99         tick = 1000;
100         linesep = NULL;
101     }
102 
~ICUZDump()103     ~ICUZDump() {
104     }
105 
setLowYear(int32_t lo)106     void setLowYear(int32_t lo) {
107         loyear = lo;
108     }
109 
setHighYear(int32_t hi)110     void setHighYear(int32_t hi) {
111         hiyear = hi;
112     }
113 
setTick(int32_t t)114     void setTick(int32_t t) {
115         tick = t;
116     }
117 
setTimeZone(TimeZone * tz)118     void setTimeZone(TimeZone* tz) {
119         timezone = tz;
120     }
121 
setDumpFormatter(DumpFormatter * fmt)122     void setDumpFormatter(DumpFormatter* fmt) {
123         formatter = fmt;
124     }
125 
setLineSeparator(const char * sep)126     void setLineSeparator(const char* sep) {
127         linesep = sep;
128     }
129 
dump(ostream & out)130     void dump(ostream& out) {
131         UErrorCode status = U_ZERO_ERROR;
132         UDate SEARCH_INCREMENT = 12 * 60 * 60 * 1000; // half day
133         UDate t, cutlo, cuthi;
134         int32_t rawOffset, dstOffset;
135         UnicodeString str;
136 
137         getCutOverTimes(cutlo, cuthi);
138         t = cutlo;
139         timezone->getOffset(t, FALSE, rawOffset, dstOffset, status);
140         while (t < cuthi) {
141             int32_t newRawOffset, newDstOffset;
142             UDate newt = t + SEARCH_INCREMENT;
143 
144             timezone->getOffset(newt, FALSE, newRawOffset, newDstOffset, status);
145 
146             UBool bSameOffset = (rawOffset + dstOffset) == (newRawOffset + newDstOffset);
147             UBool bSameDst = ((dstOffset != 0) && (newDstOffset != 0)) || ((dstOffset == 0) && (newDstOffset == 0));
148 
149             if (!bSameOffset || !bSameDst) {
150                 // find the boundary
151                 UDate lot = t;
152                 UDate hit = newt;
153                 while (true) {
154                     int32_t diff = (int32_t)(hit - lot);
155                     if (diff <= tick) {
156                         break;
157                     }
158                     UDate medt = lot + ((diff / 2) / tick) * tick;
159                     int32_t medRawOffset, medDstOffset;
160                     timezone->getOffset(medt, FALSE, medRawOffset, medDstOffset, status);
161 
162                     bSameOffset = (rawOffset + dstOffset) == (medRawOffset + medDstOffset);
163                     bSameDst = ((dstOffset != 0) && (medDstOffset != 0)) || ((dstOffset == 0) && (medDstOffset == 0));
164 
165                     if (!bSameOffset || !bSameDst) {
166                         hit = medt;
167                     } else {
168                         lot = medt;
169                     }
170                 }
171                 // write out the boundary
172                 str.remove();
173                 formatter->format(lot, rawOffset + dstOffset, (dstOffset == 0 ? FALSE : TRUE), str);
174                 out << str << " > ";
175                 str.remove();
176                 formatter->format(hit, newRawOffset + newDstOffset, (newDstOffset == 0 ? FALSE : TRUE), str);
177                 out << str;
178                 if (linesep != NULL) {
179                     out << linesep;
180                 } else {
181                     out << endl;
182                 }
183 
184                 rawOffset = newRawOffset;
185                 dstOffset = newDstOffset;
186             }
187             t = newt;
188         }
189     }
190 
191 private:
getCutOverTimes(UDate & lo,UDate & hi)192     void getCutOverTimes(UDate& lo, UDate& hi) {
193         UErrorCode status = U_ZERO_ERROR;
194         GregorianCalendar* gcal = new GregorianCalendar(timezone, Locale::getEnglish(), status);
195         gcal->clear();
196         gcal->set(loyear, 0, 1, 0, 0, 0);
197         lo = gcal->getTime(status);
198         gcal->set(hiyear, 0, 1, 0, 0, 0);
199         hi = gcal->getTime(status);
200     }
201 
202     TimeZone*   timezone;
203     int32_t     loyear;
204     int32_t     hiyear;
205     int32_t     tick;
206 
207     DumpFormatter*  formatter;
208     const char*  linesep;
209 };
210 
211 class ZoneIterator {
212 public:
ZoneIterator(UBool bAll=FALSE)213     ZoneIterator(UBool bAll = FALSE) {
214         if (bAll) {
215             zenum = TimeZone::createEnumeration();
216         }
217         else {
218             zenum = NULL;
219             zids = NULL;
220             idx = 0;
221             numids = 1;
222         }
223     }
224 
ZoneIterator(const char ** ids,int32_t num)225     ZoneIterator(const char** ids, int32_t num) {
226         zenum = NULL;
227         zids = ids;
228         idx = 0;
229         numids = num;
230     }
231 
~ZoneIterator()232     ~ZoneIterator() {
233         if (zenum != NULL) {
234             delete zenum;
235         }
236     }
237 
next()238     TimeZone* next() {
239         TimeZone* tz = NULL;
240         if (zenum != NULL) {
241             UErrorCode status = U_ZERO_ERROR;
242             const UnicodeString* zid = zenum->snext(status);
243             if (zid != NULL) {
244                 tz = TimeZone::createTimeZone(*zid);
245             }
246         }
247         else {
248             if (idx < numids) {
249                 if (zids != NULL) {
250                     tz = TimeZone::createTimeZone((const UnicodeString&)zids[idx]);
251                 }
252                 else {
253                     tz = TimeZone::createDefault();
254                 }
255                 idx++;
256             }
257         }
258         return tz;
259     }
260 
261 private:
262     const char** zids;
263     StringEnumeration* zenum;
264     int32_t idx;
265     int32_t numids;
266 };
267 
268 enum {
269   kOptHelpH = 0,
270   kOptHelpQuestionMark,
271   kOptAllZones,
272   kOptCutover,
273   kOptDestDir,
274   kOptLineSep
275 };
276 
277 static UOption options[]={
278     UOPTION_HELP_H,
279     UOPTION_HELP_QUESTION_MARK,
280     UOPTION_DEF("allzones", 'a', UOPT_NO_ARG),
281     UOPTION_DEF("cutover", 'c', UOPT_REQUIRES_ARG),
282     UOPTION_DEF("destdir", 'd', UOPT_REQUIRES_ARG),
283     UOPTION_DEF("linesep", 'l', UOPT_REQUIRES_ARG)
284 };
285 
286 extern int
main(int argc,char * argv[])287 main(int argc, char *argv[]) {
288     int32_t low = 1902;
289     int32_t high = 2038;
290     UBool bAll = FALSE;
291     const char *dir = NULL;
292     const char *linesep = NULL;
293 
294     U_MAIN_INIT_ARGS(argc, argv);
295     argc = u_parseArgs(argc, argv, UPRV_LENGTHOF(options), options);
296 
297     if (argc < 0) {
298         cerr << "Illegal command line argument(s)" << endl << endl;
299     }
300 
301     if (argc < 0 || options[kOptHelpH].doesOccur || options[kOptHelpQuestionMark].doesOccur) {
302         cerr
303             << "Usage: icuzdump [-options] [zoneid1 zoneid2 ...]" << endl
304             << endl
305             << "\tDump all offset transitions for the specified zones." << endl
306             << endl
307             << "Options:" << endl
308             << "\t-a       : Dump all available zones." << endl
309             << "\t-d <dir> : When specified, write transitions in a file under" << endl
310             << "\t           the directory for each zone." << endl
311             << "\t-l <sep> : New line code type used in file outputs. CR or LF (default)"
312             << "\t           or CRLF." << endl
313             << "\t-c [<low_year>,]<high_year>" << endl
314             << "\t         : When specified, dump transitions starting <low_year>" << endl
315             << "\t           (inclusive) up to <high_year> (exclusive).  The default" << endl
316             << "\t           values are 1902(low) and 2038(high)." << endl;
317         return argc < 0 ? U_ILLEGAL_ARGUMENT_ERROR : U_ZERO_ERROR;
318     }
319 
320     bAll = options[kOptAllZones].doesOccur;
321 
322     if (options[kOptDestDir].doesOccur) {
323         dir = options[kOptDestDir].value;
324     }
325 
326     if (options[kOptLineSep].doesOccur) {
327         if (strcmp(options[kOptLineSep].value, "CR") == 0) {
328             linesep = "\r";
329         } else if (strcmp(options[kOptLineSep].value, "CRLF") == 0) {
330             linesep = "\r\n";
331         } else if (strcmp(options[kOptLineSep].value, "LF") == 0) {
332             linesep = "\n";
333         }
334     }
335 
336     if (options[kOptCutover].doesOccur) {
337         char* comma = (char*)strchr(options[kOptCutover].value, ',');
338         if (comma == NULL) {
339             high = atoi(options[kOptCutover].value);
340         } else {
341             *comma = 0;
342             low = atoi(options[kOptCutover].value);
343             high = atoi(comma + 1);
344         }
345     }
346 
347     ICUZDump dumper;
348     dumper.setLowYear(low);
349     dumper.setHighYear(high);
350     if (dir != NULL && linesep != NULL) {
351         // use the specified line separator only for file output
352         dumper.setLineSeparator((const char*)linesep);
353     }
354 
355     ZoneIterator* zit;
356     if (bAll) {
357         zit = new ZoneIterator(TRUE);
358     } else {
359         if (argc <= 1) {
360             zit = new ZoneIterator();
361         } else {
362             zit = new ZoneIterator((const char**)&argv[1], argc - 1);
363         }
364     }
365 
366     UnicodeString id;
367     if (dir != NULL) {
368         // file output
369         ostringstream path;
370         ios::openmode mode = ios::out;
371         if (linesep != NULL) {
372             mode |= ios::binary;
373         }
374         for (;;) {
375             TimeZone* tz = zit->next();
376             if (tz == NULL) {
377                 break;
378             }
379             dumper.setTimeZone(tz);
380             tz->getID(id);
381 
382             // target file path
383             path.str("");
384             path << dir << U_FILE_SEP_CHAR;
385             id = id.findAndReplace("/", "-");
386             path << id;
387 
388             ofstream* fout = new ofstream(path.str().c_str(), mode);
389             if (fout->fail()) {
390                 cerr << "Cannot open file " << path.str() << endl;
391                 delete fout;
392                 delete tz;
393                 break;
394             }
395 
396             dumper.dump(*fout);
397             fout->close();
398             delete fout;
399             delete tz;
400         }
401 
402     } else {
403         // stdout
404         UBool bFirst = TRUE;
405         for (;;) {
406             TimeZone* tz = zit->next();
407             if (tz == NULL) {
408                 break;
409             }
410             dumper.setTimeZone(tz);
411             tz->getID(id);
412             if (bFirst) {
413                 bFirst = FALSE;
414             } else {
415                 cout << endl;
416             }
417             cout << "ZONE: " << id << endl;
418             dumper.dump(cout);
419             delete tz;
420         }
421     }
422     delete zit;
423 }
424