package Time::ZoneInfo;
use strict;
use vars qw/$VERSION/;
$VERSION = '0.3';
use IO::File;

$Time::ZoneInfo::ERROR = "";

sub new {
	my ($class, @params) = @_;
	my $this = bless {}, ref($class) || $class;
	return undef unless ($this->init(@params));
	return $this;
}

sub init {
	my ($this, %params) = @_;
	# Read in and process the /usr/share/zoneinfo/zone.tab file
	# Create a REGIONS array (only region part) and ZONES (full part)
	my (@zones, %regions, %localisation, %countries, $zone);
	
	my $filename = $params{zonetab} || "/usr/share/zoneinfo/zone.tab";
	my $fh = new IO::File;
	$fh->open($filename);
	if (!defined($fh)) {
		$Time::ZoneInfo::ERROR = "Can't find file ($filename) - $!";
		return 0;
	}
	
	while(my $line = <$fh>) {
		chomp $line;
		next if ($line =~ /^\s*#/);
		next if ($line =~ /^\s*$/);
		$line =~ s/#.*$//;
		if ($line =~ /^([^#]\S+)\s*\t\s*(\S+)\s*\t\s*(\S+)\s*(\t|$)/) {
			$zone = $3;
			$countries{$zone} = $1;
			$localisation{$zone} = $2;
			if ($zone =~ m|^(.+?)/(.+)$|) {
				$regions{$1}++;
			}
			push @zones, $zone;
		}
	}
	$this->{REGIONS} = [keys %regions];
	$this->{ZONES} = \@zones;
	$this->{COUNTRIES} = \%countries;
	$this->{LOCALISATION} = \%localisation;
	return 1;
}

sub regions {
	my ($this) = @_;
	return wantarray ? @{$this->{REGIONS}} : $this->{REGIONS};
}

sub zones {
	my ($this, $region) = @_;
	return $this->_zones if (!defined($region));

	my @ret = ();
	foreach my $zone ($this->_zones) {
		if ($zone =~ m|^$region/|) {
			push @ret, $zone;
		}
	}
	return wantarray ? @ret : \@ret;
}

sub _zones {
	my ($this) = @_;
	return wantarray ? @{$this->{ZONES}} : $this->{ZONES};
}

sub country {
	my ($this, $zone) = @_;
	$this->{COUNTRIES}{$zone};
}

sub current_zone {
	my ($this) = @_;
	if (-e '/etc/timezone') {
		open(my $F, '<', '/etc/timezone');
		my ($zone) = <$F> =~ /(\S+)/ or return;
		$zone;
	} elsif (-e '/etc/sysconfig/clock') {
		open(my $F, '<', '/etc/sysconfig/clock');
		my ($zone) = join('', <$F>) =~ /^ZONE=(.*)/m or return;
		$zone =~ s/^"(.*)"$/$1/ || $zone =~ s/^'(.*)'$/$1/;
		$zone;
	} else {
		undef;
	}
}

sub _raw_latitude_longitude {
	my ($this, $zone) = @_;
	my $loc = $this->{LOCALISATION}{$zone} or return;
	$loc =~ /^([+-]\d\d)(\d\d)(\d\d)?([+-]\d\d\d)(\d\d)(\d\d)?/ or return;

	[ $1, $2, $3 || '00' ], [ $4, $5, $6 || '00' ];
}

sub _deg_min_sec_to_decimal {
	my ($coord) = @_;
	my ($deg, $min, $sec) = @$coord;
	$deg + $min / 60 + $sec / 3600;
}

sub _deg_min_sec_to_sexagesimal {
	my ($coord, $pos, $neg) = @_;
	my ($deg, $min, $sec) = @$coord;
	sprintf("%02d_%02d'", abs($deg), $min) . ($sec != 0 ? qq($sec") : '') . ($deg >= 0 ? $pos : $neg);
}

sub latitude_longitude_decimal {
	my ($this, $zone) = @_;
	map { _deg_min_sec_to_decimal($_) } $this->_raw_latitude_longitude($zone);
}

sub latitude_longitude_sexagesimal {
	my ($this, $zone) = @_;
	my ($lat, $lon) = $this->_raw_latitude_longitude($zone) or return;
	_deg_min_sec_to_sexagesimal($lat, 'N', 'S'), 
	  _deg_min_sec_to_sexagesimal($lon, 'E', 'W'); 
}

sub latitude_decimal  { (latitude_longitude_decimal(@_))[0] }
sub longitude_decimal { (latitude_longitude_decimal(@_))[1] }

sub latitude_sexagesimal  { (latitude_longitude_sexagesimal(@_))[0] }
sub longitude_sexagesimal { (latitude_longitude_sexagesimal(@_))[1] }

1;

__END__

=head1 NAME

Time::ZoneInfo - Perl extension for returning a list of Time Zones...

=head1 SYNOPSIS

	use Time::ZoneInfo;
	my $zones = Time::ZoneInfo->new();
	foreach my $zone ($zones->zones) {
		# print $zone->zone . ' - ' . $zone->offset . "\n";
		print $zone . "\n";
	}

	if (my $zone = Time::ZoneInfo->current_zone) {
		my ($latitude, $longitude) = $zones->latitude_longitude_sexagesimal($zone);
		print "$zone: ", $zones->country($zone), "  $latitude $longitude\n";
	}

=head1 DESCRIPTION

An OO interface to accessing a list of timezones. This is useful if you
want to provide an interface for your user to choose one of the available
time zones.

Version 0.3.1 also gives the geolocalisation for a time zone.

=head1 METHODS

=head2 new

You can specify the zonetab file by passing in zonetab => 'file/location'

=head2 regions

Return an array (or array ref) to the list of regions

=head2 zones ([region])

Return zones (optionally just for one region)

=head2 country (zone)

Return the country associated with the given zone

=head2 current_zone

Return the current zone configured

=head2 latitude_decimal(zone)

Return the latitude for the given zone using decimal notation.

=head2 latitude_sexagesimal(zone)

Return the latitude for the given zone using sexagesimal notation 
(eg: 51_30'30"N)

=head2 longitude_decimal(zone)

Return the longitude for the given zone using decimal notation.

=head2 longitude_sexagesimal(zone)

Return the longitude for the given zone using sexagesimal notation 
(eg: 00_07'31"E)

=head2 latitude_longitude_decimal(zone)

Return a list of two elements: the latitude and longitude for the given zone using decimal notation.

=head2 latitude_longitude_sexagesimal(zone)

Return a list of two elements: the latitude and longitude for the given zone using sexagesimal notation
(eg: 51_30'30"N and 00_07'31"E)

=

=head1 ERRORS

You can read $Time::ZoneInfo::ERROR for an error message at any time.

=head1 CONTRIBUTIONS

Thanks to Richard Carver <cpan.org-rnc@thecarverzone.com> for finding
issues processing comments.

Pascal "Pixel" Rigaux added geolocalisation

=head1 AUTHOR

Scot Penrose, E<lt>scott@cpan.orgE<gt>

=head1 SEE ALSO

=cut

