This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
5871fac4f8f47446b3f41e99c35bfd92365f87a5
[perl5.git] / ext / Time / Piece / Piece.pm
1 # $Id: Piece.pm 72 2007-11-19 01:26:10Z matt $
2
3 package Time::Piece;
4
5 use strict;
6
7 require Exporter;
8 require DynaLoader;
9 use Time::Seconds;
10 use Carp;
11 use Time::Local;
12 use UNIVERSAL qw(isa);
13
14 our @ISA = qw(Exporter DynaLoader);
15
16 our @EXPORT = qw(
17     localtime
18     gmtime
19 );
20
21 our %EXPORT_TAGS = (
22     ':override' => 'internal',
23     );
24
25 our $VERSION = '1.12';
26
27 bootstrap Time::Piece $VERSION;
28
29 my $DATE_SEP = '-';
30 my $TIME_SEP = ':';
31 my @MON_LIST = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
32 my @FULLMON_LIST = qw(January February March April May June July
33                       August September October November December);
34 my @DAY_LIST = qw(Sun Mon Tue Wed Thu Fri Sat);
35 my @FULLDAY_LIST = qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday);
36
37 use constant 'c_sec' => 0;
38 use constant 'c_min' => 1;
39 use constant 'c_hour' => 2;
40 use constant 'c_mday' => 3;
41 use constant 'c_mon' => 4;
42 use constant 'c_year' => 5;
43 use constant 'c_wday' => 6;
44 use constant 'c_yday' => 7;
45 use constant 'c_isdst' => 8;
46 use constant 'c_epoch' => 9;
47 use constant 'c_islocal' => 10;
48
49 sub localtime {
50     unshift @_, __PACKAGE__ unless eval { $_[0]->isa('Time::Piece') };
51     my $class = shift;
52     my $time  = shift;
53     $time = time if (!defined $time);
54     $class->_mktime($time, 1);
55 }
56
57 sub gmtime {
58     unshift @_, __PACKAGE__ unless eval { $_[0]->isa('Time::Piece') };
59     my $class = shift;
60     my $time  = shift;
61     $time = time if (!defined $time);
62     $class->_mktime($time, 0);
63 }
64
65 sub new {
66     my $class = shift;
67     my ($time) = @_;
68     
69     my $self;
70     
71     if (defined($time)) {
72         $self = $class->localtime($time);
73     }
74     elsif (ref($class) && $class->isa(__PACKAGE__)) {
75         $self = $class->_mktime($class->epoch, $class->[c_islocal]);
76     }
77     else {
78         $self = $class->localtime();
79     }
80     
81     return bless $self, $class;
82 }
83
84 sub parse {
85     my $proto = shift;
86     my $class = ref($proto) || $proto;
87     my @components;
88     if (@_ > 1) {
89         @components = @_;
90     }
91     else {
92         @components = shift =~ /(\d+)$DATE_SEP(\d+)$DATE_SEP(\d+)(?:(?:T|\s+)(\d+)$TIME_SEP(\d+)(?:$TIME_SEP(\d+)))/;
93         @components = reverse(@components[0..5]);
94     }
95     return $class->new(_strftime("%s", @components));
96 }
97
98 sub _mktime {
99     my ($class, $time, $islocal) = @_;
100     $class = eval { (ref $class) && (ref $class)->isa('Time::Piece') }
101            ? ref $class
102            : $class;
103     if (ref($time)) {
104         $time->[c_epoch] = undef;
105         return wantarray ? @$time : bless [@$time, $islocal], $class;
106     }
107     _tzset();
108     my @time = $islocal ?
109             CORE::localtime($time)
110                 :
111             CORE::gmtime($time);
112     wantarray ? @time : bless [@time, $time, $islocal], $class;
113 }
114
115 my %_special_exports = (
116   localtime => sub { my $c = $_[0]; sub { $c->localtime(@_) } },
117   gmtime    => sub { my $c = $_[0]; sub { $c->gmtime(@_)    } },
118 );
119
120 sub export {
121   my ($class, $to, @methods) = @_;
122   for my $method (@methods) {
123     if (exists $_special_exports{$method}) {
124       no strict 'refs';
125       no warnings 'redefine';
126       *{$to . "::$method"} = $_special_exports{$method}->($class);
127     } else {
128       $class->SUPER::export($to, $method);
129     }
130   }
131 }
132
133 sub import {
134     # replace CORE::GLOBAL localtime and gmtime if required
135     my $class = shift;
136     my %params;
137     map($params{$_}++,@_,@EXPORT);
138     if (delete $params{':override'}) {
139         $class->export('CORE::GLOBAL', keys %params);
140     }
141     else {
142         $class->export((caller)[0], keys %params);
143     }
144 }
145
146 ## Methods ##
147
148 sub sec {
149     my $time = shift;
150     $time->[c_sec];
151 }
152
153 *second = \&sec;
154
155 sub min {
156     my $time = shift;
157     $time->[c_min];
158 }
159
160 *minute = \&min;
161
162 sub hour {
163     my $time = shift;
164     $time->[c_hour];
165 }
166
167 sub mday {
168     my $time = shift;
169     $time->[c_mday];
170 }
171
172 *day_of_month = \&mday;
173
174 sub mon {
175     my $time = shift;
176     $time->[c_mon] + 1;
177 }
178
179 sub _mon {
180     my $time = shift;
181     $time->[c_mon];
182 }
183
184 sub month {
185     my $time = shift;
186     if (@_) {
187         return $_[$time->[c_mon]];
188     }
189     elsif (@MON_LIST) {
190         return $MON_LIST[$time->[c_mon]];
191     }
192     else {
193         return $time->strftime('%b');
194     }
195 }
196
197 *monname = \&month;
198
199 sub fullmonth {
200     my $time = shift;
201     if (@_) {
202         return $_[$time->[c_mon]];
203     }
204     elsif (@FULLMON_LIST) {
205         return $FULLMON_LIST[$time->[c_mon]];
206     }
207     else {
208         return $time->strftime('%B');
209     }
210 }
211
212 sub year {
213     my $time = shift;
214     $time->[c_year] + 1900;
215 }
216
217 sub _year {
218     my $time = shift;
219     $time->[c_year];
220 }
221
222 sub yy {
223     my $time = shift;
224     my $res = $time->[c_year] % 100;
225     return $res > 9 ? $res : "0$res";
226 }
227
228 sub wday {
229     my $time = shift;
230     $time->[c_wday] + 1;
231 }
232
233 sub _wday {
234     my $time = shift;
235     $time->[c_wday];
236 }
237
238 *day_of_week = \&_wday;
239
240 sub wdayname {
241     my $time = shift;
242     if (@_) {
243         return $_[$time->[c_wday]];
244     }
245     elsif (@DAY_LIST) {
246         return $DAY_LIST[$time->[c_wday]];
247     }
248     else {
249         return $time->strftime('%a');
250     }
251 }
252
253 *day = \&wdayname;
254
255 sub fullday {
256     my $time = shift;
257     if (@_) {
258         return $_[$time->[c_wday]];
259     }
260     elsif (@FULLDAY_LIST) {
261         return $FULLDAY_LIST[$time->[c_wday]];
262     }
263     else {
264         return $time->strftime('%A');
265     }
266 }
267
268 sub yday {
269     my $time = shift;
270     $time->[c_yday];
271 }
272
273 *day_of_year = \&yday;
274
275 sub isdst {
276     my $time = shift;
277     $time->[c_isdst];
278 }
279
280 *daylight_savings = \&isdst;
281
282 # Thanks to Tony Olekshy <olekshy@cs.ualberta.ca> for this algorithm
283 sub tzoffset {
284     my $time = shift;
285     
286     return Time::Seconds->new(0) unless $time->[c_islocal];
287
288     my $epoch = $time->epoch;
289
290     my $j = sub {
291
292         my ($s,$n,$h,$d,$m,$y) = @_; $m += 1; $y += 1900;
293
294         $time->_jd($y, $m, $d, $h, $n, $s);
295
296     };
297
298     # Compute floating offset in hours.
299     #
300     my $delta = 24 * (&$j(CORE::localtime $epoch) - &$j(CORE::gmtime $epoch));
301
302     # Return value in seconds rounded to nearest minute.
303     return Time::Seconds->new( int($delta * 60 + ($delta >= 0 ? 0.5 : -0.5)) * 60 );
304 }
305
306 sub epoch {
307     my $time = shift;
308     if (defined($time->[c_epoch])) {
309         return $time->[c_epoch];
310     }
311     else {
312         my $epoch = $time->[c_islocal] ?
313           timelocal(@{$time}[c_sec .. c_mon], $time->[c_year]+1900)
314           :
315           timegm(@{$time}[c_sec .. c_mon], $time->[c_year]+1900);
316         $time->[c_epoch] = $epoch;
317         return $epoch;
318     }
319 }
320
321 sub hms {
322     my $time = shift;
323     my $sep = @_ ? shift(@_) : $TIME_SEP;
324     sprintf("%02d$sep%02d$sep%02d", $time->[c_hour], $time->[c_min], $time->[c_sec]);
325 }
326
327 *time = \&hms;
328
329 sub ymd {
330     my $time = shift;
331     my $sep = @_ ? shift(@_) : $DATE_SEP;
332     sprintf("%d$sep%02d$sep%02d", $time->year, $time->mon, $time->[c_mday]);
333 }
334
335 *date = \&ymd;
336
337 sub mdy {
338     my $time = shift;
339     my $sep = @_ ? shift(@_) : $DATE_SEP;
340     sprintf("%02d$sep%02d$sep%d", $time->mon, $time->[c_mday], $time->year);
341 }
342
343 sub dmy {
344     my $time = shift;
345     my $sep = @_ ? shift(@_) : $DATE_SEP;
346     sprintf("%02d$sep%02d$sep%d", $time->[c_mday], $time->mon, $time->year);
347 }
348
349 sub datetime {
350     my $time = shift;
351     my %seps = (date => $DATE_SEP, T => 'T', time => $TIME_SEP, @_);
352     return join($seps{T}, $time->date($seps{date}), $time->time($seps{time}));
353 }
354
355
356
357 # Julian Day is always calculated for UT regardless
358 # of local time
359 sub julian_day {
360     my $time = shift;
361     # Correct for localtime
362     $time = $time->gmtime( $time->epoch ) if $time->[c_islocal];
363
364     # Calculate the Julian day itself
365     my $jd = $time->_jd( $time->year, $time->mon, $time->mday,
366                         $time->hour, $time->min, $time->sec);
367
368     return $jd;
369 }
370
371 # MJD is defined as JD - 2400000.5 days
372 sub mjd {
373     return shift->julian_day - 2_400_000.5;
374 }
375
376 # Internal calculation of Julian date. Needed here so that
377 # both tzoffset and mjd/jd methods can share the code
378 # Algorithm from Hatcher 1984 (QJRAS 25, 53-55), and
379 #  Hughes et al, 1989, MNRAS, 238, 15
380 # See: http://adsabs.harvard.edu/cgi-bin/nph-bib_query?bibcode=1989MNRAS.238.1529H&db_key=AST
381 # for more details
382
383 sub _jd {
384     my $self = shift;
385     my ($y, $m, $d, $h, $n, $s) = @_;
386
387     # Adjust input parameters according to the month
388     $y = ( $m > 2 ? $y : $y - 1);
389     $m = ( $m > 2 ? $m - 3 : $m + 9);
390
391     # Calculate the Julian Date (assuming Julian calendar)
392     my $J = int( 365.25 *( $y + 4712) )
393       + int( (30.6 * $m) + 0.5)
394         + 59
395           + $d
396             - 0.5;
397
398     # Calculate the Gregorian Correction (since we have Gregorian dates)
399     my $G = 38 - int( 0.75 * int(49+($y/100)));
400
401     # Calculate the actual Julian Date
402     my $JD = $J + $G;
403
404     # Modify to include hours/mins/secs in floating portion.
405     return $JD + ($h + ($n + $s / 60) / 60) / 24;
406 }
407
408 sub week {
409     my $self = shift;
410
411     my $J  = $self->julian_day;
412     # Julian day is independent of time zone so add on tzoffset
413     # if we are using local time here since we want the week day
414     # to reflect the local time rather than UTC
415     $J += ($self->tzoffset/(24*3600)) if $self->[c_islocal];
416
417     # Now that we have the Julian day including fractions
418     # convert it to an integer Julian Day Number using nearest
419     # int (since the day changes at midday we oconvert all Julian
420     # dates to following midnight).
421     $J = int($J+0.5);
422
423     use integer;
424     my $d4 = ((($J + 31741 - ($J % 7)) % 146097) % 36524) % 1461;
425     my $L  = $d4 / 1460;
426     my $d1 = (($d4 - $L) % 365) + $L;
427     return $d1 / 7 + 1;
428 }
429
430 sub _is_leap_year {
431     my $year = shift;
432     return (($year %4 == 0) && !($year % 100 == 0)) || ($year % 400 == 0)
433                ? 1 : 0;
434 }
435
436 sub is_leap_year {
437     my $time = shift;
438     my $year = $time->year;
439     return _is_leap_year($year);
440 }
441
442 my @MON_LAST = qw(31 28 31 30 31 30 31 31 30 31 30 31);
443
444 sub month_last_day {
445     my $time = shift;
446     my $year = $time->year;
447     my $_mon = $time->_mon;
448     return $MON_LAST[$_mon] + ($_mon == 1 ? _is_leap_year($year) : 0);
449 }
450
451 sub strftime {
452     my $time = shift;
453     my $tzname = $time->[c_islocal] ? '%Z' : 'UTC';
454     my $format = @_ ? shift(@_) : "%a, %d %b %Y %H:%M:%S $tzname";
455     if (!defined $time->[c_wday]) {
456         if ($time->[c_islocal]) {
457             return _strftime($format, CORE::localtime($time->epoch));
458         }
459         else {
460             return _strftime($format, CORE::gmtime($time->epoch));
461         }
462     }
463     return _strftime($format, (@$time)[c_sec..c_isdst]);
464 }
465
466 sub strptime {
467     my $time = shift;
468     my $string = shift;
469     my $format = @_ ? shift(@_) : "%a, %d %b %Y %H:%M:%S %Z";
470     my @vals = _strptime($string, $format);
471 #    warn(sprintf("got vals: %d-%d-%d %d:%d:%d\n", reverse(@vals)));
472     return scalar $time->_mktime(\@vals, (ref($time) ? $time->[c_islocal] : 0));
473 }
474
475 sub day_list {
476     shift if ref($_[0]) && $_[0]->isa(__PACKAGE__); # strip first if called as a method
477     my @old = @DAY_LIST;
478     if (@_) {
479         @DAY_LIST = @_;
480     }
481     return @old;
482 }
483
484 sub mon_list {
485     shift if ref($_[0]) && $_[0]->isa(__PACKAGE__); # strip first if called as a method
486     my @old = @MON_LIST;
487     if (@_) {
488         @MON_LIST = @_;
489     }
490     return @old;
491 }
492
493 sub time_separator {
494     shift if ref($_[0]) && $_[0]->isa(__PACKAGE__);
495     my $old = $TIME_SEP;
496     if (@_) {
497         $TIME_SEP = $_[0];
498     }
499     return $old;
500 }
501
502 sub date_separator {
503     shift if ref($_[0]) && $_[0]->isa(__PACKAGE__);
504     my $old = $DATE_SEP;
505     if (@_) {
506         $DATE_SEP = $_[0];
507     }
508     return $old;
509 }
510
511 use overload '""' => \&cdate,
512              'cmp' => \&str_compare,
513              'fallback' => undef;
514
515 sub cdate {
516     my $time = shift;
517     if ($time->[c_islocal]) {
518         return scalar(CORE::localtime($time->epoch));
519     }
520     else {
521         return scalar(CORE::gmtime($time->epoch));
522     }
523 }
524
525 sub str_compare {
526     my ($lhs, $rhs, $reverse) = @_;
527     if (UNIVERSAL::isa($rhs, 'Time::Piece')) {
528         $rhs = "$rhs";
529     }
530     return $reverse ? $rhs cmp $lhs->cdate : $lhs->cdate cmp $rhs;
531 }
532
533 use overload
534         '-' => \&subtract,
535         '+' => \&add;
536
537 sub subtract {
538     my $time = shift;
539     my $rhs = shift;
540     if (UNIVERSAL::isa($rhs, 'Time::Seconds')) {
541         $rhs = $rhs->seconds;
542     }
543
544     if (shift)
545     {
546         # SWAPED is set (so someone tried an expression like NOTDATE - DATE).
547         # Imitate Perl's standard behavior and return the result as if the
548         # string $time resolves to was subtracted from NOTDATE.  This way,
549         # classes which override this one and which have a stringify function
550         # that resolves to something that looks more like a number don't need
551         # to override this function.
552         return $rhs - "$time";
553     }
554     
555     if (UNIVERSAL::isa($rhs, 'Time::Piece')) {
556         return Time::Seconds->new($time->epoch - $rhs->epoch);
557     }
558     else {
559         # rhs is seconds.
560         return $time->_mktime(($time->epoch - $rhs), $time->[c_islocal]);
561     }
562 }
563
564 sub add {
565     my $time = shift;
566     my $rhs = shift;
567     if (UNIVERSAL::isa($rhs, 'Time::Seconds')) {
568         $rhs = $rhs->seconds;
569     }
570     croak "Invalid rhs of addition: $rhs" if ref($rhs);
571
572     return $time->_mktime(($time->epoch + $rhs), $time->[c_islocal]);
573 }
574
575 use overload
576         '<=>' => \&compare;
577
578 sub get_epochs {
579     my ($lhs, $rhs, $reverse) = @_;
580     if (!UNIVERSAL::isa($rhs, 'Time::Piece')) {
581         $rhs = $lhs->new($rhs);
582     }
583     if ($reverse) {
584         return $rhs->epoch, $lhs->epoch;
585     }
586     return $lhs->epoch, $rhs->epoch;
587 }
588
589 sub compare {
590     my ($lhs, $rhs) = get_epochs(@_);
591     return $lhs <=> $rhs;
592 }
593
594 1;
595 __END__
596
597 =head1 NAME
598
599 Time::Piece - Object Oriented time objects
600
601 =head1 SYNOPSIS
602
603     use Time::Piece;
604     
605     my $t = localtime;
606     print "Time is $t\n";
607     print "Year is ", $t->year, "\n";
608
609 =head1 DESCRIPTION
610
611 This module replaces the standard localtime and gmtime functions with
612 implementations that return objects. It does so in a backwards
613 compatible manner, so that using localtime/gmtime in the way documented
614 in perlfunc will still return what you expect.
615
616 The module actually implements most of an interface described by
617 Larry Wall on the perl5-porters mailing list here:
618 http://www.xray.mpe.mpg.de/mailing-lists/perl5-porters/2000-01/msg00241.html
619
620 =head1 USAGE
621
622 After importing this module, when you use localtime or gmtime in a scalar
623 context, rather than getting an ordinary scalar string representing the
624 date and time, you get a Time::Piece object, whose stringification happens
625 to produce the same effect as the localtime and gmtime functions. There is 
626 also a new() constructor provided, which is the same as localtime(), except
627 when passed a Time::Piece object, in which case it's a copy constructor. The
628 following methods are available on the object:
629
630     $t->sec                 # also available as $t->second
631     $t->min                 # also available as $t->minute
632     $t->hour                # 24 hour
633     $t->mday                # also available as $t->day_of_month
634     $t->mon                 # 1 = January
635     $t->_mon                # 0 = January
636     $t->monname             # Feb
637     $t->month               # same as $t->monname
638     $t->fullmonth           # February
639     $t->year                # based at 0 (year 0 AD is, of course 1 BC)
640     $t->_year               # year minus 1900
641     $t->yy                  # 2 digit year
642     $t->wday                # 1 = Sunday
643     $t->_wday               # 0 = Sunday
644     $t->day_of_week         # 0 = Sunday
645     $t->wdayname            # Tue
646     $t->day                 # same as wdayname
647     $t->fullday             # Tuesday
648     $t->yday                # also available as $t->day_of_year, 0 = Jan 01
649     $t->isdst               # also available as $t->daylight_savings
650
651     $t->hms                 # 12:34:56
652     $t->hms(".")            # 12.34.56
653     $t->time                # same as $t->hms
654
655     $t->ymd                 # 2000-02-29
656     $t->date                # same as $t->ymd
657     $t->mdy                 # 02-29-2000
658     $t->mdy("/")            # 02/29/2000
659     $t->dmy                 # 29-02-2000
660     $t->dmy(".")            # 29.02.2000
661     $t->datetime            # 2000-02-29T12:34:56 (ISO 8601)
662     $t->cdate               # Tue Feb 29 12:34:56 2000
663     "$t"                    # same as $t->cdate
664
665     $t->epoch               # seconds since the epoch
666     $t->tzoffset            # timezone offset in a Time::Seconds object
667
668     $t->julian_day          # number of days since Julian period began
669     $t->mjd                 # modified Julian date (JD-2400000.5 days)
670
671     $t->week                # week number (ISO 8601)
672
673     $t->is_leap_year        # true if it its
674     $t->month_last_day      # 28-31
675
676     $t->time_separator($s)  # set the default separator (default ":")
677     $t->date_separator($s)  # set the default separator (default "-")
678     $t->day_list(@days)     # set the default weekdays
679     $t->mon_list(@days)     # set the default months
680
681     $t->strftime(FORMAT)    # same as POSIX::strftime (without the overhead
682                             # of the full POSIX extension)
683     $t->strftime()          # "Tue, 29 Feb 2000 12:34:56 GMT"
684     
685     Time::Piece->strptime(STRING, FORMAT)
686                             # see strptime man page. Creates a new
687                             # Time::Piece object
688
689 =head2 Local Locales
690
691 Both wdayname (day) and monname (month) allow passing in a list to use
692 to index the name of the days against. This can be useful if you need
693 to implement some form of localisation without actually installing or
694 using locales.
695
696   my @days = qw( Dimanche Lundi Merdi Mercredi Jeudi Vendredi Samedi );
697
698   my $french_day = localtime->day(@days);
699
700 These settings can be overriden globally too:
701
702   Time::Piece::day_list(@days);
703
704 Or for months:
705
706   Time::Piece::mon_list(@months);
707
708 And locally for months:
709
710   print localtime->month(@months);
711
712 =head2 Date Calculations
713
714 It's possible to use simple addition and subtraction of objects:
715
716     use Time::Seconds;
717     
718     my $seconds = $t1 - $t2;
719     $t1 += ONE_DAY; # add 1 day (constant from Time::Seconds)
720
721 The following are valid ($t1 and $t2 are Time::Piece objects):
722
723     $t1 - $t2; # returns Time::Seconds object
724     $t1 - 42; # returns Time::Piece object
725     $t1 + 533; # returns Time::Piece object
726
727 However adding a Time::Piece object to another Time::Piece object
728 will cause a runtime error.
729
730 Note that the first of the above returns a Time::Seconds object, so
731 while examining the object will print the number of seconds (because
732 of the overloading), you can also get the number of minutes, hours,
733 days, weeks and years in that delta, using the Time::Seconds API.
734
735 =head2 Date Comparisons
736
737 Date comparisons are also possible, using the full suite of "<", ">",
738 "<=", ">=", "<=>", "==" and "!=".
739
740 =head2 Date Parsing
741
742 Time::Piece links to your C library's strptime() function, allowing
743 you incredibly flexible date parsing routines. For example:
744
745   my $t = Time::Piece->strptime("Sun 3rd Nov, 1943",
746                                 "%A %drd %b, %Y");
747   
748   print $t->strftime("%a, %d %b %Y");
749
750 Outputs:
751
752   Wed, 03 Nov 1943
753
754 (see, it's even smart enough to fix my obvious date bug)
755
756 For more information see "man strptime", which should be on all unix
757 systems.
758
759 =head2 YYYY-MM-DDThh:mm:ss
760
761 The ISO 8601 standard defines the date format to be YYYY-MM-DD, and
762 the time format to be hh:mm:ss (24 hour clock), and if combined, they
763 should be concatenated with date first and with a capital 'T' in front
764 of the time.
765
766 =head2 Week Number
767
768 The I<week number> may be an unknown concept to some readers.  The ISO
769 8601 standard defines that weeks begin on a Monday and week 1 of the
770 year is the week that includes both January 4th and the first Thursday
771 of the year.  In other words, if the first Monday of January is the
772 2nd, 3rd, or 4th, the preceding days of the January are part of the
773 last week of the preceding year.  Week numbers range from 1 to 53.
774
775 =head2 Global Overriding
776
777 Finally, it's possible to override localtime and gmtime everywhere, by
778 including the ':override' tag in the import list:
779
780     use Time::Piece ':override';
781
782 =head1 AUTHOR
783
784 Matt Sergeant, matt@sergeant.org
785 Jarkko Hietaniemi, jhi@iki.fi (while creating Time::Piece for core perl)
786
787 =head1 License
788
789 This module is free software, you may distribute it under the same terms
790 as Perl.
791
792 =head1 SEE ALSO
793
794 The excellent Calendar FAQ at http://www.tondering.dk/claus/calendar.html
795
796 =head1 BUGS
797
798 The test harness leaves much to be desired. Patches welcome.
799
800 =cut