| #!/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 |
| } |
| } |
| |
| |