package Date::ISO;

use strict;
use vars qw( $VERSION @ISA @EXPORT );
require Exporter;
@ISA = qw(Exporter AutoLoader);
@EXPORT = qw( localiso iso inverseiso);
$VERSION = '1.02';

=head1 NAME

Date::ISO - Perl extension for converting dates between ISO and
Gregorian formats.

=head1 SYNOPSIS

  use Date::ISO;
  ($yearnumber, $weeknumber, $weekday) = iso($year, $month, $day);
  ($yearnumber, $weeknumber, $weekday) = localiso(time);

=head1 DESCRIPTION

Convert dates between ISO and Gregorian formats.

=head2 iso

	($year, $week, $day) = iso($year, $month, $day);

Returns the ISO year, week and day, when given the year, month, and day,
as returned by localtime. (That is, months are zero-based, years are
-1900.)

=cut

sub iso	{
	my ($year, $month, $day) = @_;
	my ($doy, $yy, $c, $g, $janone,	$h,	$weekday,
		$yearnumber, $weeknumber, $i, $j,
		$leap, $lastleap,
		);

	my %doy = ( 0 => 0, 1 => 31, 2 => 59, 3 => 90,
		4 => 120, 5 => 151, 6 => 181, 7 => 212, 8 => 243,
		9 => 273, 10 => 304, 11 => 334 );
	$doy = $doy{$month} + $day;
	$leap = isleap($year);
	$lastleap = isleap($year - 1);
	$year += 1900;
	$doy++ if ($leap && $month > 1);
	$yy = ($year - 1) % 100;
	$c = ($year - 1) - $yy;
	$g = $yy + int($yy/4);
	$janone = 1 + (((( (int($c/100)) % 4) * 5) + $g) % 7);
	$h = $doy + ($janone - 1);
	$weekday = 1 + (($h - 1) % 7);
	$i = $leap ? 366 : 365;
	# Is it really the last week of last year?
	if ( ($doy <= (8 - $janone)) && ($janone > 4) )	{
		$yearnumber = $year - 1;
		if ($janone == 5 || ($janone == 6 && $lastleap ))	{
			$weeknumber = 53;
		} else {
			$weeknumber = 52;
		}
	}
	# Is it really the first week of next year?
	elsif ( ($i - $doy) < (4 - $weekday) )	{
		$yearnumber = $year + 1;
		$weeknumber = 1;
	} else {
		$yearnumber = $year;
		$j = $doy +	(7 - $weekday) + ($janone - 1);
		$weeknumber = int ($j / 7);
		if ($janone > 4)	{
			$weeknumber--;
		}
	}
	return ($yearnumber, $weeknumber, $weekday);
}
		
=head2 inverseiso

	($year, $month, $day) = inverse_iso($year, $week, $day);

Given an ISO year, week, and day, returns year, month, and day, as
localtime would give them to you.

=cut

sub inverseiso	{
	my ($yearnumber, $weeknumber, $weekday) = @_;
	my ($yy, $c, $g, $janone, $eoy,
		$year, $month, $day, $doy,
		);
	$yy = ($year - 1) % 100;
	$c = ($year - 1) - $yy;
	$g = $yy + int($yy / 4);
	$janone = 1 + (((( int($c / 100) % 4) * 5) + $g) % 7);
	if (($weeknumber == 1) && ($janone < 5) && ($weekday < $janone))	{
		$year = $yearnumber - 1;
		$month = 12;
		$day = 32 - ($janone - $weekday);
	} else {
		$year = $yearnumber;
	}
	$doy = ($weeknumber - 1) * 7;
	if ($janone < 5)	{
		$doy += $weekday - ($janone - 1);
	} else {
		$doy += $weekday + (8 - $janone);
	}
	if (isleap($yearnumber - 1900))	{
		$eoy = 366;
	} else {
		$eoy = 365;
	}
	if ($doy > $eoy)	{
		$year = $yearnumber + 1;
		$month = 1;
		$day = $doy - $eoy;
	} else {
		$year = $yearnumber;
	}
	if ($year == $yearnumber)	{
		my @month = (31, 28, 31, 30, 31,30, 31, 31, 30, 31, 30, 31);
		$month[1] = 29 if (isleap($year - 1900));
		my $h = 0;
		$month=0;
		for my $days (@month)	{
			last if $h > $doy;
			$h += $days;
			$month++;
		}
		$month--;
		$day = $doy - ($h - $month[$month]);
	}
	return ($year, $month, $day);
}

=head2 localiso

	($year, $week, $day) = localiso(time);

Given a time value (epoch time) returns the ISO year, week, and day.

=cut

sub localiso	{
	my ($datetime) = @_;
	$datetime ||= time;
	my ($year, $month, $day) = (localtime($datetime))[5,4,3];
	return iso($year, $month, $day);
}

sub isleap	{
	my ($year) = @_;
	$year += 1900;
	return 1 if $year%4==0;
	return 1 if ($year%4==0 && !$year%100);
	return 0;
}

1;

__END__

=head1 AUTHOR

Rich Bowen <rbowen@rcbowen.com>

=head1 Additional comments

For more information about this calendar, please see:

http://personal.ecu.edu/mccartyr/ISOwdALG.txt

http://personal.ecu.edu/mccartyr/isowdcal.html

=cut
