1# $Id: TZ.pm 2902 2007-08-08 19:04:08Z verthezp $
2# $URL: file:///var/lib/svn/intclock/tags/R2_13/lib/Intclock/TZ.pm $
3#___________________________________________________________________
4#
5# Timezone handling routines for intclock
6#___________________________________________________________________
7
8# Copyright (C) 2004-2007 by Peter Verthez
9
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2, or (at your option)
13# any later version.
14
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
23# 02111-1307, USA.
24
25# Written by Peter Verthez, <Peter.Verthez@advalvas.be>.
26# Based on Jens-Ulrik Petersen's hsclock
27
28package Intclock::TZ;
29
30use strict;
31use diagnostics;
32use POSIX;
33use vars qw($zonedir @zonedirs $countryfile $zonefile
34	    %countryhash %zonehash
35	    $cldrterrfile %cldrterrhash
36	    $cldrzonefile %cldrzonehash
37	    %invzonehash);
38
39BEGIN {
40  @zonedirs = ( "/usr/share/zoneinfo",
41		"/usr/lib/zoneinfo",
42		"/usr/local/share/zoneinfo",
43		"/usr/local/lib/zoneinfo",
44		"/etc/zoneinfo",
45		);
46
47  $countryfile = "iso3166.tab";
48  $zonefile    = "zone.tab";
49  $cldrterrfile = "territories.dat";
50  $cldrzonefile = "zones.dat";
51}
52
53sub init {
54  my ($language) = @_;
55  set_timezone();
56  _get_zone_dir();
57  _get_countries();
58  if (defined $language) {
59    _get_cldr();
60  }
61  _get_zones($language);
62}
63
64sub _get_zone_dir {
65  # some heuristics:
66  # - if TZDIR is set and the directory exists, use it
67  if (exists $ENV{"TZDIR"} and -d $ENV{"TZDIR"}) {
68    $zonedir = $ENV{"TZDIR"};
69  }
70  # - if 'tzselect' exists, try to find the default TZDIR there
71  if (!$zonedir or ! -d $zonedir) {
72    my $tzselect=`which tzselect`;
73    chomp $tzselect;
74    if (-r $tzselect) {
75      my $line = `grep "TZDIR=" $tzselect`;
76      if ($line =~ /^: \$\{TZDIR=(.*?)\}/) {
77	$zonedir = $1;
78      }
79    }
80  }
81  # - otherwise, use the fixed list of directories above to search
82  if (!$zonedir or ! -d $zonedir) {
83    foreach my $dir (@zonedirs) {
84      if (-d $dir) {
85	$zonedir = $dir;
86	last;
87      }
88    }
89  }
90  ($zonedir and -d $zonedir) or die "No zoneinfo directory found\n";
91}
92
93sub _get_countries {
94  open INPUT, "/usr/share/misc/iso3166" or die "Can't read country file\n";
95  while (<INPUT>) {
96    next if /^\#/;
97    next if /^\s*$/;
98    my ($code, $name) = split /\t/;
99    chomp $name;
100    $countryhash{$code} = $name;
101  }
102  close INPUT;
103}
104
105sub _get_cldr_sub {
106  my ($file, $hashref, $currlang) = @_;
107  open INPUT, "<:utf8", $file or die "Can't read CLDR data file '$file'\n";
108  my $lang;
109  while (<INPUT>) {
110    my $line = $_;
111    next if /^\#/;
112    next if /^\s*$/;
113    if ($line =~ /^\[LANG=(.*?)\]/) {
114      $lang = $1;
115    }
116    elsif ($lang and (index($currlang, $lang) == 0)
117	   and $line =~ /^\s*(.+?)=(.*)$/) {
118      my ($code, $name) = ($1, $2);
119      $name =~ s/\s*$//;
120      $name =~ s/\&\#x([A-Z0-9]+);/chr(hex($1))/eg;
121      if ($name !~ /^\s*$/) {
122	$hashref->{$code} = $name;
123      }
124    }
125  }
126  close INPUT;
127}
128
129sub _get_cldr {
130  my $curr_lang = $ENV{"LANG"};
131  if ($curr_lang) {
132    my $datadir = Intclock::Config::get_datadir();
133    _get_cldr_sub("$datadir/$cldrterrfile", \%cldrterrhash, $curr_lang);
134    _get_cldr_sub("$datadir/$cldrzonefile", \%cldrzonehash, $curr_lang);
135  }
136}
137
138sub _get_zones {
139  my ($language) = @_;
140  open INPUT, "$zonedir/$zonefile" or die "Can't read zone file\n";
141  while (<INPUT>) {
142    next if /^\#/;
143    next if /^\s*$/;
144    my ($country, $coord, $tz, $comments) = split /\t/;
145    chomp $tz;
146    my $full_tz = $tz;
147    $comments ||= "";
148    chomp $comments;
149    $tz =~ s|^([^/]+)/||;
150    my $continent = $1;
151
152    $coord =~ s/^(.*?)([\-\+]\d+)$/$2/;
153    my $longitude = substr($coord, 0, 6);
154
155    my %data = ("comments"  => $comments,
156		"long"      => $longitude,
157		"continent" => $continent);
158
159    my $full_continent = $continent;
160    if ($continent =~ /^Arctic|Atlantic|Indian|Pacific$/) {
161      $full_continent = "$continent Ocean";
162    }
163
164    my $l_cont;
165    if (defined $language) {
166      eval {
167	$l_cont = $language->maketext($full_continent);
168      };
169      if ($@) {
170	print "WARNING: '$full_continent' not translated\n";
171	$l_cont = $full_continent;
172      }
173    }
174    else {
175      $l_cont = $full_continent;
176    }
177
178    my $l_country = $countryhash{$country};
179    if (defined $language and exists $cldrterrhash{$country}) {
180      $l_country = $cldrterrhash{$country};
181    }
182
183    my $l_tz = $tz;
184    if (defined $language and exists $cldrzonehash{$full_tz}) {
185      $l_tz = $cldrzonehash{$full_tz};
186      $data{"translated_zone"} = $l_tz;
187    }
188    else {
189      $l_tz =~ s|^.*?/||;
190      $l_tz =~ s/_/ /g;
191    }
192    $zonehash{$l_cont}->{$l_country}->{$tz} = \%data;
193    $invzonehash{$full_tz} = [ $l_country, $l_tz,
194			       $zonehash{$l_cont}->{$l_country}];
195  }
196  close INPUT;
197  undef %countryhash;
198  undef %cldrterrhash;
199}
200
201sub zone_info {
202  return (\%zonehash, \%invzonehash);
203}
204
205sub exists {
206  my ($tz) = @_;
207  return (-f "$zonedir/$tz");
208}
209
210sub set_timezone {
211  my ($tz) = @_;
212  $ENV{"TZ"} = $tz if defined $tz;
213  POSIX::tzset();
214}
215
216sub get_timezone_names {
217  my ($tz) = @_;
218  set_timezone($tz) if defined $tz;
219  return POSIX::tzname();
220}
221
222sub in_timezone {
223  my ($time, $timezone) = @_;
224  set_timezone($timezone);
225  my @localtime = localtime($time);
226  return \@localtime;
227}
228
2291;
230