]> git.saurik.com Git - apple/icu.git/blob - icuSources/tools/tzcode/tz2icu.cpp
eba0d3aa90d24c08a320ecd0db19eade5726c059
[apple/icu.git] / icuSources / tools / tzcode / tz2icu.cpp
1
2 /*
3 **********************************************************************
4 * Copyright (c) 2003-2006, International Business Machines
5 * Corporation and others. All Rights Reserved.
6 **********************************************************************
7 * Author: Alan Liu
8 * Created: July 10 2003
9 * Since: ICU 2.8
10 **********************************************************************
11 */
12 #include "tzfile.h" // from Olson tzcode archive, copied to this dir
13
14 #ifdef WIN32
15
16 #include <windows.h>
17 #undef min // windows.h/STL conflict
18 #undef max // windows.h/STL conflict
19 // "identifier was truncated to 'number' characters" warning
20 #pragma warning(disable: 4786)
21
22 #else
23
24 #include <unistd.h>
25 #include <stdio.h>
26 #include <dirent.h>
27 #include <string.h>
28 #include <sys/stat.h>
29
30 #endif
31
32 #include <algorithm>
33 #include <cassert>
34 #include <ctime>
35 #include <fstream>
36 #include <iomanip>
37 #include <iostream>
38 #include <iterator>
39 #include <limits>
40 #include <map>
41 #include <set>
42 #include <sstream>
43 #include <sstream>
44 #include <stdexcept>
45 #include <string>
46 #include <vector>
47
48 #include "tz2icu.h"
49 #include "unicode/uversion.h"
50
51 using namespace std;
52
53 //--------------------------------------------------------------------
54 // Time utilities
55 //--------------------------------------------------------------------
56
57 const long SECS_PER_YEAR = 31536000; // 365 days
58 const long SECS_PER_LEAP_YEAR = 31622400; // 366 days
59
60 bool isLeap(int y) {
61 return (y%4 == 0) && ((y%100 != 0) || (y%400 == 0)); // Gregorian
62 }
63
64 long secsPerYear(int y) {
65 return isLeap(y) ? SECS_PER_LEAP_YEAR : SECS_PER_YEAR;
66 }
67
68 /**
69 * Given a calendar year, return the GMT epoch seconds for midnight
70 * GMT of January 1 of that year. yearToSeconds(1970) == 0.
71 */
72 long yearToSeconds(int year) {
73 // inefficient but foolproof
74 long s = 0;
75 int y = 1970;
76 while (y < year) {
77 s += secsPerYear(y++);
78 }
79 while (y > year) {
80 s -= secsPerYear(--y);
81 }
82 return s;
83 }
84
85 /**
86 * Given 1970 GMT epoch seconds, return the calendar year containing
87 * that time. secondsToYear(0) == 1970.
88 */
89 int secondsToYear(long seconds) {
90 // inefficient but foolproof
91 int y = 1970;
92 long s = 0;
93 if (seconds >= 0) {
94 for (;;) {
95 s += secsPerYear(y++);
96 if (s > seconds) break;
97 }
98 --y;
99 } else {
100 for (;;) {
101 s -= secsPerYear(--y);
102 if (s <= seconds) break;
103 }
104 }
105 return y;
106 }
107
108 //--------------------------------------------------------------------
109 // Types
110 //--------------------------------------------------------------------
111
112 struct FinalZone;
113 struct FinalRule;
114 struct SimplifiedZoneType;
115
116 // A transition from one ZoneType to another
117 // Minimal size = 5 bytes (4+1)
118 struct Transition {
119 long time; // seconds, 1970 epoch
120 int type; // index into 'ZoneInfo.types' 0..255
121 Transition(long _time, int _type) {
122 time = _time;
123 type = _type;
124 }
125 };
126
127 // A behavior mode (what zic calls a 'type') of a time zone.
128 // Minimal size = 6 bytes (4+1+3bits)
129 // SEE: SimplifiedZoneType
130 struct ZoneType {
131 long rawoffset; // raw seconds offset from GMT
132 long dstoffset; // dst seconds offset from GMT
133
134 // We don't really need any of the following, but they are
135 // retained for possible future use. See SimplifiedZoneType.
136 int abbr; // index into ZoneInfo.abbrs 0..n-1
137 bool isdst;
138 bool isstd;
139 bool isgmt;
140
141 ZoneType(const SimplifiedZoneType&); // used by optimizeTypeList
142
143 ZoneType() : rawoffset(-1), dstoffset(-1), abbr(-1) {}
144
145 // A restricted equality, of just the raw and dst offset
146 bool matches(const ZoneType& other) {
147 return rawoffset == other.rawoffset &&
148 dstoffset == other.dstoffset;
149 }
150 };
151
152 // A collection of transitions from one ZoneType to another, together
153 // with a list of the ZoneTypes. A ZoneInfo object may have a long
154 // list of transitions between a smaller list of ZoneTypes.
155 //
156 // This object represents the contents of a single zic-created
157 // zoneinfo file.
158 struct ZoneInfo {
159 vector<Transition> transitions;
160 vector<ZoneType> types;
161 vector<string> abbrs;
162
163 string finalRuleID;
164 int finalOffset;
165 int finalYear; // -1 if none
166
167 // If this is an alias, then all other fields are meaningless, and
168 // this field will point to the "real" zone 0..n-1.
169 int aliasTo; // -1 if this is a "real" zone
170
171 // If there are aliases TO this zone, then the following set will
172 // contain their index numbers (each index >= 0).
173 set<int> aliases;
174
175 ZoneInfo() : finalYear(-1), aliasTo(-1) {}
176
177 void mergeFinalData(const FinalZone& fz);
178
179 void optimizeTypeList();
180
181 // Set this zone to be an alias TO another zone.
182 void setAliasTo(int index);
183
184 // Clear the list of aliases OF this zone.
185 void clearAliases();
186
187 // Add an alias to the list of aliases OF this zone.
188 void addAlias(int index);
189
190 // Is this an alias to another zone?
191 bool isAlias() const {
192 return aliasTo >= 0;
193 }
194
195 // Retrieve alias list
196 const set<int>& getAliases() const {
197 return aliases;
198 }
199
200 void print(ostream& os, const string& id) const;
201 };
202
203 void ZoneInfo::clearAliases() {
204 assert(aliasTo < 0);
205 aliases.clear();
206 }
207
208 void ZoneInfo::addAlias(int index) {
209 assert(aliasTo < 0 && index >= 0 && aliases.find(index) == aliases.end());
210 aliases.insert(index);
211 }
212
213 void ZoneInfo::setAliasTo(int index) {
214 assert(index >= 0);
215 assert(aliases.size() == 0);
216 aliasTo = index;
217 }
218
219 typedef map<string, ZoneInfo> ZoneMap;
220
221 typedef ZoneMap::const_iterator ZoneMapIter;
222
223 //--------------------------------------------------------------------
224 // ZONEINFO
225 //--------------------------------------------------------------------
226
227 // Global map holding all our ZoneInfo objects, indexed by id.
228 ZoneMap ZONEINFO;
229
230 //--------------------------------------------------------------------
231 // zoneinfo file parsing
232 //--------------------------------------------------------------------
233
234 // Read zic-coded 32-bit integer from file
235 long readcoded(ifstream& file, long minv=numeric_limits<long>::min(),
236 long maxv=numeric_limits<long>::max()) {
237 unsigned char buf[4]; // must be UNSIGNED
238 long val=0;
239 file.read((char*)buf, 4);
240 for(int i=0,shift=24;i<4;++i,shift-=8) {
241 val |= buf[i] << shift;
242 }
243 if (val < minv || val > maxv) {
244 ostringstream os;
245 os << "coded value out-of-range: " << val << ", expected ["
246 << minv << ", " << maxv << "]";
247 throw out_of_range(os.str());
248 }
249 return val;
250 }
251
252 // Read a boolean value
253 bool readbool(ifstream& file) {
254 char c;
255 file.read(&c, 1);
256 if (c!=0 && c!=1) {
257 ostringstream os;
258 os << "boolean value out-of-range: " << (int)c;
259 throw out_of_range(os.str());
260 }
261 return (c!=0);
262 }
263
264 /**
265 * Read the zoneinfo file structure (see tzfile.h) into a ZoneInfo
266 * @param file an already-open file stream
267 */
268 void readzoneinfo(ifstream& file, ZoneInfo& info) {
269 int i;
270
271 // Check for TZ_ICU_MAGIC signature at file start. If we get a
272 // signature mismatch, it means we're trying to read a file which
273 // isn't a ICU-modified-zic-created zoneinfo file. Typically this
274 // means the user is passing in a "normal" zoneinfo directory, or
275 // a zoneinfo directory that is polluted with other files, or that
276 // the user passed in the wrong directory.
277 char buf[32];
278 file.read(buf, 4);
279 if (strncmp(buf, TZ_ICU_MAGIC, 4) != 0) {
280 throw invalid_argument("TZ_ICU_MAGIC signature missing");
281 }
282 // skip additional Olson byte version
283 file.read(buf, 1);
284 // if '\0', we have just one copy of data, if '2', there is additional
285 // 64 bit version at the end.
286 if(buf[0]!=0 && buf[0]!='2') {
287 throw invalid_argument("Bad Olson version info");
288 }
289
290 // Read reserved bytes. The first of these will be a version byte.
291 file.read(buf, 15);
292 if (*(ICUZoneinfoVersion*)&buf != TZ_ICU_VERSION) {
293 throw invalid_argument("File version mismatch");
294 }
295
296 // Read array sizes
297 long isgmtcnt = readcoded(file, 0);
298 long isdstcnt = readcoded(file, 0);
299 long leapcnt = readcoded(file, 0);
300 long timecnt = readcoded(file, 0);
301 long typecnt = readcoded(file, 0);
302 long charcnt = readcoded(file, 0);
303
304 // Confirm sizes that we assume to be equal. These assumptions
305 // are drawn from a reading of the zic source (2003a), so they
306 // should hold unless the zic source changes.
307 if (isgmtcnt != typecnt || isdstcnt != typecnt) {
308 throw invalid_argument("count mismatch between tzh_ttisgmtcnt, tzh_ttisdstcnt, tth_typecnt");
309 }
310
311 // Used temporarily to store transition times and types. We need
312 // to do this because the times and types are stored in two
313 // separate arrays.
314 vector<long> transitionTimes(timecnt, -1); // temporary
315 vector<int> transitionTypes(timecnt, -1); // temporary
316
317 // Read transition times
318 for (i=0; i<timecnt; ++i) {
319 transitionTimes[i] = readcoded(file);
320 }
321
322 // Read transition types
323 for (i=0; i<timecnt; ++i) {
324 unsigned char c;
325 file.read((char*) &c, 1);
326 int t = (int) c;
327 if (t < 0 || t >= typecnt) {
328 ostringstream os;
329 os << "illegal type: " << t << ", expected [0, " << (typecnt-1) << "]";
330 throw out_of_range(os.str());
331 }
332 transitionTypes[i] = t;
333 }
334
335 // Build transitions vector out of corresponding times and types.
336 for (i=0; i<timecnt; ++i) {
337 info.transitions.push_back(Transition(transitionTimes[i], transitionTypes[i]));
338 }
339
340 // Read types (except for the isdst and isgmt flags, which come later (why??))
341 for (i=0; i<typecnt; ++i) {
342 ZoneType type;
343
344 type.rawoffset = readcoded(file);
345 type.dstoffset = readcoded(file);
346 type.isdst = readbool(file);
347
348 unsigned char c;
349 file.read((char*) &c, 1);
350 type.abbr = (int) c;
351
352 if (type.isdst != (type.dstoffset != 0)) {
353 throw invalid_argument("isdst does not reflect dstoffset");
354 }
355
356 info.types.push_back(type);
357 }
358 assert(info.types.size() == (unsigned) typecnt);
359
360 // Read the abbreviation string
361 if (charcnt) {
362 // All abbreviations are concatenated together, with a 0 at
363 // the end of each abbr.
364 char* str = new char[charcnt + 8];
365 file.read(str, charcnt);
366
367 // Split abbreviations apart into individual strings. Record
368 // offset of each abbr in a vector.
369 vector<int> abbroffset;
370 char *limit=str+charcnt;
371 for (char* p=str; p<limit; ++p) {
372 char* start = p;
373 while (*p != 0) ++p;
374 info.abbrs.push_back(string(start, p-start));
375 abbroffset.push_back(start-str);
376 }
377
378 // Remap all the abbrs. Old value is offset into concatenated
379 // raw abbr strings. New value is index into vector of
380 // strings. E.g., 0,5,10,14 => 0,1,2,3.
381
382 // Keep track of which abbreviations get used.
383 vector<bool> abbrseen(abbroffset.size(), false);
384
385 for (vector<ZoneType>::iterator it=info.types.begin();
386 it!=info.types.end();
387 ++it) {
388 vector<int>::const_iterator x=
389 find(abbroffset.begin(), abbroffset.end(), it->abbr);
390 if (x==abbroffset.end()) {
391 // TODO: Modify code to add a new string to the end of
392 // the abbr list when a middle offset is given, e.g.,
393 // "abc*def*" where * == '\0', take offset of 1 and
394 // make the array "abc", "def", "bc", and translate 1
395 // => 2. NOT CRITICAL since we don't even use the
396 // abbr at this time.
397 #if 0
398 // TODO: Re-enable this warning if we start using
399 // the Olson abbr data, or if the above TODO is completed.
400 ostringstream os;
401 os << "Warning: unusual abbr offset " << it->abbr
402 << ", expected one of";
403 for (vector<int>::const_iterator y=abbroffset.begin();
404 y!=abbroffset.end(); ++y) {
405 os << ' ' << *y;
406 }
407 cerr << os.str() << "; using 0" << endl;
408 #endif
409 it->abbr = 0;
410 } else {
411 int index = x - abbroffset.begin();
412 it->abbr = index;
413 abbrseen[index] = true;
414 }
415 }
416
417 for (int ii=0;ii<(int) abbrseen.size();++ii) {
418 if (!abbrseen[ii]) {
419 cerr << "Warning: unused abbreviation: " << ii << endl;
420 }
421 }
422 }
423
424 // Read leap second info, if any.
425 // *** We discard leap second data. ***
426 for (i=0; i<leapcnt; ++i) {
427 readcoded(file); // transition time
428 readcoded(file); // total correction after above
429 }
430
431 // Read isstd flags
432 for (i=0; i<typecnt; ++i) info.types[i].isstd = readbool(file);
433
434 // Read isgmt flags
435 for (i=0; i<typecnt; ++i) info.types[i].isgmt = readbool(file);
436 }
437
438 //--------------------------------------------------------------------
439 // Directory and file reading
440 //--------------------------------------------------------------------
441
442 /**
443 * Process a single zoneinfo file, adding the data to ZONEINFO
444 * @param path the full path to the file, e.g., ".\zoneinfo\America\Los_Angeles"
445 * @param id the zone ID, e.g., "America/Los_Angeles"
446 */
447 void handleFile(string path, string id) {
448 // Check for duplicate id
449 if (ZONEINFO.find(id) != ZONEINFO.end()) {
450 ostringstream os;
451 os << "duplicate zone ID: " << id;
452 throw invalid_argument(os.str());
453 }
454
455 ifstream file(path.c_str(), ios::in | ios::binary);
456 if (!file) {
457 throw invalid_argument("can't open file");
458 }
459 ZoneInfo info;
460 readzoneinfo(file, info);
461
462 // Check for errors
463 if (!file) {
464 throw invalid_argument("read error");
465 }
466
467 // Check eof-relative pos (there may be a cleaner way to do this)
468 long eofPos = (long) file.tellg();
469 char buf[32];
470 file.read(buf, 4);
471 file.seekg(0, ios::end);
472 eofPos = eofPos - (long) file.tellg();
473 if (eofPos) {
474 // 2006c merged 32 and 64 bit versions in a fat binary
475 // 64 version starts at the end of 32 bit version.
476 // Therefore, if the file is *not* consumed, check
477 // if it is maybe being restarted.
478 if (strncmp(buf, TZ_ICU_MAGIC, 4) != 0) {
479 ostringstream os;
480 os << (-eofPos) << " unprocessed bytes at end";
481 throw invalid_argument(os.str());
482 }
483 }
484
485 ZONEINFO[id] = info;
486 }
487
488 /**
489 * Recursively scan the given directory, calling handleFile() for each
490 * file in the tree. The user should call with the root directory and
491 * a prefix of "". The function will call itself with non-empty
492 * prefix values.
493 */
494 #ifdef WIN32
495
496 void scandir(string dirname, string prefix="") {
497 HANDLE hList;
498 WIN32_FIND_DATA FileData;
499
500 // Get the first file
501 hList = FindFirstFile((dirname + "\\*").c_str(), &FileData);
502 if (hList == INVALID_HANDLE_VALUE) {
503 cerr << "Error: Invalid directory: " << dirname << endl;
504 exit(1);
505 }
506 for (;;) {
507 string name(FileData.cFileName);
508 string path(dirname + "\\" + name);
509 if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
510 if (name != "." && name != "..") {
511 scandir(path, prefix + name + "/");
512 }
513 } else {
514 try {
515 string id = prefix + name;
516 handleFile(path, id);
517 } catch (const exception& e) {
518 cerr << "Error: While processing \"" << path << "\", "
519 << e.what() << endl;
520 exit(1);
521 }
522 }
523
524 if (!FindNextFile(hList, &FileData)) {
525 if (GetLastError() == ERROR_NO_MORE_FILES) {
526 break;
527 } // else...?
528 }
529 }
530 FindClose(hList);
531 }
532
533 #else
534
535 void scandir(string dir, string prefix="") {
536 DIR *dp;
537 struct dirent *dir_entry;
538 struct stat stat_info;
539 char pwd[512];
540 vector<string> subdirs;
541 vector<string> subfiles;
542
543 if ((dp = opendir(dir.c_str())) == NULL) {
544 cerr << "Error: Invalid directory: " << dir << endl;
545 exit(1);
546 }
547 if (!getcwd(pwd, sizeof(pwd))) {
548 cerr << "Error: Directory name too long" << endl;
549 exit(1);
550 }
551 chdir(dir.c_str());
552 while ((dir_entry = readdir(dp)) != NULL) {
553 string name = dir_entry->d_name;
554 string path = dir + "/" + name;
555 lstat(dir_entry->d_name,&stat_info);
556 if (S_ISDIR(stat_info.st_mode)) {
557 if (name != "." && name != "..") {
558 subdirs.push_back(path);
559 subdirs.push_back(prefix + name + "/");
560 // scandir(path, prefix + name + "/");
561 }
562 } else {
563 try {
564 string id = prefix + name;
565 subfiles.push_back(path);
566 subfiles.push_back(id);
567 // handleFile(path, id);
568 } catch (const exception& e) {
569 cerr << "Error: While processing \"" << path << "\", "
570 << e.what() << endl;
571 exit(1);
572 }
573 }
574 }
575 closedir(dp);
576 chdir(pwd);
577
578 for(int i=0;i<(int)subfiles.size();i+=2) {
579 try {
580 handleFile(subfiles[i], subfiles[i+1]);
581 } catch (const exception& e) {
582 cerr << "Error: While processing \"" << subfiles[i] << "\", "
583 << e.what() << endl;
584 exit(1);
585 }
586 }
587 for(int i=0;i<(int)subdirs.size();i+=2) {
588 scandir(subdirs[i], subdirs[i+1]);
589 }
590 }
591
592 #endif
593
594 //--------------------------------------------------------------------
595 // Final zone and rule info
596 //--------------------------------------------------------------------
597
598 /**
599 * Read and discard the current line.
600 */
601 void consumeLine(istream& in) {
602 int c;
603 do {
604 c = in.get();
605 } while (c != EOF && c != '\n');
606 }
607
608 enum {
609 DOM = 0,
610 DOWGEQ = 1,
611 DOWLEQ = 2
612 };
613
614 const char* TIME_MODE[] = {"w", "s", "u"};
615
616 // Allow 29 days in February because zic outputs February 29
617 // for rules like "last Sunday in February".
618 const int MONTH_LEN[] = {31,29,31,30,31,30,31,31,30,31,30,31};
619
620 const int HOUR = 3600;
621
622 struct FinalZone {
623 int offset; // raw offset
624 int year; // takes effect for y >= year
625 string ruleid;
626 set<string> aliases;
627 FinalZone(int _offset, int _year, const string& _ruleid) :
628 offset(_offset), year(_year), ruleid(_ruleid) {
629 if (offset <= -16*HOUR || offset >= 16*HOUR) {
630 ostringstream os;
631 os << "Invalid input offset " << offset
632 << " for year " << year
633 << " and rule ID " << ruleid;
634 throw invalid_argument(os.str());
635 }
636 if (year < 1900 || year >= 2050) {
637 ostringstream os;
638 os << "Invalid input year " << year
639 << " with offset " << offset
640 << " and rule ID " << ruleid;
641 throw invalid_argument(os.str());
642 }
643 }
644 FinalZone() : offset(-1), year(-1) {}
645 void addLink(const string& alias) {
646 if (aliases.find(alias) != aliases.end()) {
647 ostringstream os;
648 os << "Duplicate alias " << alias;
649 throw invalid_argument(os.str());
650 }
651 aliases.insert(alias);
652 }
653 };
654
655 struct FinalRulePart {
656 int mode;
657 int month;
658 int dom;
659 int dow;
660 int time;
661 int offset; // dst offset, usually either 0 or 1:00
662
663 // Isstd and isgmt only have 3 valid states, corresponding to local
664 // wall time, local standard time, and GMT standard time.
665 // Here is how the isstd & isgmt flags are set by zic:
666 //| case 's': /* Standard */
667 //| rp->r_todisstd = TRUE;
668 //| rp->r_todisgmt = FALSE;
669 //| case 'w': /* Wall */
670 //| rp->r_todisstd = FALSE;
671 //| rp->r_todisgmt = FALSE;
672 //| case 'g': /* Greenwich */
673 //| case 'u': /* Universal */
674 //| case 'z': /* Zulu */
675 //| rp->r_todisstd = TRUE;
676 //| rp->r_todisgmt = TRUE;
677 bool isstd;
678 bool isgmt;
679
680 bool isset; // used during building; later ignored
681
682 FinalRulePart() : isset(false) {}
683 void set(const string& id,
684 const string& _mode,
685 int _month,
686 int _dom,
687 int _dow,
688 int _time,
689 bool _isstd,
690 bool _isgmt,
691 int _offset) {
692 if (isset) {
693 throw invalid_argument("FinalRulePart set twice");
694 }
695 isset = true;
696 if (_mode == "DOWLEQ") {
697 mode = DOWLEQ;
698 } else if (_mode == "DOWGEQ") {
699 mode = DOWGEQ;
700 } else if (_mode == "DOM") {
701 mode = DOM;
702 } else {
703 throw invalid_argument("Unrecognized FinalRulePart mode");
704 }
705 month = _month;
706 dom = _dom;
707 dow = _dow;
708 time = _time;
709 isstd = _isstd;
710 isgmt = _isgmt;
711 offset = _offset;
712
713 ostringstream os;
714 if (month < 0 || month >= 12) {
715 os << "Invalid input month " << month;
716 }
717 if (dom < 1 || dom > MONTH_LEN[month]) {
718 os << "Invalid input day of month " << dom;
719 }
720 if (mode != DOM && (dow < 0 || dow >= 7)) {
721 os << "Invalid input day of week " << dow;
722 }
723 if (offset < 0 || offset > HOUR) {
724 os << "Invalid input offset " << offset;
725 }
726 if (isgmt && !isstd) {
727 os << "Invalid input isgmt && !isstd";
728 }
729 if (!os.str().empty()) {
730 os << " for rule "
731 << id
732 << _mode
733 << month << dom << dow << time
734 << isstd << isgmt
735 << offset;
736 throw invalid_argument(os.str());
737 }
738 }
739
740 /**
741 * Return the time mode as an ICU SimpleTimeZone int from 0..2;
742 * see simpletz.h.
743 */
744 int timemode() const {
745 if (isgmt) {
746 assert(isstd);
747 return 2; // gmt standard
748 }
749 if (isstd) {
750 return 1; // local standard
751 }
752 return 0; // local wall
753 }
754
755 // The SimpleTimeZone encoding method for rules is as follows:
756 // stz_dowim stz_dow
757 // DOM: dom 0
758 // DOWGEQ: dom -(dow+1)
759 // DOWLEQ: -dom -(dow+1)
760 // E.g., to encode Mon>=7, use stz_dowim=7, stz_dow=-2
761 // to encode Mon<=7, use stz_dowim=-7, stz_dow=-2
762 // to encode 7, use stz_dowim=7, stz_dow=0
763 // Note that for this program and for SimpleTimeZone, 0==Jan,
764 // but for this program 0==Sun while for SimpleTimeZone 1==Sun.
765
766 /**
767 * Return a "dowim" param suitable for SimpleTimeZone.
768 */
769 int stz_dowim() const {
770 return (mode == DOWLEQ) ? -dom : dom;
771 }
772
773 /**
774 * Return a "dow" param suitable for SimpleTimeZone.
775 */
776 int stz_dow() const {
777 return (mode == DOM) ? 0 : -(dow+1);
778 }
779 };
780
781 struct FinalRule {
782 FinalRulePart part[2];
783
784 bool isset() const {
785 return part[0].isset && part[1].isset;
786 }
787
788 void print(ostream& os) const;
789 };
790
791 map<string,FinalZone> finalZones;
792 map<string,FinalRule> finalRules;
793
794 map<string, set<string> > links;
795 map<string, string> reverseLinks;
796 map<string, string> linkSource; // id => "Olson link" or "ICU alias"
797
798 /**
799 * Predicate used to find FinalRule objects that do not have both
800 * sub-parts set (indicating an error in the input file).
801 */
802 bool isNotSet(const pair<const string,FinalRule>& p) {
803 return !p.second.isset();
804 }
805
806 /**
807 * Predicate used to find FinalZone objects that do not map to a known
808 * rule (indicating an error in the input file).
809 */
810 bool mapsToUnknownRule(const pair<const string,FinalZone>& p) {
811 return finalRules.find(p.second.ruleid) == finalRules.end();
812 }
813
814 /**
815 * This set is used to make sure each rule in finalRules is used at
816 * least once. First we populate it with all the rules from
817 * finalRules; then we remove all the rules referred to in
818 * finaleZones.
819 */
820 set<string> ruleIDset;
821
822 void insertRuleID(const pair<string,FinalRule>& p) {
823 ruleIDset.insert(p.first);
824 }
825
826 void eraseRuleID(const pair<string,FinalZone>& p) {
827 ruleIDset.erase(p.second.ruleid);
828 }
829
830 /**
831 * Populate finalZones and finalRules from the given istream.
832 */
833 void readFinalZonesAndRules(istream& in) {
834
835 for (;;) {
836 string token;
837 in >> token;
838 if (in.eof() || !in) {
839 break;
840 } else if (token == "zone") {
841 // zone Africa/Cairo 7200 1995 Egypt # zone Africa/Cairo, offset 7200, year >= 1995, rule Egypt (0)
842 string id, ruleid;
843 int offset, year;
844 in >> id >> offset >> year >> ruleid;
845 consumeLine(in);
846 finalZones[id] = FinalZone(offset, year, ruleid);
847 } else if (token == "rule") {
848 // rule US DOWGEQ 3 1 0 7200 0 0 3600 # 52: US, file data/northamerica, line 119, mode DOWGEQ, April, dom 1, Sunday, time 7200, isstd 0, isgmt 0, offset 3600
849 // rule US DOWLEQ 9 31 0 7200 0 0 0 # 53: US, file data/northamerica, line 114, mode DOWLEQ, October, dom 31, Sunday, time 7200, isstd 0, isgmt 0, offset 0
850 string id, mode;
851 int month, dom, dow, time, offset;
852 bool isstd, isgmt;
853 in >> id >> mode >> month >> dom >> dow >> time >> isstd >> isgmt >> offset;
854 consumeLine(in);
855 FinalRule& fr = finalRules[id];
856 int p = fr.part[0].isset ? 1 : 0;
857 fr.part[p].set(id, mode, month, dom, dow, time, isstd, isgmt, offset);
858 } else if (token == "link") {
859 string fromid, toid; // fromid == "real" zone, toid == alias
860 in >> fromid >> toid;
861 // DO NOT consumeLine(in);
862 if (finalZones.find(toid) != finalZones.end()) {
863 throw invalid_argument("Bad link: `to' id is a \"real\" zone");
864 }
865
866 links[fromid].insert(toid);
867 reverseLinks[toid] = fromid;
868
869 linkSource[fromid] = "Olson link";
870 linkSource[toid] = "Olson link";
871 } else if (token.length() > 0 && token[0] == '#') {
872 consumeLine(in);
873 } else {
874 throw invalid_argument("Unrecognized keyword");
875 }
876 }
877
878 if (!in.eof() && !in) {
879 throw invalid_argument("Parse failure");
880 }
881
882 // Perform validity check: Each rule should have data for 2 parts.
883 if (count_if(finalRules.begin(), finalRules.end(), isNotSet) != 0) {
884 throw invalid_argument("One or more incomplete rule pairs");
885 }
886
887 // Perform validity check: Each zone should map to a known rule.
888 if (count_if(finalZones.begin(), finalZones.end(), mapsToUnknownRule) != 0) {
889 throw invalid_argument("One or more zones refers to an unknown rule");
890 }
891
892 // Perform validity check: Each rule should be referred to by a zone.
893 ruleIDset.clear();
894 for_each(finalRules.begin(), finalRules.end(), insertRuleID);
895 for_each(finalZones.begin(), finalZones.end(), eraseRuleID);
896 if (ruleIDset.size() != 0) {
897 throw invalid_argument("Unused rules");
898 }
899 }
900
901 //--------------------------------------------------------------------
902 // Resource bundle output
903 //--------------------------------------------------------------------
904
905 // SEE olsontz.h FOR RESOURCE BUNDLE DATA LAYOUT
906
907 void ZoneInfo::print(ostream& os, const string& id) const {
908 // Implement compressed format #2:
909
910 os << " /* " << id << " */ ";
911
912 if (aliasTo >= 0) {
913 assert(aliases.size() == 0);
914 os << ":int { " << aliasTo << " } "; // No endl - save room for comment.
915 return;
916 }
917
918 os << ":array {" << endl;
919
920 vector<Transition>::const_iterator trn;
921 vector<ZoneType>::const_iterator typ;
922
923 bool first=true;
924 os << " :intvector { ";
925 for (trn = transitions.begin(); trn != transitions.end(); ++trn) {
926 if (!first) os << ", ";
927 first = false;
928 os << trn->time;
929 }
930 os << " }" << endl;
931
932 first=true;
933 os << " :intvector { ";
934 for (typ = types.begin(); typ != types.end(); ++typ) {
935 if (!first) os << ", ";
936 first = false;
937 os << typ->rawoffset << ", " << typ->dstoffset;
938 }
939 os << " }" << endl;
940
941 os << " :bin { \"" << hex << setfill('0');
942 for (trn = transitions.begin(); trn != transitions.end(); ++trn) {
943 os << setw(2) << trn->type;
944 }
945 os << dec << "\" }" << endl;
946
947 // Final zone info, if any
948 if (finalYear != -1) {
949 os << " \"" << finalRuleID << "\"" << endl;
950 os << " :intvector { " << finalOffset << ", "
951 << finalYear << " }" << endl;
952 }
953
954 // Alias list, if any
955 if (aliases.size() != 0) {
956 first = true;
957 os << " :intvector { ";
958 for (set<int>::const_iterator i=aliases.begin(); i!=aliases.end(); ++i) {
959 if (!first) os << ", ";
960 first = false;
961 os << *i;
962 }
963 os << " }" << endl;
964 }
965
966 os << " } "; // no trailing 'endl', so comments can be placed.
967 }
968
969 inline ostream&
970 operator<<(ostream& os, const ZoneMap& zoneinfo) {
971 int c = 0;
972 for (ZoneMapIter it = zoneinfo.begin();
973 it != zoneinfo.end();
974 ++it) {
975 if(c) os << ",";
976 it->second.print(os, it->first);
977 os << "//Z#" << c++ << endl;
978 }
979 return os;
980 }
981
982 // print the string list
983 ostream& printStringList( ostream& os, const ZoneMap& zoneinfo) {
984 int n = 0; // count
985 int col = 0; // column
986 os << " Names {" << endl
987 << " ";
988 for (ZoneMapIter it = zoneinfo.begin();
989 it != zoneinfo.end();
990 ++it) {
991 if(n) {
992 os << ",";
993 col ++;
994 }
995 const string& id = it->first;
996 os << "\"" << id << "\"";
997 col += id.length() + 2;
998 if(col >= 50) {
999 os << " // " << n << endl
1000 << " ";
1001 col = 0;
1002 }
1003 n++;
1004 }
1005 os << " // " << (n-1) << endl
1006 << " }" << endl;
1007
1008 return os;
1009 }
1010
1011 //--------------------------------------------------------------------
1012 // main
1013 //--------------------------------------------------------------------
1014
1015 // Unary predicate for finding transitions after a given time
1016 bool isAfter(const Transition t, long thresh) {
1017 return t.time >= thresh;
1018 }
1019
1020 /**
1021 * A zone type that contains only the raw and dst offset. Used by the
1022 * optimizeTypeList() method.
1023 */
1024 struct SimplifiedZoneType {
1025 long rawoffset;
1026 long dstoffset;
1027 SimplifiedZoneType() : rawoffset(-1), dstoffset(-1) {}
1028 SimplifiedZoneType(const ZoneType& t) : rawoffset(t.rawoffset),
1029 dstoffset(t.dstoffset) {}
1030 bool operator<(const SimplifiedZoneType& t) const {
1031 return rawoffset < t.rawoffset ||
1032 (rawoffset == t.rawoffset &&
1033 dstoffset < t.dstoffset);
1034 }
1035 };
1036
1037 /**
1038 * Construct a ZoneType from a SimplifiedZoneType. Note that this
1039 * discards information; the new ZoneType will have meaningless
1040 * (empty) abbr, isdst, isstd, and isgmt flags; this is appropriate,
1041 * since ignoring these is how we do optimization (we have no use for
1042 * these in historical transitions).
1043 */
1044 ZoneType::ZoneType(const SimplifiedZoneType& t) :
1045 rawoffset(t.rawoffset), dstoffset(t.dstoffset),
1046 abbr(-1), isdst(false), isstd(false), isgmt(false) {}
1047
1048 /**
1049 * Optimize the type list to remove excess entries. The type list may
1050 * contain entries that are distinct only in terms of their dst, std,
1051 * or gmt flags. Since we don't care about those flags, we can reduce
1052 * the type list to a set of unique raw/dst offset pairs, and remap
1053 * the type indices in the transition list, which stores, for each
1054 * transition, a transition time and a type index.
1055 */
1056 void ZoneInfo::optimizeTypeList() {
1057 // Assemble set of unique types; only those in the `transitions'
1058 // list, since there may be unused types in the `types' list
1059 // corresponding to transitions that have been trimmed (during
1060 // merging of final data).
1061
1062 if (aliasTo >= 0) return; // Nothing to do for aliases
1063
1064 // If there are zero transitions and one type, then leave that as-is.
1065 if (transitions.size() == 0) {
1066 if (types.size() != 1) {
1067 cerr << "Error: transition count = 0, type count = " << types.size() << endl;
1068 }
1069 return;
1070 }
1071
1072 set<SimplifiedZoneType> simpleset;
1073 for (vector<Transition>::const_iterator i=transitions.begin();
1074 i!=transitions.end(); ++i) {
1075 assert(i->type < (int)types.size());
1076 simpleset.insert(types[i->type]);
1077 }
1078
1079 // Map types to integer indices
1080 map<SimplifiedZoneType,int> simplemap;
1081 int n=0;
1082 for (set<SimplifiedZoneType>::const_iterator i=simpleset.begin();
1083 i!=simpleset.end(); ++i) {
1084 simplemap[*i] = n++;
1085 }
1086
1087 // Remap transitions
1088 for (vector<Transition>::iterator i=transitions.begin();
1089 i!=transitions.end(); ++i) {
1090 assert(i->type < (int)types.size());
1091 ZoneType oldtype = types[i->type];
1092 SimplifiedZoneType newtype(oldtype);
1093 assert(simplemap.find(newtype) != simplemap.end());
1094 i->type = simplemap[newtype];
1095 }
1096
1097 // Replace type list
1098 types.clear();
1099 copy(simpleset.begin(), simpleset.end(), back_inserter(types));
1100 }
1101
1102 /**
1103 * Merge final zone data into this zone.
1104 */
1105 void ZoneInfo::mergeFinalData(const FinalZone& fz) {
1106 int year = fz.year;
1107 long seconds = yearToSeconds(year);
1108 vector<Transition>::iterator it =
1109 find_if(transitions.begin(), transitions.end(),
1110 bind2nd(ptr_fun(isAfter), seconds));
1111 transitions.erase(it, transitions.end());
1112
1113 if (finalYear != -1) {
1114 throw invalid_argument("Final zone already merged in");
1115 }
1116 finalYear = fz.year;
1117 finalOffset = fz.offset;
1118 finalRuleID = fz.ruleid;
1119 }
1120
1121 /**
1122 * Merge the data from the given final zone into the core zone data by
1123 * calling the ZoneInfo member function mergeFinalData.
1124 */
1125 void mergeOne(const string& zoneid, const FinalZone& fz) {
1126 if (ZONEINFO.find(zoneid) == ZONEINFO.end()) {
1127 throw invalid_argument("Unrecognized final zone ID");
1128 }
1129 ZONEINFO[zoneid].mergeFinalData(fz);
1130 }
1131
1132 /**
1133 * Visitor function that merges the final zone data into the main zone
1134 * data structures. It calls mergeOne for each final zone and its
1135 * list of aliases.
1136 */
1137 void mergeFinalZone(const pair<string,FinalZone>& p) {
1138 const string& id = p.first;
1139 const FinalZone& fz = p.second;
1140
1141 mergeOne(id, fz);
1142 }
1143
1144 /**
1145 * Print this rule in resource bundle format to os. ID and enclosing
1146 * braces handled elsewhere.
1147 */
1148 void FinalRule::print(ostream& os) const {
1149 // First print the rule part that enters DST; then the rule part
1150 // that exits it.
1151 int whichpart = (part[0].offset != 0) ? 0 : 1;
1152 assert(part[whichpart].offset != 0);
1153 assert(part[1-whichpart].offset == 0);
1154
1155 os << " ";
1156 for (int i=0; i<2; ++i) {
1157 const FinalRulePart& p = part[whichpart];
1158 whichpart = 1-whichpart;
1159 os << p.month << ", " << p.stz_dowim() << ", " << p.stz_dow() << ", "
1160 << p.time << ", " << p.timemode() << ", ";
1161 }
1162 os << part[whichpart].offset << endl;
1163 }
1164
1165 int main(int argc, char *argv[]) {
1166 string rootpath, zonetab, version;
1167
1168 if (argc != 4) {
1169 cout << "Usage: tz2icu <dir> <cmap> <vers>" << endl
1170 << " <dir> path to zoneinfo file tree generated by" << endl
1171 << " ICU-patched version of zic" << endl
1172 << " <cmap> country map, from tzdata archive," << endl
1173 << " typically named \"zone.tab\"" << endl
1174 << " <vers> version string, such as \"2003e\"" << endl;
1175 exit(1);
1176 } else {
1177 rootpath = argv[1];
1178 zonetab = argv[2];
1179 version = argv[3];
1180 }
1181
1182 cout << "Olson data version: " << version << endl;
1183
1184 try {
1185 ifstream finals(ICU_ZONE_FILE);
1186 if (finals) {
1187 readFinalZonesAndRules(finals);
1188
1189 cout << "Finished reading " << finalZones.size()
1190 << " final zones and " << finalRules.size()
1191 << " final rules from " ICU_ZONE_FILE << endl;
1192 } else {
1193 cerr << "Error: Unable to open " ICU_ZONE_FILE << endl;
1194 return 1;
1195 }
1196 } catch (const exception& error) {
1197 cerr << "Error: While reading " ICU_ZONE_FILE ": " << error.what() << endl;
1198 return 1;
1199 }
1200
1201 // Read the legacy alias list and process it. Treat the legacy mappings
1202 // like links, but also record them in the "legacy" hash.
1203 try {
1204 ifstream aliases(ICU_TZ_ALIAS);
1205 if (!aliases) {
1206 cerr << "Error: Unable to open " ICU_TZ_ALIAS << endl;
1207 return 1;
1208 }
1209 int n = 0;
1210 string line;
1211 while (getline(aliases, line)) {
1212 string::size_type lb = line.find('#');
1213 if (lb != string::npos) {
1214 line.resize(lb); // trim comments
1215 }
1216 vector<string> a;
1217 istringstream is(line);
1218 copy(istream_iterator<string>(is),istream_iterator<string>(),
1219 back_inserter(a));
1220 if (a.size() == 0) continue; // blank line
1221 if (a.size() != 2) {
1222 cerr << "Error: Can't parse \"" << line << "\" in "
1223 ICU_TZ_ALIAS << endl;
1224 exit(1);
1225 }
1226 ++n;
1227
1228 string alias(a[0]), olson(a[1]);
1229 if (links.find(alias) != links.end()) {
1230 cerr << "Error: Alias \"" << alias
1231 << "\" is an Olson zone in "
1232 ICU_TZ_ALIAS << endl;
1233 return 1;
1234 }
1235 if (reverseLinks.find(alias) != reverseLinks.end()) {
1236 cerr << "Error: Alias \"" << alias
1237 << "\" is an Olson link to \"" << reverseLinks[olson]
1238 << "\" in " << ICU_TZ_ALIAS << endl;
1239 return 1;
1240 }
1241
1242 // Record source for error reporting
1243 if (linkSource.find(olson) == linkSource.end()) {
1244 linkSource[olson] = "ICU alias";
1245 }
1246 assert(linkSource.find(alias) == linkSource.end());
1247 linkSource[alias] = "ICU alias";
1248
1249 links[olson].insert(alias);
1250 reverseLinks[alias] = olson;
1251 }
1252 cout << "Finished reading " << n
1253 << " aliases from " ICU_TZ_ALIAS << endl;
1254 } catch (const exception& error) {
1255 cerr << "Error: While reading " ICU_TZ_ALIAS ": " << error.what() << endl;
1256 return 1;
1257 }
1258
1259 try {
1260 // Recursively scan all files below the given path, accumulating
1261 // their data into ZONEINFO. All files must be TZif files. Any
1262 // failure along the way will result in a call to exit(1).
1263 scandir(rootpath);
1264 } catch (const exception& error) {
1265 cerr << "Error: While scanning " << rootpath << ": " << error.what() << endl;
1266 return 1;
1267 }
1268
1269 cout << "Finished reading " << ZONEINFO.size() << " zoneinfo files ["
1270 << (ZONEINFO.begin())->first << ".."
1271 << (--ZONEINFO.end())->first << "]" << endl;
1272
1273 try {
1274 for_each(finalZones.begin(), finalZones.end(), mergeFinalZone);
1275 } catch (const exception& error) {
1276 cerr << "Error: While merging final zone data: " << error.what() << endl;
1277 return 1;
1278 }
1279
1280 // Process links (including ICU aliases). For each link set we have
1281 // a canonical ID (e.g., America/Los_Angeles) and a set of one or more
1282 // aliases (e.g., PST, PST8PDT, ...).
1283
1284 // 1. Add all aliases as zone objects in ZONEINFO
1285 for (map<string,set<string> >::const_iterator i = links.begin();
1286 i!=links.end(); ++i) {
1287 const string& olson = i->first;
1288 const set<string>& aliases = i->second;
1289 if (ZONEINFO.find(olson) == ZONEINFO.end()) {
1290 cerr << "Error: Invalid " << linkSource[olson] << " to non-existent \""
1291 << olson << "\"" << endl;
1292 return 1;
1293 }
1294 for (set<string>::const_iterator j=aliases.begin();
1295 j!=aliases.end(); ++j) {
1296 ZONEINFO[*j] = ZoneInfo();
1297 }
1298 }
1299
1300 // 2. Create a mapping from zones to index numbers 0..n-1.
1301 map<string,int> zoneIDs;
1302 vector<string> zoneIDlist;
1303 int z=0;
1304 for (ZoneMap::iterator i=ZONEINFO.begin(); i!=ZONEINFO.end(); ++i) {
1305 zoneIDs[i->first] = z++;
1306 zoneIDlist.push_back(i->first);
1307 }
1308 assert(z == (int) ZONEINFO.size());
1309
1310 // 3. Merge aliases. Sometimes aliases link to other aliases; we
1311 // resolve these into simplest possible sets.
1312 map<string,set<string> > links2;
1313 map<string,string> reverse2;
1314 for (map<string,set<string> >::const_iterator i = links.begin();
1315 i!=links.end(); ++i) {
1316 string olson = i->first;
1317 while (reverseLinks.find(olson) != reverseLinks.end()) {
1318 olson = reverseLinks[olson];
1319 }
1320 for (set<string>::const_iterator j=i->second.begin(); j!=i->second.end(); ++j) {
1321 links2[olson].insert(*j);
1322 reverse2[*j] = olson;
1323 }
1324 }
1325 links = links2;
1326 reverseLinks = reverse2;
1327
1328 if (false) { // Debugging: Emit link map
1329 for (map<string,set<string> >::const_iterator i = links.begin();
1330 i!=links.end(); ++i) {
1331 cout << i->first << ": ";
1332 for (set<string>::const_iterator j=i->second.begin(); j!=i->second.end(); ++j) {
1333 cout << *j << ", ";
1334 }
1335 cout << endl;
1336 }
1337 }
1338
1339 // 4. Update aliases
1340 for (map<string,set<string> >::const_iterator i = links.begin();
1341 i!=links.end(); ++i) {
1342 const string& olson = i->first;
1343 const set<string>& aliases = i->second;
1344 ZONEINFO[olson].clearAliases();
1345 ZONEINFO[olson].addAlias(zoneIDs[olson]);
1346 for (set<string>::const_iterator j=aliases.begin();
1347 j!=aliases.end(); ++j) {
1348 assert(zoneIDs.find(olson) != zoneIDs.end());
1349 assert(zoneIDs.find(*j) != zoneIDs.end());
1350 assert(ZONEINFO.find(*j) != ZONEINFO.end());
1351 ZONEINFO[*j].setAliasTo(zoneIDs[olson]);
1352 ZONEINFO[olson].addAlias(zoneIDs[*j]);
1353 }
1354 }
1355
1356 // Once merging of final data is complete, we can optimize the type list
1357 for (ZoneMap::iterator i=ZONEINFO.begin(); i!=ZONEINFO.end(); ++i) {
1358 i->second.optimizeTypeList();
1359 }
1360
1361 // Create the country map
1362 map<string, set<string> > countryMap; // country -> set of zones
1363 map<string, string> reverseCountryMap; // zone -> country
1364 try {
1365 ifstream f(zonetab.c_str());
1366 if (!f) {
1367 cerr << "Error: Unable to open " << zonetab << endl;
1368 return 1;
1369 }
1370 int n = 0;
1371 string line;
1372 while (getline(f, line)) {
1373 string::size_type lb = line.find('#');
1374 if (lb != string::npos) {
1375 line.resize(lb); // trim comments
1376 }
1377 string country, coord, zone;
1378 istringstream is(line);
1379 is >> country >> coord >> zone;
1380 if (country.size() == 0) continue;
1381 if (country.size() != 2 || zone.size() < 1) {
1382 cerr << "Error: Can't parse " << line << " in " << zonetab << endl;
1383 return 1;
1384 }
1385 if (ZONEINFO.find(zone) == ZONEINFO.end()) {
1386 cerr << "Error: Country maps to invalid zone " << zone
1387 << " in " << zonetab << endl;
1388 return 1;
1389 }
1390 countryMap[country].insert(zone);
1391 reverseCountryMap[zone] = country;
1392 //cerr << (n+1) << ": " << country << " <=> " << zone << endl;
1393 ++n;
1394 }
1395 cout << "Finished reading " << n
1396 << " country entries from " << zonetab << endl;
1397 } catch (const exception& error) {
1398 cerr << "Error: While reading " << zonetab << ": " << error.what() << endl;
1399 return 1;
1400 }
1401
1402 // Merge ICU aliases into country map. Don't merge any alias
1403 // that already has a country map, since that doesn't make sense.
1404 // E.g. "Link Europe/Oslo Arctic/Longyearbyen" doesn't mean we
1405 // should cross-map the countries between these two zones.
1406 for (map<string,set<string> >::const_iterator i = links.begin();
1407 i!=links.end(); ++i) {
1408 const string& olson(i->first);
1409 if (reverseCountryMap.find(olson) == reverseCountryMap.end()) {
1410 continue;
1411 }
1412 string c = reverseCountryMap[olson];
1413 const set<string>& aliases(i->second);
1414 for (set<string>::const_iterator j=aliases.begin();
1415 j != aliases.end(); ++j) {
1416 if (reverseCountryMap.find(*j) == reverseCountryMap.end()) {
1417 countryMap[c].insert(*j);
1418 reverseCountryMap[*j] = c;
1419 //cerr << "Aliased country: " << c << " <=> " << *j << endl;
1420 }
1421 }
1422 }
1423
1424 // Create a pseudo-country containing all zones belonging to no country
1425 set<string> nocountry;
1426 for (ZoneMap::iterator i=ZONEINFO.begin(); i!=ZONEINFO.end(); ++i) {
1427 if (reverseCountryMap.find(i->first) == reverseCountryMap.end()) {
1428 nocountry.insert(i->first);
1429 }
1430 }
1431 countryMap[""] = nocountry;
1432
1433 // Get local time & year for below
1434 time_t sec;
1435 time(&sec);
1436 struct tm* now = localtime(&sec);
1437 int thisYear = now->tm_year + 1900;
1438
1439 // Write out a resource-bundle source file containing data for
1440 // all zones.
1441 ofstream file(ICU_TZ_RESOURCE ".txt");
1442 if (file) {
1443 file << "//---------------------------------------------------------" << endl
1444 << "// Copyright (C) 2003";
1445 if (thisYear > 2003) {
1446 file << "-" << thisYear;
1447 }
1448 file << ", International Business Machines" << endl
1449 << "// Corporation and others. All Rights Reserved." << endl
1450 << "//---------------------------------------------------------" << endl
1451 << "// Build tool: tz2icu" << endl
1452 << "// Build date: " << asctime(now) /* << endl -- asctime emits CR */
1453 << "// Olson source: ftp://elsie.nci.nih.gov/pub/" << endl
1454 << "// Olson version: " << version << endl
1455 << "// ICU version: " << U_ICU_VERSION << endl
1456 << "//---------------------------------------------------------" << endl
1457 << "// >> !!! >> THIS IS A MACHINE-GENERATED FILE << !!! <<" << endl
1458 << "// >> !!! >>> DO NOT EDIT <<< !!! <<" << endl
1459 << "//---------------------------------------------------------" << endl
1460 << endl
1461 << ICU_TZ_RESOURCE ":table(nofallback) {" << endl
1462 << " TZVersion { \"" << version << "\" }" << endl
1463 << " Zones:array { " << endl
1464 << ZONEINFO // Zones (the actual data)
1465 << " }" << endl;
1466
1467 // Names correspond to the Zones list, used for binary searching.
1468 printStringList ( file, ZONEINFO ); // print the Names list
1469
1470 // Final Rules are used if requested by the zone
1471 file << " Rules { " << endl;
1472 // Emit final rules
1473 int frc = 0;
1474 for(map<string,FinalRule>::iterator i=finalRules.begin();
1475 i!=finalRules.end(); ++i) {
1476 const string& id = i->first;
1477 const FinalRule& r = i->second;
1478 file << " " << id << ":intvector {" << endl;
1479 r.print(file);
1480 file << " } //_#" << frc++ << endl;
1481 }
1482 file << " }" << endl;
1483
1484 // Emit country (region) map. Emitting the string zone IDs results
1485 // in a 188 kb binary resource; emitting the zone index numbers
1486 // trims this to 171 kb. More work for the runtime code, but
1487 // a smaller data footprint.
1488 file << " Regions { " << endl;
1489 int rc = 0;
1490 for (map<string, set<string> >::const_iterator i=countryMap.begin();
1491 i != countryMap.end(); ++i) {
1492 string country = i->first;
1493 const set<string>& zones(i->second);
1494 file << " ";
1495 if(country[0]==0) {
1496 file << "Default";
1497 }
1498 file << country << ":intvector { ";
1499 bool first = true;
1500 for (set<string>::const_iterator j=zones.begin();
1501 j != zones.end(); ++j) {
1502 if (!first) file << ", ";
1503 first = false;
1504 if (zoneIDs.find(*j) == zoneIDs.end()) {
1505 cerr << "Error: Nonexistent zone in country map: " << *j << endl;
1506 return 1;
1507 }
1508 file << zoneIDs[*j]; // emit the zone's index number
1509 }
1510 file << " } //R#" << rc++ << endl;
1511 }
1512 file << " }" << endl;
1513
1514 file << "}" << endl;
1515 }
1516
1517 file.close();
1518
1519 if (file) { // recheck error bit
1520 cout << "Finished writing " ICU_TZ_RESOURCE ".txt" << endl;
1521 } else {
1522 cerr << "Error: Unable to open/write to " ICU_TZ_RESOURCE ".txt" << endl;
1523 return 1;
1524 }
1525
1526 #define ICU4J_TZ_CLASS "ZoneMetaData"
1527
1528 // Write out a Java source file containing only a few pieces of
1529 // meta-data missing from the core JDK: the equivalency lists and
1530 // the country map.
1531 ofstream java(ICU4J_TZ_CLASS ".java");
1532 if (java) {
1533 java << "//---------------------------------------------------------" << endl
1534 << "// Copyright (C) 2003";
1535 if (thisYear > 2003) {
1536 java << "-" << thisYear;
1537 }
1538 java << ", International Business Machines" << endl
1539 << "// Corporation and others. All Rights Reserved." << endl
1540 << "//---------------------------------------------------------" << endl
1541 << "// Build tool: tz2icu" << endl
1542 << "// Build date: " << asctime(now) /* << endl -- asctime emits CR */
1543 << "// Olson source: ftp://elsie.nci.nih.gov/pub/" << endl
1544 << "// Olson version: " << version << endl
1545 << "// ICU version: " << U_ICU_VERSION << endl
1546 << "//---------------------------------------------------------" << endl
1547 << "// >> !!! >> THIS IS A MACHINE-GENERATED FILE << !!! <<" << endl
1548 << "// >> !!! >>> DO NOT EDIT <<< !!! <<" << endl
1549 << "//---------------------------------------------------------" << endl
1550 << endl
1551 << "package com.ibm.icu.impl;" << endl
1552 << endl
1553 << "public final class " ICU4J_TZ_CLASS " {" << endl;
1554
1555 // Emit equivalency lists
1556 bool first1 = true;
1557 java << " public static final String VERSION = \"" + version + "\";" << endl;
1558 java << " public static final String[][] EQUIV = {" << endl;
1559 for (ZoneMap::const_iterator i=ZONEINFO.begin(); i!=ZONEINFO.end(); ++i) {
1560 if (i->second.isAlias() || i->second.getAliases().size() == 0) {
1561 continue;
1562 }
1563 if (!first1) java << "," << endl;
1564 first1 = false;
1565 // The ID of this zone (the canonical zone, to which the
1566 // aliases point) will be sorted into the list, so it
1567 // won't be at position 0. If we want to know which is
1568 // the canonical zone, we should move it to position 0.
1569 java << " { ";
1570 bool first2 = true;
1571 const set<int>& s = i->second.getAliases();
1572 for (set<int>::const_iterator j=s.begin(); j!=s.end(); ++j) {
1573 if (!first2) java << ", ";
1574 java << '"' << zoneIDlist[*j] << '"';
1575 first2 = false;
1576 }
1577 java << " }";
1578 }
1579 java << endl
1580 << " };" << endl;
1581
1582 // Emit country map.
1583 first1 = true;
1584 java << " public static final String[][] COUNTRY = {" << endl;
1585 for (map<string, set<string> >::const_iterator i=countryMap.begin();
1586 i != countryMap.end(); ++i) {
1587 if (!first1) java << "," << endl;
1588 first1 = false;
1589 string country = i->first;
1590 const set<string>& zones(i->second);
1591 java << " { \"" << country << '"';
1592 for (set<string>::const_iterator j=zones.begin();
1593 j != zones.end(); ++j) {
1594 java << ", \"" << *j << '"';
1595 }
1596 java << " }";
1597 }
1598 java << endl
1599 << " };" << endl;
1600
1601 java << "}" << endl;
1602 }
1603
1604 java.close();
1605
1606 if (java) { // recheck error bit
1607 cout << "Finished writing " ICU4J_TZ_CLASS ".java" << endl;
1608 } else {
1609 cerr << "Error: Unable to open/write to " ICU4J_TZ_CLASS ".java" << endl;
1610 return 1;
1611 }
1612
1613 return 0;
1614 }
1615
1616 //eof