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