#!perl -lw # # print out current time in all known time zones, also giving metadata # about each time zone. usage: # # perl alltimezones.pl [unixtime] # # optionally, provide Unix time as a numeric argument to do that time # instead of the current time. # # output: # # first output field: time offset from UTC. # second output field: longitude with a period in front of it # third output field: 0 if "canonical" time zone name, 1 if a "link" time zone. # then a colon. # then time, a colon, time zone name, then metadata about the time zone, including comments in time zone database, country, what it links to if it is a "link" time zone. # # the first three fields are intended to be used as sort keys then # discarded, for example: # # perl alltimezones.pl | sort -k1g -k2g | perl -plwe 's/.*?://' # # Copyright 2022 Ken Takusagawa # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . use POSIX qw(strftime); # the file exists in debian bullseye, not present in buster $zifile=$ENV{TZDATAZIFILE}; $zifile='/usr/share/zoneinfo/tzdata.zi' unless defined$zifile; open FI, $zifile or die; while(){ if(/^Z\s+(\S+)/){ die if $mainzone{$1}; $mainzone{$1}=1; } elsif(/^L\s+(\S+)\s+(\S+)$/){ die if $link{$2}; $link{$2}=$1; } } #for(keys %z){print;} for(keys %link){ die if$mainzone{$_}; #print; } open FI,'/usr/share/zoneinfo/zone1970.tab' or die; while(){ chomp; next if /^#/; @F=split/\t/,$_; $zone=$F[2]; #print "zone=$zone"; unless ($mainzone{$zone}){ # this happens if you use incompatible tzdata.zi and zone1970.tab print STDERR "ignoring zone1970.tab $zone"; next; } die unless ($long1)=($F[1]=~/^[+-]\d+([+-]\d+)$/); die if defined($longitude{$zone}); #print "got $lat1 $long1"; $longitude{$zone}=$long1; @c=split/,/,$F[0]; for(@c){ push @{$countrycodes{$_}},$zone; push @{$zonecountries{$zone}},$_; } if($F[3]){ die if $comment{$zone}; ($zonesuffix)=($zone =~ m,([^/]*)$,); unless($F[3] ne $zonesuffix){ #print "new ignore redundant comment $_"; } else { $F[3] =~ s/$zonesuffix/~/g; # swung dash $comment{$zone}=$F[3]; } } } #print for(keys %countrycodes); #for(keys%z){ # print"$_" unless defined($lat{$_}) and defined($long{$_}); #} if (open FI,'/usr/share/zoneinfo/zone.tab'){ while(){ chomp; next if /^#/; @F=split /\t/,$_; $zone=$F[2]; print STDERR "zone.tab not in zi: $_" unless $mainzone{$zone} or $link{$zone}; die unless ($long1)=($F[1]=~/^[+-]\d+([+-]\d+)$/); die if defined($oldlongitude{$zone}); $oldlongitude{$zone}=$long1; die if defined($oldcountry{$zone}); $oldcountry{$zone}=$F[0]; die if defined($oldcomment{$zone}); if(defined$F[3]){ ($zonesuffix)=($zone =~ m,([^/]*)$,); unless($F[3] ne $zonesuffix){ #print "old ignore redundant comment $_"; } else { if(defined($comment{$zone}) and $comment{$zone} ne $F[3]){ # as of Debian Bullseye (2021a), differences in comments are only in accented letters. # 2021b and beyond (tested 2021e) introduced merged time zones, so a city's time zone could apply to a country it is not, e.g., America/Creston (Canada) -> America/Phoenix (US) . This makes the comments on Phoenix harder to understand. #print "explore $_\nexploreold=($F[3])\nexplorenew=($comment{$zone})" } $F[3] =~ s/$zonesuffix/~/g; # swung dash $oldcomment{$zone}=$F[3]; } } } } # these zones have no location information # CST6CDT # CET # EET # EST # EST5EDT # Etc/GMT # Etc/GMT+1 # Etc/GMT+10 # Etc/GMT+11 # Etc/GMT+12 # Etc/GMT+2 # Etc/GMT+3 # Etc/GMT+4 # Etc/GMT+5 # Etc/GMT+6 # Etc/GMT+7 # Etc/GMT+8 # Etc/GMT+9 # Etc/GMT-1 # Etc/GMT-10 # Etc/GMT-11 # Etc/GMT-12 # Etc/GMT-13 # Etc/GMT-14 # Etc/GMT-2 # Etc/GMT-3 # Etc/GMT-4 # Etc/GMT-5 # Etc/GMT-6 # Etc/GMT-7 # Etc/GMT-8 # Etc/GMT-9 # Etc/UTC # Factory # HST # MET # MST # MST7MDT # PST8PDT # WET open FI,'/usr/share/zoneinfo/iso3166.tab' or die; while(){ next if/^#/; die unless /^(\S\S)\t(.+)/; die if defined($codelookup{$1}); $codelookup{$1}=$2; } $now=$ARGV[0]; $now=time() unless defined$now; sub getlongitude { my$a=shift; my$n; if(defined($longitude{$a})){ $n=$longitude{$a}; } # assigning longitudes to time zones is all around questionable, defended by wanting things to be roughly in the correct location elsif ($a =~ m,^Etc/GMT([+-]\d+)$,) { $n=0-$1; $n*=(360/24); # 15 degrees per time zone $n-=360 while($n>180); # because madness like GMT-14 $n+=360 while($n<-180); # currently unneeded if($n>=0){ $n=sprintf("+%03d",$n); } else { $n=sprintf("%04d",$n); } #print "#n $n"; } elsif ($a eq 'PST8PDT') { $n=$longitude{$link{'US/Pacific'}}; } elsif ($a eq 'MST7MDT') { $n=$longitude{$link{'US/Mountain'}}; } elsif ($a eq 'MST') { # no summer time, assigning location to the most famous place $n=$longitude{$link{'US/Arizona'}}; } elsif ($a eq 'CST6CDT') { $n=$longitude{$link{'US/Central'}}; } elsif ($a eq 'EST5EDT') { $n=$longitude{$link{'US/Eastern'}}; } elsif ($a eq 'HST') { $n=$longitude{$link{'US/Hawaii'}}; # questionable, because Alaska is sometimes Hawaii } elsif ($a eq 'CET' or $a eq 'MET') { # does summer time $n="+015" } elsif ($a eq 'EET') { # does summer time $n="+030" } elsif ($a eq 'EST') { # no summer time $n="-075" }else { # everything else not assigned a longitude get assigned to 0. # this includes incompatibities between tzdata.zi and zone1970.tab $n="+00"; # WET does summer time } &makesortable($n); } sub makesortable { my$n=shift; $n =~ s/^(.)(.*)/${1}0.$2/; # make it something that sort -g can sort # we need some sort of numeric sort because the negative sign essentially changes sort order to reverse $n; } # almost the same as %c, but including AM/PM $mainformat=":%a %d %b %Y %T %P %Z (%z)"; sub calldate { my $z=shift; my $format=shift; #my $y=`env TZ=:$z date --date="\@$now"`; # shelling out is 70 times slower # colon indicates it is a file name in tzdata $ENV{TZ}=":$z"; my@t=localtime($now); strftime($format,$t[0],$t[1],$t[2],$t[3],$t[4],$t[5],$t[6],$t[7],$t[8]); # passing $t[8] is necessary for %z to work } for(keys%mainzone){ if($comm=$comment{$_}){ $comm=" ($comm)"; } else { $comm=""; } $countrystring=""; for$country(@{$zonecountries{$_}}){ $countrystring.="[".$country.":".$codelookup{$country}; if(1==scalar(@{$countrycodes{$country}})){ $countrystring.=":entirety"; } $countrystring.="]"; } $countrystring=" ".$countrystring if $countrystring ne ""; $done=&calldate($_,$mainformat); die if defined$save{$_}; $save{$_}=$done; $offset=&calldate($_,"%z"); print "$offset ",&getlongitude($_)," 0 ",$done," : ",$_,$comm,$countrystring; # extra field "0" for sorting: canonical comes first } for$l(keys%link){ $m=$link{$l}; die unless defined($mainzone{$m}); die unless defined($done=$save{$m}); $longi=$oldlongitude{$l}; $longi=&makesortable($longi) if defined$longi; $longi=&getlongitude($m) unless defined$longi; $countrystring=$oldcountry{$l}; unless(defined$countrystring){ $countrystring=""; } else { $countrystring=" [$countrystring:".$codelookup{$countrystring}."]"; } $comment=$oldcomment{$l}; unless(defined$comment){ $comment=""; }else{ $comment=" ($comment)"; } $offset=&calldate($l,"%z"); print "$offset ",$longi," 1 ",$done," : ",$l,$comment,$countrystring," (->$m)"; } # no longer done sub docountries { for$c(keys %countrycodes){ if(1==scalar(@{$countrycodes{$c}})){ $m=${$countrycodes{$c}}[0]; if(defined($name=$codelookup{$c})){ $name=" ($name)"; } else { # currently all country codes have expansions, so this branch is not taken $name=""; } print "",&getlongitude($m)," 2 ",&calldate($m,$mainformat)," : ",".",lc$c,$name; } else { #print "country has multiple time zones $c"; # AQ AR AU BR CA CD CL CN CY DE EC ES FM GL ID KI KZ MH MN MX MY NZ PF PG PS PT RU TF UA UM US UZ VN } } }