122c09be40
PR 23566 * scripts/timezones.pl: Parse each file in 2 passes, in one parse just Rule lines, in the other everything else. Pass 0 instead of $savings as second argument to parseRule when parsing the start rule. * java/util/TimeZone.java (timezones): Regenerate from tzdata2007a. From-SVN: r121740
400 lines
11 KiB
Perl
Executable File
400 lines
11 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
# Create the timezone tables for java/util/TimeZone from the
|
|
# standard timezone sources by Arthur David Olson (as used by glibc)
|
|
#
|
|
# This needs the files from the package tzdata2000h which may be found
|
|
# at ftp://ftp.cs.mu.oz.au/pub/.
|
|
|
|
$TIMEZONEDIR = "tzdata";
|
|
@TIMEZONEFILES = ("africa", "antarctica", "asia", "australasia",
|
|
"europe", "northamerica", "pacificnew", "southamerica",
|
|
"../tzabbrevs");
|
|
|
|
# rules hash table:
|
|
# key is a rule name
|
|
# value is either "-" (no daylight savings) or a list of three elements:
|
|
# $value[0] = end savings rule (list containing MONTH, DAY and TIME)
|
|
# $value[1] = start savings rule (ditto)
|
|
# $value[2] = daylight offset in milliseconds
|
|
my %rules = ("-" => "-");
|
|
|
|
# timezones list, list of pairs:
|
|
# $timezones[$i][0] is a timezone name
|
|
# $timezones[$i][1] = raw offset in milliseconds
|
|
# $timezones[$i][2] = rule in the same format as the value of
|
|
# the rules table, but TIME in milliseconds
|
|
# $timezones[$i][3] = list of timezone names with this rule (aliases)
|
|
my @timezones = ( [ "GMT", 0, "-", [ "GMT", "UTC" ] ]);
|
|
|
|
|
|
# parse the offset of form +/-hh:mm:ss (:ss is optional) and return it
|
|
# in milliseconds against UTC
|
|
sub parseOffset($) {
|
|
my $offset = $_[0];
|
|
$offset =~ /^([+-]?)(\d+)(:(\d+)(:(\d+))?)?$/
|
|
or die "Can't parse offset $offset";
|
|
my $seconds = $2 * 3600;
|
|
$seconds += $4 * 60 if ($3);
|
|
$seconds += $6 if ($3 && $5);
|
|
if ($1 eq "-") {
|
|
$seconds = - $seconds;
|
|
}
|
|
return $seconds * 1000;
|
|
}
|
|
|
|
# parse the time of form +/-hh:mm:ss[swguz] (:ss is optional) and return it
|
|
# in milliseconds since midnight in local wall time
|
|
my $timezonename;
|
|
sub parseTime($$$) {
|
|
my ($rawoffset, $stdoffset, $time) = @_;
|
|
$time =~ /^([+-]?)(\d+):(\d+)(:(\d+))?([swguz]?)$/
|
|
or die "Can't parse time $time";
|
|
my ($hour, $min) = ($2, $3);
|
|
my $sec = ($4) ? $5 : 0;
|
|
my $millis = ((($hour * 60) + $min) * 60 + $sec) * 1000;
|
|
if ($1 eq "-") {
|
|
$millis = -$millis;
|
|
}
|
|
# Normally millis is in wall time, adjust for utc and standard time.
|
|
if ($6 =~ /[guz]/) {
|
|
$millis += $rawoffset + $stdoffset;
|
|
} elsif ($6 =~ /s/) {
|
|
$millis += $stdoffset;
|
|
}
|
|
return $millis;
|
|
}
|
|
|
|
my %monthnames =
|
|
( "Jan" => "1",
|
|
"Feb" => "2",
|
|
"Mar" => "3",
|
|
"Apr" => "4",
|
|
"May" => "5",
|
|
"Jun" => "6",
|
|
"Jul" => "7",
|
|
"Aug" => "8",
|
|
"Sep" => "9",
|
|
"Oct" => "10",
|
|
"Nov" => "11",
|
|
"Dec" => "12" );
|
|
sub parseMonth($) {
|
|
my $month = $monthnames{"$_[0]"} or die "Unknown month $_[0]";
|
|
return $month;
|
|
}
|
|
|
|
my %weekdaynames =
|
|
( "Sun" => "7",
|
|
"Mon" => "1",
|
|
"Tue" => "2",
|
|
"Wed" => "3",
|
|
"Thu" => "4",
|
|
"Fri" => "5",
|
|
"Sat" => "6" );
|
|
sub parseWeekday($) {
|
|
my $weekday = $weekdaynames{"$_[0]"} or die "Unknown weekday $_[0]";
|
|
return $weekday;
|
|
}
|
|
|
|
my @weekdayjavanames =
|
|
( "Calendar.SUNDAY",
|
|
"Calendar.MONDAY",
|
|
"Calendar.TUESDAY",
|
|
"Calendar.WEDNESDAY",
|
|
"Calendar.THURSDAY",
|
|
"Calendar.FRIDAY",
|
|
"Calendar.SATURDAY" );
|
|
my @daysInMonths = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
|
|
sub parseDay($$$) {
|
|
my ($dayoffset, $month, $day) = @_;
|
|
if ($day =~ /^\d+$/) {
|
|
return "$day, 0";
|
|
} elsif ($day =~ /^last([A-Z][a-z][a-z])$/) {
|
|
my $weekday = ( parseWeekday($1) + $dayoffset + 7 ) % 7;
|
|
if ($dayoffset) {
|
|
my $day = $daysInMonths[$month - 1] + $dayoffset;
|
|
warn "Can only approximate $day with dayoffset in $file"
|
|
if ($month == 2);
|
|
return "$day, -$weekdayjavanames[$weekday]";
|
|
} else {
|
|
return "-1, $weekdayjavanames[$weekday]";
|
|
}
|
|
} elsif ($day =~ /^([A-Z][a-z][a-z])>=(\d+)$/) {
|
|
my $start = $2 + $dayoffset;
|
|
my $weekday = ( parseWeekday($1) + $dayoffset + 7 ) % 7;
|
|
if (($start % 7) == 1) {
|
|
$start = ($start + 6) / 7;
|
|
return "$start, $weekdayjavanames[$weekday]";
|
|
} else {
|
|
return "$start, -$weekdayjavanames[$weekday]";
|
|
}
|
|
} else {
|
|
die "Unknown day $day";
|
|
}
|
|
}
|
|
|
|
my @monthjavanames =
|
|
( "Calendar.JANUARY",
|
|
"Calendar.FEBRUARY",
|
|
"Calendar.MARCH",
|
|
"Calendar.APRIL",
|
|
"Calendar.MAY",
|
|
"Calendar.JUNE",
|
|
"Calendar.JULY",
|
|
"Calendar.AUGUST",
|
|
"Calendar.SEPTEMBER",
|
|
"Calendar.OCTOBER",
|
|
"Calendar.NOVEMBER",
|
|
"Calendar.DECEMBER" );
|
|
|
|
sub parseRule($$$) {
|
|
my ($rawoffset, $stdoffset, $rule) = @_;
|
|
my $monthnr = parseMonth($rule->[0]);
|
|
my $time = parseTime($rawoffset, $stdoffset, $rule->[2]);
|
|
my $dayoffset = 0;
|
|
while ($time < 0) {
|
|
$time += 24*3600*1000;
|
|
$dayoffset--;
|
|
}
|
|
while ($time > 24*3600*1000) {
|
|
$time -= 24*3600*1000;
|
|
$dayoffset++;
|
|
}
|
|
$day = parseDay($dayoffset, $monthnr, $rule->[1]);
|
|
return [ $monthjavanames[$monthnr-1], $day, $time ];
|
|
}
|
|
|
|
|
|
sub ruleEquals($$) {
|
|
my ($rule1, $rule2) = @_;
|
|
# check month names
|
|
return (($rule1->[0] eq $rule2->[0])
|
|
&& ($rule1->[1] eq $rule2->[1])
|
|
&& ($rule1->[2] == $rule2->[2]));
|
|
}
|
|
|
|
sub findAlias($$) {
|
|
my ($rawoffset, $rule) = @_;
|
|
foreach $tz (@timezones) {
|
|
my ($key, $tzoffset, $tzrule, $aliaslist) = @{$tz};
|
|
next if ($tzoffset != $rawoffset);
|
|
if ($rule eq "-") {
|
|
return $tz if ($tzrule eq "-");
|
|
} elsif ($tzrule ne "-") {
|
|
next if $rule->[2] != $tzrule->[2];
|
|
if (ruleEquals($rule->[0], $tzrule->[0])
|
|
&& ruleEquals($rule->[1], $tzrule->[1])) {
|
|
return $tz;
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
sub makePretty($) {
|
|
my ($offset) = @_;
|
|
if (($offset % 3600) == 0) {
|
|
$offset /= 3600;
|
|
return "$offset * 3600";
|
|
} else {
|
|
return "$offset";
|
|
}
|
|
}
|
|
|
|
sub tzcompare($$) {
|
|
my ($a, $b) = @_;
|
|
if (($a =~ /\//) != ($b =~ /\//)) {
|
|
return ($a =~ /\//) ? 1 : -1;
|
|
} else {
|
|
return $a cmp $b;
|
|
}
|
|
}
|
|
|
|
foreach $file (@TIMEZONEFILES) {
|
|
# print STDERR "$file\n";
|
|
open INPUT, "$TIMEZONEDIR/$file" or die "Can't open $TIMEZONEDIR/$file";
|
|
my $in_time_zone = 0;
|
|
# As Zone can reference even Rule which is defined after the
|
|
# current line, parse the file in 2 passes.
|
|
|
|
# First pass, parse just Rule lines:
|
|
while (<INPUT>) {
|
|
$_ = $1 if /^([^\#]*)\#/;
|
|
next if /^\s*$/;
|
|
my @entries = split;
|
|
# $, = ","; print "'$_' -> [",@entries,"]\n";
|
|
if (!$in_time_zone) {
|
|
if ($entries[0] eq "Rule") {
|
|
# check if rule still applies
|
|
# column 3 is TO entry.
|
|
if ($entries[3] eq "max") {
|
|
my $rulename = $entries[1];
|
|
my $month = $entries[5];
|
|
my $day = $entries[6];
|
|
my $time = $entries[7];
|
|
if ($entries[8] eq "0") {
|
|
# This is the end time rule
|
|
$rules{"$rulename"}[0] = [ $month, $day, $time ];
|
|
} else {
|
|
# This is the start time rule
|
|
$rules{"$rulename"}[1] = [ $month, $day, $time ];
|
|
$rules{"$rulename"}[2] = parseOffset($entries[8]);
|
|
}
|
|
}
|
|
} elsif ($entries[0] eq "Zone") {
|
|
$in_time_zone = 1;
|
|
shift @entries;
|
|
shift @entries;
|
|
} elsif (!(($entries[0] eq "Remove") || ($entries[0] eq "Link"))) {
|
|
die "Unknown command: $_";
|
|
}
|
|
}
|
|
if ($in_time_zone) {
|
|
die "early end of Zone: $_" if ($entries[0] =~ /^[A-Za-z]+/);
|
|
if (@entries <= 3) {
|
|
$in_time_zone = 0;
|
|
}
|
|
}
|
|
}
|
|
close INPUT;
|
|
open INPUT, "$TIMEZONEDIR/$file" or die "Can't open $TIMEZONEDIR/$file";
|
|
$in_time_zone = 0;
|
|
|
|
# Second pass, parse all but Rule lines.
|
|
while (<INPUT>) {
|
|
$_ = $1 if /^([^\#]*)\#/;
|
|
next if /^\s*$/;
|
|
my @entries = split;
|
|
# $, = ","; print "'$_' -> [",@entries,"]\n";
|
|
if (!$in_time_zone) {
|
|
if ($entries[0] eq "Rule") {
|
|
# Do nothing, this was already handled in the
|
|
# first pass.
|
|
} elsif ($entries[0] eq "Zone") {
|
|
$in_time_zone = 1;
|
|
shift @entries;
|
|
$timezonename = shift @entries;
|
|
} elsif ($entries[0] eq "Remove") {
|
|
my $found = 0;
|
|
foreach $tz (@timezones) {
|
|
my @newaliases;
|
|
foreach $tzname (@{$tz->[3]}) {
|
|
if ($tzname eq $entries[1]) {
|
|
$found = 1;
|
|
} else {
|
|
push @newaliases, $tzname;
|
|
}
|
|
}
|
|
if ($found) {
|
|
if ($tz->[0] eq $entries[1]) {
|
|
$tz->[0] = $newaliases[0];
|
|
}
|
|
$tz->[3] = \@newaliases;
|
|
last;
|
|
}
|
|
}
|
|
|
|
die "Unknown link $_" if ! $found;
|
|
} elsif ($entries[0] eq "Link") {
|
|
my $alias = 0;
|
|
foreach $tz (@timezones) {
|
|
foreach $tzname (@{$tz->[3]}) {
|
|
if ($tzname eq $entries[1]) {
|
|
$alias = $tz;
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
|
|
die "Unknown link $_" if ! $alias;
|
|
die "@entries" if $entries[1] =~ /^\d+$/;
|
|
push @{$alias->[3]}, $entries[2];
|
|
} else {
|
|
die "Unknown command: $_";
|
|
}
|
|
}
|
|
if ($in_time_zone) {
|
|
die "early end of Zone: $_" if ($entries[0] =~ /^[A-Za-z]+/);
|
|
if (@entries <= 3) {
|
|
# print "found ZONE $timezonename $entries[0] $entries[1] $entries[2]\n";
|
|
# This is the last line and the only we look at.
|
|
# other lines are for historic time zones.
|
|
my $rawoffset = parseOffset($entries[0]);
|
|
my $rule = $rules{"$entries[1]"} || "-";
|
|
if ($rule ne "-") {
|
|
if (!defined($rule->[2])) {
|
|
$rule = "-";
|
|
} else {
|
|
# now we can parse the time since we know raw offset.
|
|
my $savings = $rule->[2];
|
|
my $endrule = parseRule($rawoffset, $savings,
|
|
$rule->[0]);
|
|
my $startrule = parseRule($rawoffset, 0,
|
|
$rule->[1]);
|
|
$rule = [ $endrule, $startrule, $savings ];
|
|
# print "start",@{$rule->[1]}, "end", @{$rule->[0]},
|
|
# "offset", $rule->[2],"\n";
|
|
}
|
|
}
|
|
my $alias = findAlias($rawoffset, $rule);
|
|
if ($alias) {
|
|
if (($alias->[0] =~ /\//)
|
|
&& ($timezonename !~ /\//)) {
|
|
# alias is of Country/City form, timezonename not
|
|
# make timezonename the real zone name
|
|
$alias->[0] = $timezonename;
|
|
}
|
|
push @{$alias->[3]}, $timezonename;
|
|
} else {
|
|
push @timezones, [ $timezonename, $rawoffset, $rule,
|
|
[ $timezonename ] ];
|
|
}
|
|
$in_time_zone = 0;
|
|
}
|
|
}
|
|
}
|
|
close INPUT;
|
|
}
|
|
|
|
@timezones = sort { if ($a->[1] != $b->[1]) { $a->[1] <=> $b->[1] }
|
|
else { $a->[0] cmp $b->[0] } } @timezones;
|
|
for (@timezones) {
|
|
my ($name, $rawoffset, $rule, $aliaslist) = @{$_};
|
|
my @aliases = sort { tzcompare($a, $b); } @{$aliaslist};
|
|
$name = $aliases[0];
|
|
$rawoffset = makePretty($rawoffset);
|
|
if ($rule eq "-") {
|
|
print <<EOF
|
|
tz = new SimpleTimeZone($rawoffset, \"$name\");
|
|
EOF
|
|
} else {
|
|
my ($endmonth, $endday, $endtime) = @{$rule->[0]};
|
|
my ($startmonth, $startday, $starttime) = @{$rule->[1]};
|
|
$endtime = makePretty($endtime);
|
|
$starttime = makePretty($starttime);
|
|
my $savings = $rule->[2];
|
|
if ($savings == 3600 * 1000) {
|
|
print <<EOF
|
|
tz = new SimpleTimeZone
|
|
($rawoffset, \"$name\",
|
|
$startmonth, $startday, $starttime,
|
|
$endmonth, $endday, $endtime);
|
|
EOF
|
|
} else {
|
|
$savings = makePretty($savings);
|
|
print <<EOF
|
|
tz = new SimpleTimeZone
|
|
($rawoffset, \"$name\",
|
|
$startmonth, $startday, $starttime,
|
|
$endmonth, $endday, $endtime, $savings);
|
|
EOF
|
|
}
|
|
}
|
|
for (@aliases) {
|
|
print <<EOF
|
|
timezones0.put(\"$_\", tz);
|
|
EOF
|
|
}
|
|
}
|
|
|
|
|