Linuxで使える世界時計
サーバのメンテナンスのために休止したり、データを入れ換えるのは、利用者になるべく不便を掛けないような時刻を選ぶようにしています。最近は海外からのアクセスが結構あるので、海外の時刻が何時なのか気になっていました。ヨーロッパとの時差は何となく実感としてあるのですが、アメリカは今何時なのだろう、と。
それで、良く証券会社の仕事場の映像に出ているような、壁に世界中の都市の時刻を表示する時計が一杯並んでいるようなものが欲しいなと思いました。
検索してみると、意外なことに、こういった世界時計は多くありません。なんだか平気で有料シェアウェアとかになっています。時差があるだけで簡単なものだと思うのですが。このため、時計を横に並べたようなものを自分で作ってみようかとemiclockのソースを取ってきたりしたのですが、残念ながらこれはGPLではないため改造して使うことはできないようです。
zdump
そうこうしている内に、zdumpというプログラムを見つけました。使い方は簡単で、
$ zdump Europe/Pais
Europe/Paris Wed Mar 17 06:54:09 2010 CET
これで現在時刻の表示ができました。色々な場所を一度に表示したければ、
$ zdump Europe/London Australia/Sydney Asia/Tokyo Asia/Singapore Asia/Katmandu Europe/Paris \
Europe/Moscow America/New_York America/Los_Angeles Africa/Kinshasa Pacific/Honolulu
Europe/London Wed Mar 17 05:55:57 2010 GMT
Australia/Sydney Wed Mar 17 16:55:57 2010 EST
Asia/Tokyo Wed Mar 17 14:55:57 2010 JST
Asia/Singapore Wed Mar 17 13:55:57 2010 SGT
Asia/Katmandu Wed Mar 17 11:40:57 2010 NPT
Europe/Paris Wed Mar 17 06:55:57 2010 CET
Europe/Moscow Wed Mar 17 08:55:57 2010 MSK
America/New_York Wed Mar 17 01:55:57 2010 EDT
America/Los_Angeles Tue Mar 16 22:55:57 2010 PDT
Africa/Kinshasa Wed Mar 17 06:55:57 2010 WAT
Pacific/Honolulu Tue Mar 16 19:55:57 2010 HST
というふうにできます。いつも一時に表示したければ、こんな感じで良いでしょう。
$ alias wclock='zdump Europe/London Australia/Sydney Asia/Tokyo Asia/Singapore Asia/Katmandu \
Europe/Paris Europe/Moscow America/New_York America/Los_Angeles \
Africa/Kinshasa Pacific/Honolulu'
worldclock.pl
さて、これで充分なのですが、ディストリビューションやバージョンによってはzdumpが動かないことがあるようです。何かのバージョン不整合があったのか、私のデスクトップマシンでは最初動きませんでした。いつも同じ時刻(グリニッジ標準時か東京時刻)を表示してしまいます。これは結局zoneinfoのインストールが上手くなかったようで、zoneinfoの少し新しいバージョンを入れたら動作するようになりました。元々はzoneinfo(/usr/share/zoneinfo)に.ics(中身はVCalendar)のファイルしかなかったのですが、追加したら拡張子が付かないバイナリファイル(TZif形式)が追加されてました。
それで、動かない間に、元々の.icsファイを読んでzdumpと同じ動作をするworldclock.pl(worldclock.pl.zip)を作成しました。こちらは少しおまけが付いていて、都市だけで、もしくは都市名に完全に一致しなくても、マッチするものを表示します。
$ worldclock.pl Par
03/17 Wed 03:19:47 2010 SRT America/Paramaribo
03/17 Wed 07:19:47 2010 CET Europe/Paris
こちらも、都市名(Aria/City)を複数指定して、上記のwclockのようにalias等を設定することができます。
#!/usr/bin/perl
$Usage = "$0 [-h] [-verbose] [Aria/]city ...\n";
$ZoneinfoDir = "/usr/share/zoneinfo";
@WeekdayName = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
@MonthName = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
$Verbose = 0;
#--- interpret the command-line arguments
while ($ARGV[0]) {
# print STDERR "$ARGV[0]\n";
if ($ARGV[0] =~ /^-D.*/) {
$Debug = 1;
} elsif ($ARGV[0] =~ /^-v.*/) {
$Verbose = 1;
} elsif ($ARGV[0] =~ /^-.*/) {
print STDERR "$Usage";
exit 0;
} else {
last;
}
shift(@ARGV);
}
#--- checking to set the necessary variables.
#--- DebugPrint function
#$Debug = 1;
sub Dprint { if (defined($Debug)) { printf STDERR "DBG @_"; } return 0; }
Dprint "ARGS($#ARGV): @ARGV\n";
#--- Current time of Greenwich.
$gm_time = time() - 9 * 60 * 60;
#--- Check zoneinfo dir
if (!-d $ZoneinfoDir) {
my @zdirs = `locate /zoneinfo/Asia/Tokyo.ics`;
#-- Use the first one without reasons.
if ($#zdirs < 0 || $zdirs[0] !~ m|^(.*)/[^/]+/[^/]+$/|) {
printf STDERR "$0 zoneinfo directory is not found.";
exit(-1);
}
$ZoneinfoDir = $1;
Dprint "ZoneinfoDir ($ZoneinfoDir)\n";
}
#--- Create file list
foreach my $arg (@ARGV) {
my @matchs_complete = ();
my @matchs_partial = ();
my $sout = "";
if ($arg =~ m|/|) {
#-- Asia/Katmandu
if (-f "$ZoneinfoDir/$arg.ics") {
push(@matchs_complete, "$ZoneinfoDir/$arg.ics");
}
@matchs_partial = glob("$ZoneinfoDir/${arg}?*.ics");
} else {
#-- Katmandu
@matchs_complete = glob("$ZoneinfoDir/*/$arg.ics");
@matchs_partial = glob("$ZoneinfoDir/*/${arg}?*.ics");
}
Dprint "Complete: (@matchs_complete)\n";
Dprint "Partial : (@matchs_partial)\n";
foreach my $zfile (@matchs_complete) {
$sout = GetLocalTimeString($zfile);
print "$sout\n";
}
foreach my $zfile (@matchs_partial) {
$sout = GetLocalTimeString($zfile);
print "$sout\n";
}
}
exit 0;
#---
sub GetLocalTimeString {
my ($zf) = @_;
my $srtn;
open(IN, "<$zf") or return "";
my @czf = <IN>;
close(IN);
my ($tzsign, $tzhour, $tzmin, $tzname) = ("", "", "");
foreach (@czf) {
if (m|TZOFFSETTO:([+-])([0-9]{2})([0-9]{2})|i) {
($tzsign, $tzhour, $tzmin) = ($1, $2, $3);
} elsif (m|X-LIC-LOCATION:(.+)|i) {
$tzlocation = $1;
} elsif (m|TZNAME:([a-zA-Z]{3})|i) {
$tzname = $1;
}
}
Dprint "($tzsign, $tzhour, $tzmin, $tzname)\n";
if (length($tzhour) <= 0 || length($tzmin) <= 0 || length($tzsign) <= 0) {
return "";
}
my $lc_time;
if ($tzsign eq "-") {
$lc_time = $gm_time - ($tzhour * 60 + $tzmin) * 60;
} else {
$lc_time = $gm_time + ($tzhour * 60 + $tzmin) * 60;
}
Dprint "($gm_time, $lc_time)\n";
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($lc_time);
if ($isdst) {
#-- The aria supports summer time.
($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime($lc_time + 60 * 60);
$tzlocation .= " (ST)";
}
$srtn = Fmt($mon + 1, 2) . "/" . Fmt($mday, 2) . " $WeekdayName[$wday] " . Fmt($hour, 2) . ":"
. Fmt($min, 2) . ":" . Fmt($sec, 2) . " " . ($year + 1900) . " $tzname " . "$tzlocation";
return $srtn;
}
#---
sub Fmt {
my ($nbr, $kta) = @_;
return sprintf("%0${kta}d", $nbr);
}
因みにzoneinfoがあるディレクトリは/usr/share/zoneinfoだと思いますが、これがなかった場合、あるいはあってもVCalendar形式ファイルがなくTZifのバイナリファイルだけの場合には、システム中からlocateで捜します。evolutionの下にあったりするようです。