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