File Coverage

blib/lib/Calendar/Functions.pm
Criterion Covered Total %
statement 134 134 100.0
branch 75 78 96.1
condition 55 56 98.2
subroutine 18 18 100.0
pod 10 10 100.0
total 292 296 98.6


line stmt bran cond sub pod time code
1             package Calendar::Functions;
2              
3 18     18   1021107 use strict;
  18         55  
  18         585  
4 18     18   96 use warnings;
  18         34  
  18         543  
5              
6 18     18   88 use vars qw($VERSION @ISA %EXPORT_TAGS @EXPORT @EXPORT_OK);
  18         32  
  18         3270  
7             $VERSION = '1.02';
8              
9             #----------------------------------------------------------------------------
10              
11             =head1 NAME
12              
13             Calendar::Functions - A module containing functions for dates and calendars.
14              
15             =head1 SYNOPSIS
16              
17             use Calendar::Functions;
18             $ext = ext($day);
19             $moty = moty($monthname);
20             $monthname = moty($moty);
21             $dotw = dotw($dayname);
22             $dayname = dotw($dotw);
23              
24             use Calendar::Functions qw(:dates);
25             my $dateobj = encode_date($day,$month,$year);
26             ($day,$month,$year,$dotw) = decode_date($dateobj);
27             $cmp = compare_dates($dateobj1, $dateobj2);
28              
29             use Calendar::Functions qw(:form);
30             $str = format_date( $fmt, $day, $month, $year, $dotw);
31             $str = reformat_date( $date, $fmt1, $fmt2 );
32              
33             use Calendar::Functions qw(:all);
34             fail_range($year);
35              
36             =head1 DESCRIPTION
37              
38             The module is intended to provide numerous support functions for other
39             date and/or calendar functions
40              
41             =head1 EXPORT
42              
43             ext, moty, dotw
44              
45             dates: encode_date, decode_date, compare_dates, add_day
46              
47             form: format_date, reformat_date
48              
49             all: encode_date, decode_date, compare_dates, add_day
50             format_date, reformat_date,
51             ext, moty, dotw, fail_range
52              
53             =cut
54              
55             #----------------------------------------------------------------------------
56              
57             #############################################################################
58             #Export Settings #
59             #############################################################################
60              
61             require Exporter;
62              
63             @ISA = qw(Exporter);
64              
65             %EXPORT_TAGS = (
66             'basic' => [ qw( ext moty dotw ) ],
67             'dates' => [ qw( ext moty dotw
68             encode_date decode_date compare_dates add_day ) ],
69             'form' => [ qw( ext moty dotw format_date reformat_date ) ],
70             'all' => [ qw( ext moty dotw format_date reformat_date fail_range
71             encode_date decode_date compare_dates add_day ) ],
72             'test' => [ qw( _caltest ) ],
73             );
74              
75             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} }, @{ $EXPORT_TAGS{'test'} } );
76             @EXPORT = ( @{ $EXPORT_TAGS{'basic'} } );
77              
78             #############################################################################
79             #Library Modules #
80             #############################################################################
81              
82 18     18   9358 use Time::Local;
  18         40091  
  18         38802  
83 18     18   9209 eval "use Date::ICal";
  18         140151  
  18         373  
84             my $di = ! $@;
85 18     18   16281 eval "use DateTime";
  18         8717765  
  18         404  
86             my $dt = ! $@;
87 18     18   11671 eval "use Time::Piece";
  18         132225  
  18         92  
88             my $tp = ! $@;
89              
90             if($tp) {
91             require Time::Piece;
92             }
93              
94             #############################################################################
95             #Variables
96             #############################################################################
97              
98             # prime our print out names
99             my @months = qw( NULL January February March April May June July
100             August September October November December );
101             my @dotw = qw( Sunday Monday Tuesday Wednesday Thursday Friday Saturday );
102              
103             my $MinYear = 1902;
104             my $MaxYear = 2037;
105             my $EpoYear = 1970;
106              
107             #----------------------------------------------------------------------------
108              
109             #############################################################################
110             #Interface Functions #
111             #############################################################################
112              
113             =head1 FUNCTIONS
114              
115             =over 4
116              
117             =item encode_date( DD, MM, YYYY )
118              
119             Translates the given date values into a date object or number.
120              
121             =cut
122              
123             # name: encode_date
124             # args: day,month,year .... standard numerical day/month/year values
125             # retv: date object or number
126             # desc: Translates the given date values into a date object or number.
127              
128             sub encode_date {
129 627     627 1 37754 my ($day,$mon,$year) = @_;
130 627         900 my $this;
131              
132 627 100 100     2838 if($day && $mon && $year) {
      100        
133 605 100       1688 if($dt) { # DateTime.pm loaded
    100          
134 230         755 $this = DateTime->new(day=>$day,month=>$mon,year=>$year);
135             } elsif($di) { # Date::ICal loaded
136 181         583 $this = Date::ICal->new(day=>$day,month=>$mon,year=>$year,offset=>0);
137             } else { # using Time::Local
138 194 100       386 return if(fail_range($year));
139 192         561 $this = timegm(0,0,12,$day,$mon-1,$year);
140             }
141             }
142              
143 625         93054 return $this
144             }
145              
146             =item decode_date( date )
147              
148             Translates the given date object into date values.
149              
150             =cut
151              
152             # name: decode_date
153             # args: date1 .... date object or number
154             # retv: the standard numerical day/month/year values
155             # desc: Translates the date object or number into date values.
156              
157             sub decode_date {
158 2868   100 2868 1 14105 my $date = shift || return;
159 2863         7760 my ($day,$month,$year,$dow);
160              
161 2863 100       5386 if($dt) { # DateTime.pm loaded
    100          
162 944         2192 ($day,$month,$year,$dow) =
163             ($date->day,$date->month,$date->year,$date->dow);
164 944         12406 $dow %= 7;
165             } elsif($di) { # Date::ICal loaded
166 931         1760 ($day,$month,$year,$dow) =
167             ($date->day,$date->month,$date->year,$date->day_of_week);
168             } else { # using Time::Local
169 988         15729 ($day,$month,$year,$dow) = (localtime($date))[3..6];
170 988         13224 (undef,undef,undef,$day,$month,$year,$dow) = (localtime($date));
171 988         2383 $month++;
172 988         1776 $year+=1900;
173             }
174              
175 2863         59103 return $day,$month,$year,$dow;
176             }
177              
178             =item compare_dates( date, date )
179              
180             Using the appropriate method, determines the ordering of the two given dates.
181              
182             =cut
183              
184             # name: compare_dates
185             # args: date1 .... date object or string
186             # date2 .... date object or string
187             # retv: the compare value, as per the 'cmp' or '<=>' functionality.
188             # desc: Using the selected module, determines whether the first date is before,
189             # after or the same as the second.
190              
191             sub compare_dates {
192 1708     1708 1 4403 my ($d1,$d2) = @_;
193 1708 100 100     3625 return 0 if(! defined $d1 && ! defined $d2);
194 1702 100 100     6425 return 1 if( defined $d1 && ! defined $d2);
195 988 100       1822 return -1 if(! defined $d1);
196              
197 982         1329 my $diff = 0;
198 982 100       1847 if($dt) { $diff = DateTime->compare( $d1, $d2 ); }
  320 100       882  
199 312         664 elsif($di) { $diff = $d1->compare($d2); }
200 350 100       628 else { $diff = $d1 < $d2 ? -1 : ($d1 > $d2 ? 1 : 0); }
    100          
201              
202 982         25976 return $diff;
203             }
204              
205             =item add_day( date )
206              
207             Add one day to the date object.
208              
209             =cut
210              
211             sub add_day {
212 1548     1548 1 2245 my $d1 = shift;
213              
214 1548 100       2960 if($dt) { $d1->add( days => 1 ); }
  506 100       1759  
215 506         1038 elsif($di) { $d1->add( day => 1 ); }
216 536         719 else { $d1 += 60 * 60 * 24; }
217              
218 1548         514337 return $d1;
219             }
220              
221             =item format_date( fmt, day, mon, year [, dotw])
222              
223             transposes the standard date values into a formatted string.
224              
225             =cut
226              
227             # name: format_date
228             # args: fmt ............. format string
229             # day/mon/year .... standard date values
230             # dotw ............ day of the week number (optional)
231             # retv: newly formatted date
232             # desc: Transposes the format string and date values into a correctly
233             # formatted date string.
234              
235             sub format_date {
236 1702     1702 1 21271 my ($fmt,$day,$mon,$year,$dotw) = @_;
237 1702 100 100     6819 return unless($day && $mon && $year);
      100        
238              
239 1699 100       2956 unless($dotw) {
240 258         574 (undef,undef,undef,$dotw) = decode_date(encode_date($day,$mon,$year));
241             }
242              
243             # create date mini strings
244 1699         4560 my $fday = sprintf "%02d", $day;
245 1699         2968 my $fmon = sprintf "%02d", $mon;
246 1699         2679 my $fyear = sprintf "%04d", $year;
247 1699         3091 my $fmonth = sprintf "%s", $months[$mon];
248 1699         2705 my $fdotw = sprintf "%s", $dotw[$dotw];
249 1699         2914 my $fddext = sprintf "%d%s", $day, ext($day);
250 1699         3124 my $amonth = substr($fmonth,0,3);
251 1699         2446 my $adotw = substr($fdotw,0,3);
252 1699         2195 my $epoch = -1; # an arbitory number
253              
254             # epoch only supports the same dates in the 32-bit range
255 1699 100 66     6140 if($tp && $fmt =~ /\bEPOCH\b/ && $year >= $EpoYear && $year <= $MaxYear) {
      100        
      100        
256 3         16 my $date = timegm 0, 0, 12, $day, $mon -1, $year;
257 3         156 my $t = Time::Piece::gmtime($date);
258 3 50       244 $epoch = $t->epoch if($t);
259             }
260              
261             # transpose format string into a date string
262 1699         4981 $fmt =~ s/\bDMY\b/$fday-$fmon-$fyear/i;
263 1699         3234 $fmt =~ s/\bMDY\b/$fmon-$fday-$fyear/i;
264 1699         3055 $fmt =~ s/\bYMD\b/$fyear-$fmon-$fday/i;
265 1699         3090 $fmt =~ s/\bMABV\b/$amonth/i;
266 1699         3080 $fmt =~ s/\bDABV\b/$adotw/i;
267 1699         3224 $fmt =~ s/\bMONTH\b/$fmonth/i;
268 1699         3180 $fmt =~ s/\bDAY\b/$fdotw/i;
269 1699         2905 $fmt =~ s/\bDDEXT\b/$fddext/i;
270 1699         4842 $fmt =~ s/\bYYYY\b/$fyear/i;
271 1699         4419 $fmt =~ s/\bMM\b/$fmon/i;
272 1699         3983 $fmt =~ s/\bDD\b/$fday/i;
273 1699         3259 $fmt =~ s/\bEPOCH\b/$epoch/i;
274              
275 1699         4319 return $fmt;
276             }
277              
278             =item reformat_date( date, form1, form1 )
279              
280             transposes the standard date values into a formatted string.
281              
282             =cut
283              
284             # name: reformat_date
285             # args: date ..... date string
286             # form1 .... format string
287             # form2 .... format string
288             # retv: converted date string
289             # desc: Transposes the date from one format to another.
290              
291             sub reformat_date {
292 8     8 1 4672 my ($date,$form1,$form2) = @_;
293 8         16 my ($year,$mon,$day,$dotw) = ();
294              
295 8         20 while($form1) {
296 49 100       186 if($form1 =~ /^YYYY/) {
    100          
    100          
    100          
    100          
    100          
297 8         23 ($year) = ($date =~ /^(\d{4})/);
298 8         23 $form1 =~ s/^....//;
299 8         19 $date =~ s/^....//;
300              
301             } elsif($form1 =~ /^MONTH/) {
302 3         7 my ($month) = ($date =~ /^(\w+)/);
303 3         8 $mon = moty($month);
304 3         11 $form1 =~ s/^\w+//;
305 3         9 $date =~ s/^\w+//;
306              
307             } elsif($form1 =~ /^MM/) {
308 5         13 ($mon) = ($date =~ /^(\d{2})/);
309 5         12 $form1 =~ s/^..//;
310 5         13 $date =~ s/^..//;
311              
312             } elsif($form1 =~ /^DDEXT/) {
313 3         11 ($day) = ($date =~ /^(\d{1,2})/);
314 3         7 $form1 =~ s/^.....//;
315 3         10 $date =~ s/^\d{1,2}..//;
316              
317             } elsif($form1 =~ /^DD/) {
318 5         14 ($day) = ($date =~ /^(\d{2})/);
319 5         12 $form1 =~ s/^..//;
320 5         16 $date =~ s/^..//;
321              
322             } elsif($form1 =~ /^DAY/) {
323 3         14 my ($wday) = ($date =~ /^(\w+)/);
324 3         8 $dotw = dotw($wday);
325 3         12 $form1 =~ s/^\w+//;
326 3         12 $date =~ s/^\w+//;
327              
328             } else {
329 22         41 $form1 =~ s/^.//;
330 22         53 $date =~ s/^.//;
331             }
332             }
333              
334             # return original date if badly formed date
335 8 100 100     56 return $_[0] unless(int($day) && int($mon) && int($year));
      100        
336              
337             # get the day of the week, if we need it
338 5 100 100     38 $dotw = dotw($day,$mon,$year) if($form2 =~ /DAY/ && !$dotw);
339              
340             # rebuild date into second format
341 5         13 return format_date($form2,$day,$mon,$year,$dotw);
342             }
343              
344             =item ext( day )
345              
346             Returns the extension associated with the given day value.
347              
348             =cut
349              
350             # name: ext
351             # args: day .... day value
352             # retv: day value extension
353             # desc: Returns the extension associated with the given day value.
354              
355             sub ext {
356 1730 100 100 1730 1 26890 return 'st' if($_[0] == 1 ||$_[0] == 21 || $_[0] == 31);
      100        
357 1585 100 100     4476 return 'nd' if($_[0] == 2 ||$_[0] == 22);
358 1470 100 100     4036 return 'rd' if($_[0] == 3 ||$_[0] == 23);
359 1327         2968 return 'th';
360             }
361              
362             =item dotw( day | dayname )
363              
364             Returns the day number (0..6) if passed the day name, or the day
365             name if passed a numeric.
366              
367             =cut
368              
369             sub dotw {
370 20 100   20 1 7756 return $dotw[$_[0]] if($_[0] =~ /\d/);
371              
372 11         32 foreach my $inx (0..6) {
373 44 100       310 return $inx if($_[0] =~ /$dotw[$inx]/i);
374             }
375              
376 1         9 return;
377             }
378              
379             =item moty( month | monthname )
380              
381             Returns the month number (1..12) if passed the month name, or the month
382             name if passed a numeric.
383              
384             =cut
385              
386             sub moty {
387 29 100   29 1 13992 return $months[$_[0]] if($_[0] =~ /\d/);
388              
389 16         41 foreach my $inx (1..12) {
390 112 100       779 return $inx if($_[0] =~ /$months[$inx]/i);
391             }
392              
393 1         8 return;
394             }
395              
396             =item fail_range( year )
397              
398             Returns true or false based on whether the date given will break the
399             basic date range, 01-01-1902 to 31-12-2037.
400              
401             =cut
402              
403             sub fail_range {
404 212 100   212 1 2215 return 1 unless($_[0]);
405 209 100 100     640 return 0 if($dt || $di);
406 199 100 100     617 return 1 if($_[0] < $MinYear || $_[0] > $MaxYear);
407 195         392 return 0;
408             }
409              
410             sub _caltest {
411 7 50   7   4457 $dt = $_[0] if($dt);
412 7 50       35 $di = $_[1] if($di);
413             }
414              
415             1;
416              
417             __END__
418              
419             #----------------------------------------------------------------------------
420              
421             =back
422              
423             =head1 DATE FORMATS
424              
425             =over 4
426              
427             =item Parameters
428              
429             The date formatting parameters passed to the two formatting functions can
430             take many different formats. A formatting string can contain several key
431             strings, which will be replaced with date components. The following are
432             key strings which are currently supported:
433              
434             DD
435             MM
436             YYYY
437             DAY
438             MONTH
439             DDEXT
440             DMY
441             MDY
442             YMD
443             MABV
444             DABV
445              
446             The first three are tranlated into the numerical day/month/year strings.
447             The DAY format is translated into the day of the week name, and MONTH
448             is the month name. DDEXT is the day with the appropriate suffix, eg 1st,
449             22nd or 13th. DMY, MDY and YMD default to '13-09-1965' (DMY) style strings.
450             MABV and DABV provide 3 letter abbreviations of MONTH and DAY respectively.
451              
452             =back
453              
454             =head1 DATE MODULES
455              
456             Internal to this module is some date comparison code. As a consequence this
457             requires some date modules that can handle a wide range of dates. There are
458             three modules which are tested for you, these are, in order of preference,
459             DateTime, Date::ICal and Time::Local.
460              
461             Each module has the ability to handle dates, although only Time::Local exists
462             in the core release of Perl. Unfortunately Time::Local is limited by the
463             Operating System. On a 32bit machine this limit means dates before 1st January
464             1902 and after 31st December 2037 will not be represented. If this date range
465             is well within your scope, then you can safely allow the module to use
466             Time::Local. However, should you require a date range that exceedes this
467             range, then it is recommended that you install one of the two other modules.
468              
469             =head1 ERROR HANDLING
470              
471             In the event that Time::Local is being used and dates that exceed the range
472             of 1st January 1902 to 31st December 2037 are passed, an undef is returned.
473              
474             =head1 SEE ALSO
475              
476             =head2 Further Modules
477              
478             =over 4
479              
480             =item L<Calendar::List>
481              
482             =back
483              
484             =head2 Date/Time Modules
485              
486             =over 4
487              
488             =item L<Date::ICal>
489              
490             =item L<DateTime>
491              
492             =item L<Time::Local>
493              
494             =item L<Time::Piece>
495              
496             =back
497              
498             =head2 Further Information
499              
500             =over 4
501              
502             =item L<The Calendar FAQ>
503              
504             L<http://www.tondering.dk/claus/calendar.html>
505              
506             =item L<The Perl Advent Entry>
507              
508             2018-12-01 : L<http://perladvent.org/2018/2018-12-01.html>
509              
510             =back
511            
512             =head1 BUGS, PATCHES & FIXES
513              
514             There are no known bugs at the time of this release. However, if you spot a
515             bug or are experiencing difficulties that are not explained within the POD
516             documentation, please submit a bug to the RT system (see link below). However,
517             it would help greatly if you are able to pinpoint problems or even supply a
518             patch.
519              
520             Fixes are dependent upon their severity and my availability. Should a fix not
521             be forthcoming, please feel free to (politely) remind me by sending an email
522             to barbie@cpan.org .
523              
524             RT: http://rt.cpan.org/Public/Dist/Display.html?Name=Calendar-List
525              
526             =head1 AUTHOR
527              
528             Barbie, <barbie@cpan.org>
529             for Miss Barbell Productions <http://www.missbarbell.co.uk>.
530              
531             =head1 THANKS TO
532              
533             Dave Cross, E<lt>dave at dave.orgE<gt> for creating Calendar::Simple, the
534             newbie poster on a technical message board who inspired me to write the
535             original wrapper code and Richard Clamp E<lt>richardc at unixbeard.co.ukE<gt>
536             for testing the beta versions.
537              
538             =head1 COPYRIGHT AND LICENSE
539              
540             Copyright (C) 2003-2019 Barbie for Miss Barbell Productions
541              
542             This distribution is free software; you can redistribute it and/or
543             modify it under the Artistic License v2.
544              
545             =cut