The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Calendar::Any::Gregorian;
{
  $Calendar::Any::Gregorian::VERSION = '0.5';
}
use base 'Calendar::Any::Julian';
use POSIX qw/ceil/;
our $default_format = "%D";
my @MONTH_DAYS = Calendar::Any::Julian::MONTH_DAYS();

sub from_absolute {
    use integer;
    my $self = shift;
    my $d0 = shift;
    $self->{absolute} = $d0;
    $d0--;
    my ($n400, $d1, $n100, $d2, $n4, $d3, $n1, $day, $year, $month);
    $n400 = $d0 / 146097;
    $d1   = $d0 % 146097;
    $n100 = $d1 / 36524;
    $d2   = $d1 % 36524;
    $n4   = $d2 / 1461;
    $d3 = $d2 % 1461;
    $n1 = $d3 / 365;
    $day = $d3 % 365 + 1;
    $year = 400*$n400 + 100*$n100 + 4*$n4 + $n1;
    if ( $n100==4 || $n1==4 ) {
        $month = 12;
        $day = 31;
    } else {
        $year++;
        $month = ceil($day/31);
        my $leap = (_is_leap_year($year) ? 1 : 0);
        while ( $day > $MONTH_DAYS[$month]+($month>1?$leap:0) ) {
            $month++;
        }
        $day = $day-$MONTH_DAYS[$month-1]-($month>2?$leap:0);
    }
    $self->{year} = $year;
    $self->{month} = $month;
    $self->{day} = $day;
    return $self;
}

sub absolute_date {
    use integer;
    my $self = shift;
    if ( exists $self->{absolute} ) {
        return $self->{absolute};
    }
    $self->assert_date();
    my $year = $self->year;
    if ( $year > 0 ) {
        my $offset = $year -1;
        $self->{absolute} = $self->day_of_year + 365*$offset + $offset/4 - $offset/100 + $offset/400;
    } else {
        my $offset = abs($year+1);
        $self->{absolute} = -($self->day_of_year + 365*$offset + $offset/4 - $offset/100 + $offset/400 + _day_of_year(12, 31, -1));
    }
    return $self->{absolute};
}

sub is_leap_year {
    return _is_leap_year(shift->year);
}

#==========================================================
# Private functions
#==========================================================
sub _is_leap_year {
    my $year = shift;
    if ( $year < 0 ) {
        $year = abs($year) - 1;
    }
    ($year%4 == 0) && ($year%100>0 || ($year%400 == 0));
}

1;
__END__

=head1 NAME

Calendar::Any::Gregorian - Perl extension for Gregorian Calendar

=head1 VERSION

version 0.5

=head1 SYNOPSIS

   use Calendar::Any::Gregorian;
   my $date = Calendar::Any::Gregorian->new(1, 1, 2006);

=head1 DESCRIPTION

From "FREQUENTLY ASKED QUESTIONS ABOUT CALENDARS"(C<http://www.tondering.dk/claus/calendar.html>)

=over

The Gregorian calendar is the one commonly used today. It was proposed
by Aloysius Lilius, a physician from Naples, and adopted by Pope
Gregory XIII in accordance with instructions from the Council of Trent
(1545-1563) to correct for errors in the older Julian Calendar. It was
decreed by Pope Gregory XIII in a papal bull on 24 February 1582. This
bull is named "Inter Gravissimas" after its first two words.

In the Gregorian calendar, the tropical year is approximated as
365 97/400 days = 365.2425 days. Thus it takes approximately 3300
years for the tropical year to shift one day with respect to the
Gregorian calendar.

The approximation 365 97/400 is achieved by having 97 leap years
every 400 years.

=back

=head1 METHOD

This class is inherited from L<Calendar::Any::Julian>. The method is the
same as Calendar::Any::Julian.

=head1 AUTHOR

Ye Wenbin <wenbinye@gmail.com>

=head1 SEE ALSO

L<Calendar::Any>, L<Calendar::Any::Julian>

=cut