DateConvert
    
    
  
  
  
view release on metacpan or search on metacpan
$VERSION=$VERSION; # to make -w happy.  :)
# methods that every class should have:
# initialize, day, date, date_string
# methods that are recommended if applicable:
# year, month, day, is_leap
$BEGINNING=1721426; # 1 Jan 1 in the Gregorian calendar, although technically, 
                    # the Gregorian calendar didn't exist at the time.
$VERSION_TODAY=2450522; # today in JDN, when I wrote this.
sub new { # straight out of the perlobj manpage:
    my $class = shift;
    my $self = {};
    bless $self, $class;
    $self->initialize(@_);
    return $self;
}
package Date::Convert::Gregorian;
use Carp;
@ISA = qw ( Date::Convert );
$GREG_BEGINNING=1721426; # 1 Jan 1 in the Gregorian calendar, although
                    # technically, the Gregorian calendar didn't exist at
                    # the time.
@MONTHS_SHORT  = qw ( nil Jan Feb Mar Apr May Jun July Aug Sep Oct Nov Dec );
@MONTH_ENDS    = qw ( 0   31  59  90  120 151 181  212 243 273 304 334 365 );
@LEAP_ENDS     = qw ( 0   31  60  91  121 152 182  213 244 274 305 335 366 );
$NORMAL_YEAR    = 365;
$LEAP_YEAR      = $NORMAL_YEAR + 1;
$FOUR_YEARS     = 4 * $NORMAL_YEAR + 1; # one leap year every four years
$CENTURY        = 25 * $FOUR_YEARS - 1; # centuries aren't leap years . . .
$FOUR_CENTURIES = 4 * $CENTURY + 1;     # . . .except every four centuries.
@NORMAL_YEAR = (354,   8, 876); # &part_mult(12,  @MONTH);
@LEAP_YEAR   = (383,  21, 589); # &part_mult(13,  @MONTH);
@CYCLE_YEARS = (6939, 16, 595); # &part_mult(235, @MONTH);
@FIRST_MOLAD = ( 1,  5, 204);
@LEAP_CYCLE  = qw ( 3 6 8 11 14 17 0 );
@MONTHS = ('Nissan', 'Iyyar', 'Sivan', 'Tammuz', 'Av',
	'Elul', 'Tishrei', 'Cheshvan', 'Kislev', 'Teves',
	'Shevat', 'Adar', 'Adar II' );
# In the Hebrew calendar, the year starts in the seventh month, there can
# be a leap month, and there are two months with a variable number of days.
# Rather than calculate do the actual math, let's set up lookup tables based
# on year length.  :)
%MONTH_START=
    ('353' => [177, 207, 236, 266, 295, 325, 1, 31, 60, 89, 118, 148],
     '354' => [178, 208, 237, 267, 296, 326, 1, 31, 60, 90, 119, 149],
     '355' => [179, 209, 238, 268, 297, 327, 1, 31, 61, 91, 120, 150],
     '383' => [207, 237, 266, 296, 325, 355, 1, 31, 60, 89, 118, 148, 178],
     '384' => [208, 238, 267, 297, 326, 356, 1, 31, 60, 90, 119, 149, 179],
}
sub year {
    my $self = shift;
    return $$self{year} if exists $$self{year};
    my $days=$$self{absol};
    my $year=int($days/365)-3*365; # just an initial guess, but a good one.
    warn "Date::Convert::Hebrew isn't reliable before the beginning of\n".
	"\tthe Hebrew calendar" if $days < $HEBREW_BEGINNING;
    $year++ while rosh Date::Convert::Hebrew ($year+1)<=$days;
    $$self{year}=$year;
    $$self{days}=$days-(rosh Date::Convert::Hebrew $year)+1;
    return $year;
}
sub month {
    my $self = shift;
    return $$self{month} if exists $$self{month};
the default way described above for B<new>.)  Note the American spelling of
"initialize": "z", not "s".
=back
=head1 SUBCLASS SPECIFIC NOTES
=head2 Absolute
The "Absolute" calendar is just the number of days from a certain reference
point.  Calendar people should recognize it as the "Julian Day Number" with
one minor modification:  When you convert a Gregorian day n to absolute,
you get the JDN of the Gregorian day from noon on.
Since "absolute" has no notion of years it is an extremely easy calendar
for conversion purposes.  I stole the "absolute" calendar format from
Reingold's emacs calendar mode, for debugging purposes.
The subclass is little more than the base class, and as the lowest common
denominator, doesn't have any special functions.
=head2 Gregorian
The Gregorian calendar is a purely solar calendar, with a month that is
only an approximation of a lunar month.  It is based on the old Julian
(Roman) calendar.  This is the calendar that has been used by most of the
Western world for the last few centuries.  The time of its adoption varies
from country to country.  This B<::Gregorian> allows you to extrapolate
back to 1 A.D., as per the prorgamming tradition, even though the calendar
definitely was not in use then.
In addition to the required methods, B<Gregorian> also has B<year>,
B<month>, B<day>, and B<is_leap> methods.  As mentioned above, B<is_leap>
can also be used statically.
=head2 Hebrew
This is the traditional Jewish calendar.  It's based on the solar year, on
the lunar month, and on a number of additional rules created by Rabbis to
make life tough on people who calculate calendars.  :)  If you actually wade
through the source, you should note that the seventh month really does come
before the first month, that's not a bug.
It comes with the following additional methods: B<year>, B<month>, B<day>,
B<is_leap>, B<rosh>, B<part_add>, and B<part_mult>.  B<rosh> returns the
absolute day corresponding to "Rosh HaShana" (New year) for a given year,
and can also be invoked as a static.  B<part_add> and B<part_mult> are
useful functions for Hebrew calendrical calculations are not for much else;
if you're not familiar with the Hebrew calendar, don't worry about them.
=head2 Islamic
The traditional Muslim calendar, a purely lunar calendar with a year that
is a rough approximation of a solar year.  Currently unimplemented.
=head2 Julian
The old Roman calendar, allegedly named for Julius Caesar.  Purely solar,
with a month that is a rough approximation of the lunar month.  Used
extensively in the Western world up to a few centuries ago, then the West
gradually switched over to the more accurate Gregorian.  Now used only by
the Eastern Orthodox Church, AFAIK.
=head1 ADDING NEW SUBCLASSES
This section describes how to extend B<Date::Convert> to add your favorite
date formats.  If you're not interested, feel free to skip it.  :)
There are only three function you I<have> to write to add a new subclass:
you need B<initialize>, B<date>, and B<date_string>.  Of course, helper
functions would probably help. . .  You do I<not> need to write a B<new> or
B<convert> function, since the base class handles them nicely.
First, a quick conceptual overhaul: the base class uses an "absolute day
format" (basically "Julian day format") borrowed from B<emacs>.  This is
just days numbered absolutely from an extremely long time ago.  It's really
easy to use, particularly if you have emacs and emacs' B<calendar mode>.
Each Date::Convert object is a reference to a hash (as in all OO perl) and
includes a special "absol" value stored under a reserved "absol" key.  When
B<initialize> initializes an object, say a Gregorian date, it stores
whatever data it was given in the object and it also calculates the "absol"
equivalent of the date and stores it, too.  If the user converts to another
date, the object is wiped clean of all data except "absol".  Then when the
B<date> method for the new format is called, it calculates the date in the
new format from the "absol" data.
Now that I've thoroughly confused you, here's a more compartmentalized
Date::Convert 0.15 (pre-alpha)
=head1 AUTHOR
Mordechai T. Abzug <morty@umbc.edu>
=head1 ACKNOWLEDGEMENTS AND FURTHER READING
The basic idea of using astronomical dates as an intermediary between all
calculations comes from Dershowitz and Reingold.  Reingold's code is the
basis of emacs's calendar mode.  Two papers describing their work (which I
used to own, but lost!  Darn.) are:
``Calendrical Calculations'' by Nachum Dershowitz and Edward M. Reingold,
I<Software--Practice and Experience>, Volume 20, Number 9 (September,
1990), pages 899-928.  ``Calendrical Calculations, Part II: Three
Historical Calendars'' by E. M. Reingold, N. Dershowitz, and S. M. Clamen,
I<Software--Practice and Experience>, Volume 23, Number 4 (April, 1993),
pages 383-404.
They were also scheduled to come out with a book on calendrical
- stick some sort of forward_day and back_day methods into the base class
- make things more OO.
- make it impossible to instantiate the base class (ie. make it an ADT).
- move the responsibilty for default constructor from subclasses to base
	class
- make Gregorian inherit from Julian rather than the other way around
- Add more calendars (ie. Islamic)
- Get ahold of my original sources
- make the default constructor initialize using today's date.
- add a whole lot more test cases to the test suites, particularly boundary
	conditions
- add more argument passing options a la CGI.pm, ie. 'year'=>1974 or 
	-year=>1974
- docs: explain about the second-part approximation that bugged Ed. Sabol
- add some form of format specifier to date_string for Gregorian
- Conversion between formats that understand time of dat and ones that don't?
( run in 0.548 second using v1.01-cache-2.11-cpan-5dc5da66d9d )