michael@0: /* michael@0: ******************************************************************************* michael@0: * michael@0: * Copyright (C) 2007, International Business Machines michael@0: * Corporation and others. All Rights Reserved. michael@0: * michael@0: ******************************************************************************* michael@0: * file name: icuzdump.cpp michael@0: * encoding: US-ASCII michael@0: * tab size: 8 (not used) michael@0: * indentation:4 michael@0: * michael@0: * created on: 2007-04-02 michael@0: * created by: Yoshito Umaoka michael@0: * michael@0: * This tool write out timezone transitions for ICU timezone. This tool michael@0: * is used as a part of tzdata update process to check if ICU timezone michael@0: * code works as well as the corresponding Olson stock localtime/zdump. michael@0: */ michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "unicode/utypes.h" michael@0: #include "unicode/ustring.h" michael@0: #include "unicode/timezone.h" michael@0: #include "unicode/simpletz.h" michael@0: #include "unicode/smpdtfmt.h" michael@0: #include "unicode/decimfmt.h" michael@0: #include "unicode/gregocal.h" michael@0: #include "unicode/ustream.h" michael@0: #include "unicode/putil.h" michael@0: michael@0: #include "uoptions.h" michael@0: michael@0: using namespace std; michael@0: michael@0: class DumpFormatter { michael@0: public: michael@0: DumpFormatter() { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: stz = new SimpleTimeZone(0, ""); michael@0: sdf = new SimpleDateFormat((UnicodeString)"yyyy-MM-dd EEE HH:mm:ss", Locale::getEnglish(), status); michael@0: DecimalFormatSymbols *symbols = new DecimalFormatSymbols(Locale::getEnglish(), status); michael@0: decf = new DecimalFormat("00", symbols, status); michael@0: } michael@0: ~DumpFormatter() { michael@0: } michael@0: michael@0: UnicodeString& format(UDate time, int32_t offset, UBool isDst, UnicodeString& appendTo) { michael@0: stz->setRawOffset(offset); michael@0: sdf->setTimeZone(*stz); michael@0: UnicodeString str = sdf->format(time, appendTo); michael@0: if (offset < 0) { michael@0: appendTo += "-"; michael@0: offset = -offset; michael@0: } else { michael@0: appendTo += "+"; michael@0: } michael@0: michael@0: int32_t hour, min, sec; michael@0: michael@0: offset /= 1000; michael@0: sec = offset % 60; michael@0: offset = (offset - sec) / 60; michael@0: min = offset % 60; michael@0: hour = offset / 60; michael@0: michael@0: decf->format(hour, appendTo); michael@0: decf->format(min, appendTo); michael@0: decf->format(sec, appendTo); michael@0: appendTo += "[DST="; michael@0: if (isDst) { michael@0: appendTo += "1"; michael@0: } else { michael@0: appendTo += "0"; michael@0: } michael@0: appendTo += "]"; michael@0: return appendTo; michael@0: } michael@0: private: michael@0: SimpleTimeZone* stz; michael@0: SimpleDateFormat* sdf; michael@0: DecimalFormat* decf; michael@0: }; michael@0: michael@0: class ICUZDump { michael@0: public: michael@0: ICUZDump() { michael@0: formatter = new DumpFormatter(); michael@0: loyear = 1902; michael@0: hiyear = 2050; michael@0: tick = 1000; michael@0: linesep = NULL; michael@0: } michael@0: michael@0: ~ICUZDump() { michael@0: } michael@0: michael@0: void setLowYear(int32_t lo) { michael@0: loyear = lo; michael@0: } michael@0: michael@0: void setHighYear(int32_t hi) { michael@0: hiyear = hi; michael@0: } michael@0: michael@0: void setTick(int32_t t) { michael@0: tick = t; michael@0: } michael@0: michael@0: void setTimeZone(TimeZone* tz) { michael@0: timezone = tz; michael@0: } michael@0: michael@0: void setDumpFormatter(DumpFormatter* fmt) { michael@0: formatter = fmt; michael@0: } michael@0: michael@0: void setLineSeparator(const char* sep) { michael@0: linesep = sep; michael@0: } michael@0: michael@0: void dump(ostream& out) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UDate SEARCH_INCREMENT = 12 * 60 * 60 * 1000; // half day michael@0: UDate t, cutlo, cuthi; michael@0: int32_t rawOffset, dstOffset; michael@0: UnicodeString str; michael@0: michael@0: getCutOverTimes(cutlo, cuthi); michael@0: t = cutlo; michael@0: timezone->getOffset(t, FALSE, rawOffset, dstOffset, status); michael@0: while (t < cuthi) { michael@0: int32_t newRawOffset, newDstOffset; michael@0: UDate newt = t + SEARCH_INCREMENT; michael@0: michael@0: timezone->getOffset(newt, FALSE, newRawOffset, newDstOffset, status); michael@0: michael@0: UBool bSameOffset = (rawOffset + dstOffset) == (newRawOffset + newDstOffset); michael@0: UBool bSameDst = ((dstOffset != 0) && (newDstOffset != 0)) || ((dstOffset == 0) && (newDstOffset == 0)); michael@0: michael@0: if (!bSameOffset || !bSameDst) { michael@0: // find the boundary michael@0: UDate lot = t; michael@0: UDate hit = newt; michael@0: while (true) { michael@0: int32_t diff = (int32_t)(hit - lot); michael@0: if (diff <= tick) { michael@0: break; michael@0: } michael@0: UDate medt = lot + ((diff / 2) / tick) * tick; michael@0: int32_t medRawOffset, medDstOffset; michael@0: timezone->getOffset(medt, FALSE, medRawOffset, medDstOffset, status); michael@0: michael@0: bSameOffset = (rawOffset + dstOffset) == (medRawOffset + medDstOffset); michael@0: bSameDst = ((dstOffset != 0) && (medDstOffset != 0)) || ((dstOffset == 0) && (medDstOffset == 0)); michael@0: michael@0: if (!bSameOffset || !bSameDst) { michael@0: hit = medt; michael@0: } else { michael@0: lot = medt; michael@0: } michael@0: } michael@0: // write out the boundary michael@0: str.remove(); michael@0: formatter->format(lot, rawOffset + dstOffset, (dstOffset == 0 ? FALSE : TRUE), str); michael@0: out << str << " > "; michael@0: str.remove(); michael@0: formatter->format(hit, newRawOffset + newDstOffset, (newDstOffset == 0 ? FALSE : TRUE), str); michael@0: out << str; michael@0: if (linesep != NULL) { michael@0: out << linesep; michael@0: } else { michael@0: out << endl; michael@0: } michael@0: michael@0: rawOffset = newRawOffset; michael@0: dstOffset = newDstOffset; michael@0: } michael@0: t = newt; michael@0: } michael@0: } michael@0: michael@0: private: michael@0: void getCutOverTimes(UDate& lo, UDate& hi) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: GregorianCalendar* gcal = new GregorianCalendar(timezone, Locale::getEnglish(), status); michael@0: gcal->clear(); michael@0: gcal->set(loyear, 0, 1, 0, 0, 0); michael@0: lo = gcal->getTime(status); michael@0: gcal->set(hiyear, 0, 1, 0, 0, 0); michael@0: hi = gcal->getTime(status); michael@0: } michael@0: michael@0: void dumpZone(ostream& out, const char* linesep, UnicodeString tzid, int32_t low, int32_t high) { michael@0: } michael@0: michael@0: TimeZone* timezone; michael@0: int32_t loyear; michael@0: int32_t hiyear; michael@0: int32_t tick; michael@0: michael@0: DumpFormatter* formatter; michael@0: const char* linesep; michael@0: }; michael@0: michael@0: class ZoneIterator { michael@0: public: michael@0: ZoneIterator(UBool bAll = FALSE) { michael@0: if (bAll) { michael@0: zenum = TimeZone::createEnumeration(); michael@0: } michael@0: else { michael@0: zenum = NULL; michael@0: zids = NULL; michael@0: idx = 0; michael@0: numids = 1; michael@0: } michael@0: } michael@0: michael@0: ZoneIterator(const char** ids, int32_t num) { michael@0: zenum = NULL; michael@0: zids = ids; michael@0: idx = 0; michael@0: numids = num; michael@0: } michael@0: michael@0: ~ZoneIterator() { michael@0: if (zenum != NULL) { michael@0: delete zenum; michael@0: } michael@0: } michael@0: michael@0: TimeZone* next() { michael@0: TimeZone* tz = NULL; michael@0: if (zenum != NULL) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: const UnicodeString* zid = zenum->snext(status); michael@0: if (zid != NULL) { michael@0: tz = TimeZone::createTimeZone(*zid); michael@0: } michael@0: } michael@0: else { michael@0: if (idx < numids) { michael@0: if (zids != NULL) { michael@0: tz = TimeZone::createTimeZone((const UnicodeString&)zids[idx]); michael@0: } michael@0: else { michael@0: tz = TimeZone::createDefault(); michael@0: } michael@0: idx++; michael@0: } michael@0: } michael@0: return tz; michael@0: } michael@0: michael@0: private: michael@0: const char** zids; michael@0: StringEnumeration* zenum; michael@0: int32_t idx; michael@0: int32_t numids; michael@0: }; michael@0: michael@0: enum { michael@0: kOptHelpH = 0, michael@0: kOptHelpQuestionMark, michael@0: kOptAllZones, michael@0: kOptCutover, michael@0: kOptDestDir, michael@0: kOptLineSep michael@0: }; michael@0: michael@0: static UOption options[]={ michael@0: UOPTION_HELP_H, michael@0: UOPTION_HELP_QUESTION_MARK, michael@0: UOPTION_DEF("allzones", 'a', UOPT_NO_ARG), michael@0: UOPTION_DEF("cutover", 'c', UOPT_REQUIRES_ARG), michael@0: UOPTION_DEF("destdir", 'd', UOPT_REQUIRES_ARG), michael@0: UOPTION_DEF("linesep", 'l', UOPT_REQUIRES_ARG) michael@0: }; michael@0: michael@0: extern int michael@0: main(int argc, char *argv[]) { michael@0: int32_t low = 1902; michael@0: int32_t high = 2038; michael@0: UBool bAll = FALSE; michael@0: const char *dir = NULL; michael@0: const char *linesep = NULL; michael@0: michael@0: U_MAIN_INIT_ARGS(argc, argv); michael@0: argc = u_parseArgs(argc, argv, sizeof(options)/sizeof(options[0]), options); michael@0: michael@0: if (argc < 0) { michael@0: cerr << "Illegal command line argument(s)" << endl << endl; michael@0: } michael@0: michael@0: if (argc < 0 || options[kOptHelpH].doesOccur || options[kOptHelpQuestionMark].doesOccur) { michael@0: cerr michael@0: << "Usage: icuzdump [-options] [zoneid1 zoneid2 ...]" << endl michael@0: << endl michael@0: << "\tDump all offset transitions for the specified zones." << endl michael@0: << endl michael@0: << "Options:" << endl michael@0: << "\t-a : Dump all available zones." << endl michael@0: << "\t-d : When specified, write transitions in a file under" << endl michael@0: << "\t the directory for each zone." << endl michael@0: << "\t-l : New line code type used in file outputs. CR or LF (default)" michael@0: << "\t or CRLF." << endl michael@0: << "\t-c [,]" << endl michael@0: << "\t : When specified, dump transitions starting " << endl michael@0: << "\t (inclusive) up to (exclusive). The default" << endl michael@0: << "\t values are 1902(low) and 2038(high)." << endl; michael@0: return argc < 0 ? U_ILLEGAL_ARGUMENT_ERROR : U_ZERO_ERROR; michael@0: } michael@0: michael@0: bAll = options[kOptAllZones].doesOccur; michael@0: michael@0: if (options[kOptDestDir].doesOccur) { michael@0: dir = options[kOptDestDir].value; michael@0: } michael@0: michael@0: if (options[kOptLineSep].doesOccur) { michael@0: if (strcmp(options[kOptLineSep].value, "CR") == 0) { michael@0: linesep = "\r"; michael@0: } else if (strcmp(options[kOptLineSep].value, "CRLF") == 0) { michael@0: linesep = "\r\n"; michael@0: } else if (strcmp(options[kOptLineSep].value, "LF") == 0) { michael@0: linesep = "\n"; michael@0: } michael@0: } michael@0: michael@0: if (options[kOptCutover].doesOccur) { michael@0: char* comma = (char*)strchr(options[kOptCutover].value, ','); michael@0: if (comma == NULL) { michael@0: high = atoi(options[kOptCutover].value); michael@0: } else { michael@0: *comma = 0; michael@0: low = atoi(options[kOptCutover].value); michael@0: high = atoi(comma + 1); michael@0: } michael@0: } michael@0: michael@0: ICUZDump dumper; michael@0: dumper.setLowYear(low); michael@0: dumper.setHighYear(high); michael@0: if (dir != NULL && linesep != NULL) { michael@0: // use the specified line separator only for file output michael@0: dumper.setLineSeparator((const char*)linesep); michael@0: } michael@0: michael@0: ZoneIterator* zit; michael@0: if (bAll) { michael@0: zit = new ZoneIterator(TRUE); michael@0: } else { michael@0: if (argc <= 1) { michael@0: zit = new ZoneIterator(); michael@0: } else { michael@0: zit = new ZoneIterator((const char**)&argv[1], argc - 1); michael@0: } michael@0: } michael@0: michael@0: UnicodeString id; michael@0: if (dir != NULL) { michael@0: // file output michael@0: ostringstream path; michael@0: ios::openmode mode = ios::out; michael@0: if (linesep != NULL) { michael@0: mode |= ios::binary; michael@0: } michael@0: for (;;) { michael@0: TimeZone* tz = zit->next(); michael@0: if (tz == NULL) { michael@0: break; michael@0: } michael@0: dumper.setTimeZone(tz); michael@0: tz->getID(id); michael@0: michael@0: // target file path michael@0: path.str(""); michael@0: path << dir << U_FILE_SEP_CHAR; michael@0: id = id.findAndReplace("/", "-"); michael@0: path << id; michael@0: michael@0: ofstream* fout = new ofstream(path.str().c_str(), mode); michael@0: if (fout->fail()) { michael@0: cerr << "Cannot open file " << path << endl; michael@0: delete fout; michael@0: delete tz; michael@0: break; michael@0: } michael@0: michael@0: dumper.dump(*fout); michael@0: fout->close(); michael@0: delete fout; michael@0: delete tz; michael@0: } michael@0: michael@0: } else { michael@0: // stdout michael@0: UBool bFirst = TRUE; michael@0: for (;;) { michael@0: TimeZone* tz = zit->next(); michael@0: if (tz == NULL) { michael@0: break; michael@0: } michael@0: dumper.setTimeZone(tz); michael@0: tz->getID(id); michael@0: if (bFirst) { michael@0: bFirst = FALSE; michael@0: } else { michael@0: cout << endl; michael@0: } michael@0: cout << "ZONE: " << id << endl; michael@0: dumper.dump(cout); michael@0: delete tz; michael@0: } michael@0: } michael@0: delete zit; michael@0: }