3 **********************************************************************
4 * Copyright (c) 2003-2006, International Business Machines
5 * Corporation and others. All Rights Reserved.
6 **********************************************************************
8 * Created: July 10 2003
10 **********************************************************************
12 #include "tzfile.h" // from Olson tzcode archive, copied to this dir
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)
49 #include "unicode/uversion.h"
53 //--------------------------------------------------------------------
55 //--------------------------------------------------------------------
57 const long SECS_PER_YEAR
= 31536000; // 365 days
58 const long SECS_PER_LEAP_YEAR
= 31622400; // 366 days
61 return (y%4
== 0) && ((y%100
!= 0) || (y%400
== 0)); // Gregorian
64 long secsPerYear(int y
) {
65 return isLeap(y
) ? SECS_PER_LEAP_YEAR
: SECS_PER_YEAR
;
69 * Given a calendar year, return the GMT epoch seconds for midnight
70 * GMT of January 1 of that year. yearToSeconds(1970) == 0.
72 long yearToSeconds(int year
) {
73 // inefficient but foolproof
77 s
+= secsPerYear(y
++);
80 s
-= secsPerYear(--y
);
86 * Given 1970 GMT epoch seconds, return the calendar year containing
87 * that time. secondsToYear(0) == 1970.
89 int secondsToYear(long seconds
) {
90 // inefficient but foolproof
95 s
+= secsPerYear(y
++);
96 if (s
> seconds
) break;
101 s
-= secsPerYear(--y
);
102 if (s
<= seconds
) break;
108 //--------------------------------------------------------------------
110 //--------------------------------------------------------------------
114 struct SimplifiedZoneType
;
116 // A transition from one ZoneType to another
117 // Minimal size = 5 bytes (4+1)
119 long time
; // seconds, 1970 epoch
120 int type
; // index into 'ZoneInfo.types' 0..255
121 Transition(long _time
, int _type
) {
127 // A behavior mode (what zic calls a 'type') of a time zone.
128 // Minimal size = 6 bytes (4+1+3bits)
129 // SEE: SimplifiedZoneType
131 long rawoffset
; // raw seconds offset from GMT
132 long dstoffset
; // dst seconds offset from GMT
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
141 ZoneType(const SimplifiedZoneType
&); // used by optimizeTypeList
143 ZoneType() : rawoffset(-1), dstoffset(-1), abbr(-1) {}
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
;
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.
156 // This object represents the contents of a single zic-created
159 vector
<Transition
> transitions
;
160 vector
<ZoneType
> types
;
161 vector
<string
> abbrs
;
165 int finalYear
; // -1 if none
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
171 // If there are aliases TO this zone, then the following set will
172 // contain their index numbers (each index >= 0).
175 ZoneInfo() : finalYear(-1), aliasTo(-1) {}
177 void mergeFinalData(const FinalZone
& fz
);
179 void optimizeTypeList();
181 // Set this zone to be an alias TO another zone.
182 void setAliasTo(int index
);
184 // Clear the list of aliases OF this zone.
187 // Add an alias to the list of aliases OF this zone.
188 void addAlias(int index
);
190 // Is this an alias to another zone?
191 bool isAlias() const {
195 // Retrieve alias list
196 const set
<int>& getAliases() const {
200 void print(ostream
& os
, const string
& id
) const;
203 void ZoneInfo::clearAliases() {
208 void ZoneInfo::addAlias(int index
) {
209 assert(aliasTo
< 0 && index
>= 0 && aliases
.find(index
) == aliases
.end());
210 aliases
.insert(index
);
213 void ZoneInfo::setAliasTo(int index
) {
215 assert(aliases
.size() == 0);
219 typedef map
<string
, ZoneInfo
> ZoneMap
;
221 typedef ZoneMap::const_iterator ZoneMapIter
;
223 //--------------------------------------------------------------------
225 //--------------------------------------------------------------------
227 // Global map holding all our ZoneInfo objects, indexed by id.
230 //--------------------------------------------------------------------
231 // zoneinfo file parsing
232 //--------------------------------------------------------------------
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
239 file
.read((char*)buf
, 4);
240 for(int i
=0,shift
=24;i
<4;++i
,shift
-=8) {
241 val
|= buf
[i
] << shift
;
243 if (val
< minv
|| val
> maxv
) {
245 os
<< "coded value out-of-range: " << val
<< ", expected ["
246 << minv
<< ", " << maxv
<< "]";
247 throw out_of_range(os
.str());
252 // Read a boolean value
253 bool readbool(ifstream
& file
) {
258 os
<< "boolean value out-of-range: " << (int)c
;
259 throw out_of_range(os
.str());
265 * Read the zoneinfo file structure (see tzfile.h) into a ZoneInfo
266 * @param file an already-open file stream
268 void readzoneinfo(ifstream
& file
, ZoneInfo
& info
) {
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.
279 if (strncmp(buf
, TZ_ICU_MAGIC
, 4) != 0) {
280 throw invalid_argument("TZ_ICU_MAGIC signature missing");
282 // skip additional Olson byte version
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");
290 // Read reserved bytes. The first of these will be a version byte.
292 if (*(ICUZoneinfoVersion
*)&buf
!= TZ_ICU_VERSION
) {
293 throw invalid_argument("File version mismatch");
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);
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");
311 // Used temporarily to store transition times and types. We need
312 // to do this because the times and types are stored in two
314 vector
<long> transitionTimes(timecnt
, -1); // temporary
315 vector
<int> transitionTypes(timecnt
, -1); // temporary
317 // Read transition times
318 for (i
=0; i
<timecnt
; ++i
) {
319 transitionTimes
[i
] = readcoded(file
);
322 // Read transition types
323 for (i
=0; i
<timecnt
; ++i
) {
325 file
.read((char*) &c
, 1);
327 if (t
< 0 || t
>= typecnt
) {
329 os
<< "illegal type: " << t
<< ", expected [0, " << (typecnt
-1) << "]";
330 throw out_of_range(os
.str());
332 transitionTypes
[i
] = t
;
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
]));
340 // Read types (except for the isdst and isgmt flags, which come later (why??))
341 for (i
=0; i
<typecnt
; ++i
) {
344 type
.rawoffset
= readcoded(file
);
345 type
.dstoffset
= readcoded(file
);
346 type
.isdst
= readbool(file
);
349 file
.read((char*) &c
, 1);
352 if (type
.isdst
!= (type
.dstoffset
!= 0)) {
353 throw invalid_argument("isdst does not reflect dstoffset");
356 info
.types
.push_back(type
);
358 assert(info
.types
.size() == (unsigned) typecnt
);
360 // Read the abbreviation string
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
);
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
) {
374 info
.abbrs
.push_back(string(start
, p
-start
));
375 abbroffset
.push_back(start
-str
);
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.
382 // Keep track of which abbreviations get used.
383 vector
<bool> abbrseen(abbroffset
.size(), false);
385 for (vector
<ZoneType
>::iterator it
=info
.types
.begin();
386 it
!=info
.types
.end();
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.
398 // TODO: Re-enable this warning if we start using
399 // the Olson abbr data, or if the above TODO is completed.
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
) {
407 cerr
<< os
.str() << "; using 0" << endl
;
411 int index
= x
- abbroffset
.begin();
413 abbrseen
[index
] = true;
417 for (int ii
=0;ii
<(int) abbrseen
.size();++ii
) {
419 cerr
<< "Warning: unused abbreviation: " << ii
<< endl
;
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
432 for (i
=0; i
<typecnt
; ++i
) info
.types
[i
].isstd
= readbool(file
);
435 for (i
=0; i
<typecnt
; ++i
) info
.types
[i
].isgmt
= readbool(file
);
438 //--------------------------------------------------------------------
439 // Directory and file reading
440 //--------------------------------------------------------------------
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"
447 void handleFile(string path
, string id
) {
448 // Check for duplicate id
449 if (ZONEINFO
.find(id
) != ZONEINFO
.end()) {
451 os
<< "duplicate zone ID: " << id
;
452 throw invalid_argument(os
.str());
455 ifstream
file(path
.c_str(), ios::in
| ios::binary
);
457 throw invalid_argument("can't open file");
460 readzoneinfo(file
, info
);
464 throw invalid_argument("read error");
467 // Check eof-relative pos (there may be a cleaner way to do this)
468 long eofPos
= (long) file
.tellg();
471 file
.seekg(0, ios::end
);
472 eofPos
= eofPos
- (long) file
.tellg();
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) {
480 os
<< (-eofPos
) << " unprocessed bytes at end";
481 throw invalid_argument(os
.str());
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
496 void scandir(string dirname
, string prefix
="") {
498 WIN32_FIND_DATA FileData
;
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
;
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
+ "/");
515 string id
= prefix
+ name
;
516 handleFile(path
, id
);
517 } catch (const exception
& e
) {
518 cerr
<< "Error: While processing \"" << path
<< "\", "
524 if (!FindNextFile(hList
, &FileData
)) {
525 if (GetLastError() == ERROR_NO_MORE_FILES
) {
535 void scandir(string dir
, string prefix
="") {
537 struct dirent
*dir_entry
;
538 struct stat stat_info
;
540 vector
<string
> subdirs
;
541 vector
<string
> subfiles
;
543 if ((dp
= opendir(dir
.c_str())) == NULL
) {
544 cerr
<< "Error: Invalid directory: " << dir
<< endl
;
547 if (!getcwd(pwd
, sizeof(pwd
))) {
548 cerr
<< "Error: Directory name too long" << endl
;
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 + "/");
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
<< "\", "
578 for(int i
=0;i
<(int)subfiles
.size();i
+=2) {
580 handleFile(subfiles
[i
], subfiles
[i
+1]);
581 } catch (const exception
& e
) {
582 cerr
<< "Error: While processing \"" << subfiles
[i
] << "\", "
587 for(int i
=0;i
<(int)subdirs
.size();i
+=2) {
588 scandir(subdirs
[i
], subdirs
[i
+1]);
594 //--------------------------------------------------------------------
595 // Final zone and rule info
596 //--------------------------------------------------------------------
599 * Read and discard the current line.
601 void consumeLine(istream
& in
) {
605 } while (c
!= EOF
&& c
!= '\n');
614 const char* TIME_MODE
[] = {"w", "s", "u"};
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};
620 const int HOUR
= 3600;
623 int offset
; // raw offset
624 int year
; // takes effect for y >= year
627 FinalZone(int _offset
, int _year
, const string
& _ruleid
) :
628 offset(_offset
), year(_year
), ruleid(_ruleid
) {
629 if (offset
<= -16*HOUR
|| offset
>= 16*HOUR
) {
631 os
<< "Invalid input offset " << offset
632 << " for year " << year
633 << " and rule ID " << ruleid
;
634 throw invalid_argument(os
.str());
636 if (year
< 1900 || year
>= 2050) {
638 os
<< "Invalid input year " << year
639 << " with offset " << offset
640 << " and rule ID " << ruleid
;
641 throw invalid_argument(os
.str());
644 FinalZone() : offset(-1), year(-1) {}
645 void addLink(const string
& alias
) {
646 if (aliases
.find(alias
) != aliases
.end()) {
648 os
<< "Duplicate alias " << alias
;
649 throw invalid_argument(os
.str());
651 aliases
.insert(alias
);
655 struct FinalRulePart
{
661 int offset
; // dst offset, usually either 0 or 1:00
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;
680 bool isset
; // used during building; later ignored
682 FinalRulePart() : isset(false) {}
683 void set(const string
& id
,
693 throw invalid_argument("FinalRulePart set twice");
696 if (_mode
== "DOWLEQ") {
698 } else if (_mode
== "DOWGEQ") {
700 } else if (_mode
== "DOM") {
703 throw invalid_argument("Unrecognized FinalRulePart mode");
714 if (month
< 0 || month
>= 12) {
715 os
<< "Invalid input month " << month
;
717 if (dom
< 1 || dom
> MONTH_LEN
[month
]) {
718 os
<< "Invalid input day of month " << dom
;
720 if (mode
!= DOM
&& (dow
< 0 || dow
>= 7)) {
721 os
<< "Invalid input day of week " << dow
;
723 if (offset
< 0 || offset
> HOUR
) {
724 os
<< "Invalid input offset " << offset
;
726 if (isgmt
&& !isstd
) {
727 os
<< "Invalid input isgmt && !isstd";
729 if (!os
.str().empty()) {
733 << month
<< dom
<< dow
<< time
736 throw invalid_argument(os
.str());
741 * Return the time mode as an ICU SimpleTimeZone int from 0..2;
744 int timemode() const {
747 return 2; // gmt standard
750 return 1; // local standard
752 return 0; // local wall
755 // The SimpleTimeZone encoding method for rules is as follows:
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.
767 * Return a "dowim" param suitable for SimpleTimeZone.
769 int stz_dowim() const {
770 return (mode
== DOWLEQ
) ? -dom
: dom
;
774 * Return a "dow" param suitable for SimpleTimeZone.
776 int stz_dow() const {
777 return (mode
== DOM
) ? 0 : -(dow
+1);
782 FinalRulePart part
[2];
785 return part
[0].isset
&& part
[1].isset
;
788 void print(ostream
& os
) const;
791 map
<string
,FinalZone
> finalZones
;
792 map
<string
,FinalRule
> finalRules
;
794 map
<string
, set
<string
> > links
;
795 map
<string
, string
> reverseLinks
;
796 map
<string
, string
> linkSource
; // id => "Olson link" or "ICU alias"
799 * Predicate used to find FinalRule objects that do not have both
800 * sub-parts set (indicating an error in the input file).
802 bool isNotSet(const pair
<const string
,FinalRule
>& p
) {
803 return !p
.second
.isset();
807 * Predicate used to find FinalZone objects that do not map to a known
808 * rule (indicating an error in the input file).
810 bool mapsToUnknownRule(const pair
<const string
,FinalZone
>& p
) {
811 return finalRules
.find(p
.second
.ruleid
) == finalRules
.end();
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
820 set
<string
> ruleIDset
;
822 void insertRuleID(const pair
<string
,FinalRule
>& p
) {
823 ruleIDset
.insert(p
.first
);
826 void eraseRuleID(const pair
<string
,FinalZone
>& p
) {
827 ruleIDset
.erase(p
.second
.ruleid
);
831 * Populate finalZones and finalRules from the given istream.
833 void readFinalZonesAndRules(istream
& in
) {
838 if (in
.eof() || !in
) {
840 } else if (token
== "zone") {
841 // zone Africa/Cairo 7200 1995 Egypt # zone Africa/Cairo, offset 7200, year >= 1995, rule Egypt (0)
844 in
>> id
>> offset
>> year
>> ruleid
;
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
851 int month
, dom
, dow
, time
, offset
;
853 in
>> id
>> mode
>> month
>> dom
>> dow
>> time
>> isstd
>> isgmt
>> offset
;
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");
866 links
[fromid
].insert(toid
);
867 reverseLinks
[toid
] = fromid
;
869 linkSource
[fromid
] = "Olson link";
870 linkSource
[toid
] = "Olson link";
871 } else if (token
.length() > 0 && token
[0] == '#') {
874 throw invalid_argument("Unrecognized keyword");
878 if (!in
.eof() && !in
) {
879 throw invalid_argument("Parse failure");
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");
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");
892 // Perform validity check: Each rule should be referred to by a zone.
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");
901 //--------------------------------------------------------------------
902 // Resource bundle output
903 //--------------------------------------------------------------------
905 // SEE olsontz.h FOR RESOURCE BUNDLE DATA LAYOUT
907 void ZoneInfo::print(ostream
& os
, const string
& id
) const {
908 // Implement compressed format #2:
910 os
<< " /* " << id
<< " */ ";
913 assert(aliases
.size() == 0);
914 os
<< ":int { " << aliasTo
<< " } "; // No endl - save room for comment.
918 os
<< ":array {" << endl
;
920 vector
<Transition
>::const_iterator trn
;
921 vector
<ZoneType
>::const_iterator typ
;
924 os
<< " :intvector { ";
925 for (trn
= transitions
.begin(); trn
!= transitions
.end(); ++trn
) {
926 if (!first
) os
<< ", ";
933 os
<< " :intvector { ";
934 for (typ
= types
.begin(); typ
!= types
.end(); ++typ
) {
935 if (!first
) os
<< ", ";
937 os
<< typ
->rawoffset
<< ", " << typ
->dstoffset
;
941 os
<< " :bin { \"" << hex
<< setfill('0');
942 for (trn
= transitions
.begin(); trn
!= transitions
.end(); ++trn
) {
943 os
<< setw(2) << trn
->type
;
945 os
<< dec
<< "\" }" << endl
;
947 // Final zone info, if any
948 if (finalYear
!= -1) {
949 os
<< " \"" << finalRuleID
<< "\"" << endl
;
950 os
<< " :intvector { " << finalOffset
<< ", "
951 << finalYear
<< " }" << endl
;
954 // Alias list, if any
955 if (aliases
.size() != 0) {
957 os
<< " :intvector { ";
958 for (set
<int>::const_iterator i
=aliases
.begin(); i
!=aliases
.end(); ++i
) {
959 if (!first
) os
<< ", ";
966 os
<< " } "; // no trailing 'endl', so comments can be placed.
970 operator<<(ostream
& os
, const ZoneMap
& zoneinfo
) {
972 for (ZoneMapIter it
= zoneinfo
.begin();
973 it
!= zoneinfo
.end();
976 it
->second
.print(os
, it
->first
);
977 os
<< "//Z#" << c
++ << endl
;
982 // print the string list
983 ostream
& printStringList( ostream
& os
, const ZoneMap
& zoneinfo
) {
985 int col
= 0; // column
986 os
<< " Names {" << endl
988 for (ZoneMapIter it
= zoneinfo
.begin();
989 it
!= zoneinfo
.end();
995 const string
& id
= it
->first
;
996 os
<< "\"" << id
<< "\"";
997 col
+= id
.length() + 2;
999 os
<< " // " << n
<< endl
1005 os
<< " // " << (n
-1) << endl
1011 //--------------------------------------------------------------------
1013 //--------------------------------------------------------------------
1015 // Unary predicate for finding transitions after a given time
1016 bool isAfter(const Transition t
, long thresh
) {
1017 return t
.time
>= thresh
;
1021 * A zone type that contains only the raw and dst offset. Used by the
1022 * optimizeTypeList() method.
1024 struct SimplifiedZoneType
{
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
);
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).
1044 ZoneType::ZoneType(const SimplifiedZoneType
& t
) :
1045 rawoffset(t
.rawoffset
), dstoffset(t
.dstoffset
),
1046 abbr(-1), isdst(false), isstd(false), isgmt(false) {}
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.
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).
1062 if (aliasTo
>= 0) return; // Nothing to do for aliases
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
;
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
]);
1079 // Map types to integer indices
1080 map
<SimplifiedZoneType
,int> simplemap
;
1082 for (set
<SimplifiedZoneType
>::const_iterator i
=simpleset
.begin();
1083 i
!=simpleset
.end(); ++i
) {
1084 simplemap
[*i
] = n
++;
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
];
1097 // Replace type list
1099 copy(simpleset
.begin(), simpleset
.end(), back_inserter(types
));
1103 * Merge final zone data into this zone.
1105 void ZoneInfo::mergeFinalData(const FinalZone
& fz
) {
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());
1113 if (finalYear
!= -1) {
1114 throw invalid_argument("Final zone already merged in");
1116 finalYear
= fz
.year
;
1117 finalOffset
= fz
.offset
;
1118 finalRuleID
= fz
.ruleid
;
1122 * Merge the data from the given final zone into the core zone data by
1123 * calling the ZoneInfo member function mergeFinalData.
1125 void mergeOne(const string
& zoneid
, const FinalZone
& fz
) {
1126 if (ZONEINFO
.find(zoneid
) == ZONEINFO
.end()) {
1127 throw invalid_argument("Unrecognized final zone ID");
1129 ZONEINFO
[zoneid
].mergeFinalData(fz
);
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
1137 void mergeFinalZone(const pair
<string
,FinalZone
>& p
) {
1138 const string
& id
= p
.first
;
1139 const FinalZone
& fz
= p
.second
;
1145 * Print this rule in resource bundle format to os. ID and enclosing
1146 * braces handled elsewhere.
1148 void FinalRule::print(ostream
& os
) const {
1149 // First print the rule part that enters DST; then the rule part
1151 int whichpart
= (part
[0].offset
!= 0) ? 0 : 1;
1152 assert(part
[whichpart
].offset
!= 0);
1153 assert(part
[1-whichpart
].offset
== 0);
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() << ", ";
1162 os
<< part
[whichpart
].offset
<< endl
;
1165 int main(int argc
, char *argv
[]) {
1166 string rootpath
, zonetab
, version
;
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
;
1182 cout
<< "Olson data version: " << version
<< endl
;
1185 ifstream
finals(ICU_ZONE_FILE
);
1187 readFinalZonesAndRules(finals
);
1189 cout
<< "Finished reading " << finalZones
.size()
1190 << " final zones and " << finalRules
.size()
1191 << " final rules from " ICU_ZONE_FILE
<< endl
;
1193 cerr
<< "Error: Unable to open " ICU_ZONE_FILE
<< endl
;
1196 } catch (const exception
& error
) {
1197 cerr
<< "Error: While reading " ICU_ZONE_FILE
": " << error
.what() << endl
;
1201 // Read the legacy alias list and process it. Treat the legacy mappings
1202 // like links, but also record them in the "legacy" hash.
1204 ifstream
aliases(ICU_TZ_ALIAS
);
1206 cerr
<< "Error: Unable to open " ICU_TZ_ALIAS
<< endl
;
1211 while (getline(aliases
, line
)) {
1212 string::size_type lb
= line
.find('#');
1213 if (lb
!= string::npos
) {
1214 line
.resize(lb
); // trim comments
1217 istringstream
is(line
);
1218 copy(istream_iterator
<string
>(is
),istream_iterator
<string
>(),
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
;
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
;
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
;
1242 // Record source for error reporting
1243 if (linkSource
.find(olson
) == linkSource
.end()) {
1244 linkSource
[olson
] = "ICU alias";
1246 assert(linkSource
.find(alias
) == linkSource
.end());
1247 linkSource
[alias
] = "ICU alias";
1249 links
[olson
].insert(alias
);
1250 reverseLinks
[alias
] = olson
;
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
;
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).
1264 } catch (const exception
& error
) {
1265 cerr
<< "Error: While scanning " << rootpath
<< ": " << error
.what() << endl
;
1269 cout
<< "Finished reading " << ZONEINFO
.size() << " zoneinfo files ["
1270 << (ZONEINFO
.begin())->first
<< ".."
1271 << (--ZONEINFO
.end())->first
<< "]" << endl
;
1274 for_each(finalZones
.begin(), finalZones
.end(), mergeFinalZone
);
1275 } catch (const exception
& error
) {
1276 cerr
<< "Error: While merging final zone data: " << error
.what() << endl
;
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, ...).
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
;
1294 for (set
<string
>::const_iterator j
=aliases
.begin();
1295 j
!=aliases
.end(); ++j
) {
1296 ZONEINFO
[*j
] = ZoneInfo();
1300 // 2. Create a mapping from zones to index numbers 0..n-1.
1301 map
<string
,int> zoneIDs
;
1302 vector
<string
> zoneIDlist
;
1304 for (ZoneMap::iterator i
=ZONEINFO
.begin(); i
!=ZONEINFO
.end(); ++i
) {
1305 zoneIDs
[i
->first
] = z
++;
1306 zoneIDlist
.push_back(i
->first
);
1308 assert(z
== (int) ZONEINFO
.size());
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
];
1320 for (set
<string
>::const_iterator j
=i
->second
.begin(); j
!=i
->second
.end(); ++j
) {
1321 links2
[olson
].insert(*j
);
1322 reverse2
[*j
] = olson
;
1326 reverseLinks
= reverse2
;
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
) {
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
]);
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();
1361 // Create the country map
1362 map
<string
, set
<string
> > countryMap
; // country -> set of zones
1363 map
<string
, string
> reverseCountryMap
; // zone -> country
1365 ifstream
f(zonetab
.c_str());
1367 cerr
<< "Error: Unable to open " << zonetab
<< endl
;
1372 while (getline(f
, line
)) {
1373 string::size_type lb
= line
.find('#');
1374 if (lb
!= string::npos
) {
1375 line
.resize(lb
); // trim comments
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
;
1385 if (ZONEINFO
.find(zone
) == ZONEINFO
.end()) {
1386 cerr
<< "Error: Country maps to invalid zone " << zone
1387 << " in " << zonetab
<< endl
;
1390 countryMap
[country
].insert(zone
);
1391 reverseCountryMap
[zone
] = country
;
1392 //cerr << (n+1) << ": " << country << " <=> " << zone << endl;
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
;
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()) {
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;
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
);
1431 countryMap
[""] = nocountry
;
1433 // Get local time & year for below
1436 struct tm
* now
= localtime(&sec
);
1437 int thisYear
= now
->tm_year
+ 1900;
1439 // Write out a resource-bundle source file containing data for
1441 ofstream
file(ICU_TZ_RESOURCE
".txt");
1443 file
<< "//---------------------------------------------------------" << endl
1444 << "// Copyright (C) 2003";
1445 if (thisYear
> 2003) {
1446 file
<< "-" << thisYear
;
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
1461 << ICU_TZ_RESOURCE
":table(nofallback) {" << endl
1462 << " TZVersion { \"" << version
<< "\" }" << endl
1463 << " Zones:array { " << endl
1464 << ZONEINFO
// Zones (the actual data)
1467 // Names correspond to the Zones list, used for binary searching.
1468 printStringList ( file
, ZONEINFO
); // print the Names list
1470 // Final Rules are used if requested by the zone
1471 file
<< " Rules { " << endl
;
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
;
1480 file
<< " } //_#" << frc
++ << endl
;
1482 file
<< " }" << endl
;
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
;
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
);
1498 file
<< country
<< ":intvector { ";
1500 for (set
<string
>::const_iterator j
=zones
.begin();
1501 j
!= zones
.end(); ++j
) {
1502 if (!first
) file
<< ", ";
1504 if (zoneIDs
.find(*j
) == zoneIDs
.end()) {
1505 cerr
<< "Error: Nonexistent zone in country map: " << *j
<< endl
;
1508 file
<< zoneIDs
[*j
]; // emit the zone's index number
1510 file
<< " } //R#" << rc
++ << endl
;
1512 file
<< " }" << endl
;
1514 file
<< "}" << endl
;
1519 if (file
) { // recheck error bit
1520 cout
<< "Finished writing " ICU_TZ_RESOURCE
".txt" << endl
;
1522 cerr
<< "Error: Unable to open/write to " ICU_TZ_RESOURCE
".txt" << endl
;
1526 #define ICU4J_TZ_CLASS "ZoneMetaData"
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
1531 ofstream
java(ICU4J_TZ_CLASS
".java");
1533 java
<< "//---------------------------------------------------------" << endl
1534 << "// Copyright (C) 2003";
1535 if (thisYear
> 2003) {
1536 java
<< "-" << thisYear
;
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
1551 << "package com.ibm.icu.impl;" << endl
1553 << "public final class " ICU4J_TZ_CLASS
" {" << endl
;
1555 // Emit equivalency lists
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) {
1563 if (!first1
) java
<< "," << endl
;
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.
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
] << '"';
1582 // Emit country map.
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
;
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
<< '"';
1601 java
<< "}" << endl
;
1606 if (java
) { // recheck error bit
1607 cout
<< "Finished writing " ICU4J_TZ_CLASS
".java" << endl
;
1609 cerr
<< "Error: Unable to open/write to " ICU4J_TZ_CLASS
".java" << endl
;