File Coverage

blib/lib/Date/Formatter.pm
Criterion Covered Total %
statement 206 210 98.1
branch 68 70 97.1
condition 38 51 74.5
subroutine 50 52 96.1
pod 42 42 100.0
total 404 425 95.0


line stmt bran cond sub pod time code
1              
2             package Date::Formatter;
3             {
4             $Date::Formatter::VERSION = '0.11';
5             }
6              
7 7     7   207847 use strict;
  7         15  
  7         278  
8 7     7   41 use warnings;
  7         13  
  7         245  
9              
10 7     7   42 use Scalar::Util qw(blessed);
  7         17  
  7         1365  
11              
12 7     7   8901 use Time::Local ();
  7         26044  
  7         268  
13 7     7   8111 use DateTime::Locale;
  7         723460  
  7         413  
14              
15             ## overload operators
16             use overload (
17 7         57 '""' => "toString",
18             '==' => "equal",
19             '!=' => "notEqual",
20             '<=>' => "compare",
21             '+' => "add",
22             '-' => "subtract"
23 7     7   11464 );
  7         8251  
24              
25             ### constructor
26              
27             sub new {
28 53     53 1 3674 my ($_class, %date) = @_;
29 53   66     228 my $class = ref($_class) || $_class;
30 53         90 my $date = {};
31 53         113 bless($date, $class);
32 53         158 $date->_init(%date);
33 50         208 return $date;
34             }
35              
36              
37             sub now {
38 12     12 1 2516 my ( $self, %date ) = @_;
39 12   100     204 my $locale = $date{locale} || 'en';
40 12         47 return $self->new( locale => $locale );
41             }
42              
43             sub _init {
44 54     54   103 my ($self, %date) = @_;
45 54         505 $self->{hourType} = 12;
46 54         98 $self->{abbreviateMonths} = 0;
47 54         82 $self->{abbreviateDays} = 0;
48 54         88 $self->{formatter} = undef;
49 54         88 $self->{internal} = undef;
50 54         105 $self->{elements} = [];
51 54         82 $self->{am_or_pm} = undef;
52 54         131 $self->{gmt_offset_hours} = undef;
53 54         81 $self->{gmt_offset_minutes} = undef;
54              
55 54         168 $self->setLocale( delete $date{locale} );
56              
57 54 100       148 if (%date) {
58             # we let Time::Local do the range checking
59             # on these values here,..
60 6 100       23 $date{seconds} = 0 unless exists $date{seconds};
61 6 100       18 $date{minutes} = 0 unless exists $date{minutes};
62 6 100       17 $date{hour} = 0 unless exists $date{hour};
63 6 100       16 $date{year} = 0 unless exists $date{year};
64 6 100       16 $date{day_of_month} = 1 unless exists $date{day_of_month};
65             # we accept normal month values
66             # instead of zero index months
67 6 100       14 if (exists $date{month}) {
68 4 100 100     75 ($date{month} =~ /^\d+$/ && $date{month} >= 1)
69             || die "Insufficient Arguments : 'month' value must be numeric and at least 1";
70 2         4 $date{month} -= 1;
71             }
72             else {
73 2         4 $date{month} = 0;
74             }
75 4         8 my $new_time;
76 4         5 eval {
77 4         22 $new_time = Time::Local::timelocal(
78             $date{seconds}, $date{minutes}, $date{hour},
79             $date{day_of_month}, $date{month}, $date{year}
80             );
81             };
82 4 100       1031 die "Insufficient Arguments : Could not construct a proper date value : $@" if $@;
83 3         11 $self->_setTime($new_time);
84             }
85             else {
86 48         216 $self->_setTime(time());
87             }
88             }
89              
90             sub setLocale {
91 54     54 1 74 my $self = shift;
92 54         63 my ($locale) = @_;
93              
94 54   100     227 $locale ||= 'en';
95              
96 54         240 $self->{locale} = DateTime::Locale->load( $locale );
97              
98 54         57162 return;
99             }
100              
101             ## alternate constructor
102             # for creating intervals of time.
103             # --------------------------------------
104             # this is best used with the overloaded
105             # versions of the '+' and '-' operator
106             # to increment and decrement another
107             # date object.
108             #
109             # Accepts the following named arguments:
110             # - > years (365 days)
111             # - > leap years (366 days)
112             # - > months (assumes 30 days)
113             # - > weeks
114             # - > days
115             # - > hours
116             # - > minutes
117             # - > seconds
118             #
119             # NOTE:
120             # This can also be used to set a time
121             # sometime past the epoch, but that is
122             # not terribly useful. Except maybe to
123             # pass in nothing and get the date of the
124             # epoch on your current system.
125             sub createTimeInterval {
126 20     20 1 2339 my ($class, %_date) = @_;
127 20         30 my $time = 0;
128 20 100 66     87 if (exists($_date{years}) && $_date{years}){
129 1         4 $time += $_date{years} * 365 * 24 * 60 * 60;
130             }
131 20 100 66     67 if (exists($_date{leapyears}) && $_date{leapyears}){
132 1         4 $time += $_date{leapyears} * 366 * 24 * 60 * 60;
133             }
134 20 100 66     76 if (exists($_date{months}) && $_date{months}){
135 1         3 $time += $_date{months} * 30 * 24 * 60 * 60;
136             }
137 20 100 66     69 if (exists($_date{weeks}) && $_date{weeks}){
138 1         3 $time += $_date{weeks} * 7 * 24 * 60 * 60;
139             }
140 20 100 66     76 if (exists($_date{days}) && $_date{days}){
141 2         6 $time += $_date{days} * 24 * 60 * 60;
142             }
143 20 100 66     93 if (exists($_date{hours}) && $_date{hours}){
144 10         51 $time += $_date{hours} * 60 * 60;
145             }
146 20 100 66     78 if (exists($_date{minutes}) && $_date{minutes}){
147 3         9 $time += $_date{minutes} * 60;
148             }
149 20 100 66     75 if (exists($_date{seconds}) && $_date{seconds}){
150 2         4 $time += $_date{seconds};
151             }
152             # if the are asking for nothing then
153             # give them 1 second past the epoch
154 20   100     50 $time ||= 1;
155 20         86 return _setTime($class->new(), $time);
156             }
157              
158             # occasionally you will want to refresh
159             # the time to be the current time. This
160             # would allow a Date object to be used
161             # over a long period of time
162             sub refresh {
163 1     1 1 3 my ($self) = @_;
164 1         6 $self->_init();
165 1         4 return $self;
166             }
167              
168             # creates a formatter subroutine to be used when
169             # the date object is printed:
170             # print $date;
171             # (see below for more documentation)
172             {
173              
174             my %_parser_table = (
175             "MM" => \&getNumericMonth,
176             "M" => \&getMonth,
177             "DD" => \&getDayOfMonth,
178             "D" => \&getDayOfWeek,
179             "YY" => \&getYear,
180             "YYYY" => \&getFullYear,
181             "hh" => \&getHours,
182             "mm" => \&getMinutes,
183             "ss" => \&getSeconds,
184             "T" => \&isAMorPM,
185             "O" => \&getGMTOffset
186             );
187            
188             sub createDateFormatter {
189 14     14 1 3807 my ($self, $format, $pattern) = @_;
190 14         18 my @date_format;
191 14   66     75 $pattern ||= qr/\(|\)/;
192 14         258 my @tokens = split $pattern => $format;
193 14         42 while (@tokens) {
194 180         223 my $token = shift(@tokens);
195 180 100       290 if (exists $_parser_table{$token}) {
196 90         202 push @date_format, $_parser_table{$token};
197             }
198             else {
199 90     108   336 push @date_format, sub{ return "$token" };
  108         194  
200             }
201             }
202             $self->{formatter} = sub {
203 17     17   21 my ($self) = @_;
204 216         378 return join "" => map {
205 17         24 $_->($self);
206             } @date_format;
207 14         61 };
208 14         113 return $self;
209             }
210            
211             }
212              
213             sub getDateFormatter {
214 1     1 1 462 my ($self) = @_;
215 1         3 return $self->{formatter};
216             }
217              
218             sub setDateFormatter {
219 4     4 1 1021 my ($self, $formatter) = @_;
220 4 100 100     46 (defined($formatter) && ref($formatter) eq "CODE")
221             || die "Insufficient Arguments : bad formatter";
222 1         3 $self->{formatter} = $formatter;
223             }
224              
225             ## private
226            
227             # special private subroutine
228             # to set the internal time of
229             # a date object after it is
230             # created. This is used by:
231             # - createTimeInterval
232             # - add
233             # - subtract
234             sub _setTime {
235 95     95   167 my ($date, $new_time) = @_;
236 95         149 $date->{internal} = $new_time;
237 95         2569 $date->{elements} = [ localtime($new_time) ];
238             # must undefine this so that
239             # it gets re-generated
240 95         212 $date->{am_or_pm} = undef;
241 95         2109 my ($gmt_minutes, $gmt_hours) = (gmtime($new_time))[1, 2];
242 95         214 $date->{gmt_offset_hours} = ($date->{elements}->[2] - $gmt_hours);
243 95         160 $date->{gmt_offset_minutes} = ($date->{elements}->[1] - $gmt_minutes);
244 95         274 return $date;
245             }
246              
247             ## configuration
248              
249             # use 12 or 24 hour clock
250             sub use24HourClock {
251 4     4 1 16 my ($self) = @_;
252 4         11 $self->{hourType} = 24;
253             }
254              
255             sub use12HourClock {
256 3     3 1 10 my ($self) = @_;
257 3         8 $self->{hourType} = 12;
258             }
259              
260             # use short or long names for months and days
261             sub useLongNames {
262 1     1 1 2 my ($self) = @_;
263 1         2 $self->{abbreviateMonths} = 0;
264 1         3 $self->{abbreviateDays} = 0;
265             }
266              
267             sub useLongMonthNames {
268 1     1 1 2 my ($self) = @_;
269 1         3 $self->{abbreviateMonths} = 0;
270             }
271              
272             sub useLongDayNames {
273 1     1 1 2 my ($self) = @_;
274 1         4 $self->{abbreviateDays} = 0;
275             }
276              
277             # short names are the first 3 letters
278             sub useShortNames {
279 1     1 1 6 my ($self) = @_;
280 1         2 $self->{abbreviateMonths} = 1;
281 1         2 $self->{abbreviateDays} = 1;
282             }
283              
284             sub useShortMonthNames {
285 1     1 1 3 my ($self) = @_;
286 1         3 $self->{abbreviateMonths} = 1;
287             }
288              
289             sub useShortDayNames {
290 1     1 1 1 my ($self) = @_;
291 1         3 $self->{abbreviateDays} = 1;
292             }
293              
294              
295             ## informational
296              
297             sub isAMorPM {
298 9     9 1 24 my ($self) = @_;
299 9 100       32 return if ($self->{hourType} == 24);
300 8 100       32 $self->getHours() unless $self->{am_or_pm};
301 8         32 return $self->{am_or_pm};
302             }
303              
304             sub getSeconds {
305 21     21 1 1000726 my ($self) = @_;
306 21         126 return sprintf("%02d", $self->{elements}->[0]);
307             }
308              
309             sub getMinutes {
310 20     20 1 27 my ($self) = @_;
311 20         88 return sprintf("%02d", $self->{elements}->[1]);
312             }
313              
314             sub getHours {
315 33     33 1 53 my ($self) = @_;
316 33 100       91 if ($self->{hourType} == 12){
317 29         51 my $hours = $self->{elements}->[2];
318 29 100       107 if ($hours == 12){
    100          
    100          
    50          
319 2         3 $self->{am_or_pm} = "p.m.";
320 2         7 return 12;
321             }
322             elsif ($hours == 0) {
323 4         7 $self->{am_or_pm} = "a.m.";
324 4         12 return 12;
325             }
326             elsif ($hours < 12){
327 21         35 $self->{am_or_pm} = "a.m.";
328 21         52 return $hours;
329             }
330             elsif ($hours > 12){
331 2         4 $self->{am_or_pm} = "p.m.";
332 2         6 return $hours - 12;
333             }
334             }
335 4         16 return $self->{elements}->[2];
336             }
337              
338             # GMT offsets
339              
340             # ... by hours
341             sub getGMTOffsetHours {
342 1     1 1 5 my ($self) = @_;
343 1         5 return $self->{gmt_offset_hours};
344             }
345              
346             # ... by minutes
347             sub getGMTOffsetMinutes {
348 1     1 1 3 my ($self) = @_;
349 1         6 return $self->{gmt_offset_minutes};
350             }
351              
352             # and finally a formatted offset
353             sub getGMTOffset {
354 1     1 1 3 my ($self) = @_;
355 1         2 my $gmt_offset = abs($self->{gmt_offset_hours});
356 1         2 my $sign = "";
357 1 50       4 $sign = "-" if ($gmt_offset > $self->{gmt_offset_hours});
358 1         13 return sprintf("%s%02d00", ($sign, $gmt_offset));
359             }
360              
361             sub getDayOfMonth {
362 17     17 1 18 my ($self) = @_;
363 17         38 return $self->{elements}->[3];
364             }
365              
366             sub getMonth {
367 6     6 1 8 my ($self) = @_;
368 6 100       15 if ($self->{abbreviateMonths} == 1){
369 2         9 return $self->{locale}->month_format_abbreviated->[$self->{elements}[4]];
370             }
371 4         17 return $self->{locale}->month_format_wide->[$self->{elements}[4]];
372             }
373              
374             sub getNumericMonth {
375 11     11 1 17 my ($self) = @_;
376 11         28 return $self->{elements}->[4] + 1;
377             }
378              
379             sub getMonthIndex {
380 0     0 1 0 my ($self) = @_;
381 0         0 return $self->{elements}->[4];
382             }
383              
384             sub getFullYear {
385 16     16 1 19 my ($self) = @_;
386 16         144 return (1900 + $self->{elements}->[5]);
387             }
388              
389             sub getYear {
390 1     1 1 1 my ($self) = @_;
391 1         13 return sprintf("%02d", ($self->{elements}->[5] % 100));
392             }
393              
394             sub getDayOfWeek {
395 7     7 1 36 my ($self) = @_;
396              
397 7         11 my @days;
398 7 100       19 if ($self->{abbreviateDays} == 1){
399 2         3 @days = @{$self->{locale}->day_format_abbreviated};
  2         9  
400             }
401             else {
402 5         8 @days = @{$self->{locale}->day_format_wide};
  5         21  
403             }
404              
405             # DateTime::Locale has Monday as the first day. This module
406             # uses Sunday. So, move the last item to the front, so @days
407             # is now Sunday -> Saturday instead of Monday -> Sunday.
408 7         42 unshift(@days, pop(@days));
409              
410 7         25 return $days[$self->{elements}->[6]];
411             }
412              
413             sub getDayOfWeekIndex {
414 0     0 1 0 my ($self) = @_;
415 0         0 return $self->{elements}->[6];
416             }
417              
418             sub getDayOfYear {
419 1     1 1 3 my ($self) = @_;
420 1         37 return $self->{elements}->[7];
421             }
422              
423             ### overloaded interfaces
424              
425             sub clone {
426 12     12 1 24 my ($self) = @_;
427 12         26 return $self->unpack($self->pack());
428             }
429              
430             ## serialization
431              
432             sub pack {
433 25     25 1 3375 my ($self) = @_;
434 25         197 return $self->{internal};
435             }
436              
437             sub unpack {
438             # this is an alternate constructor
439 13     13 1 30 my ($class, $packed_string) = @_;
440 13         31 my $obj = _setTime($class->new(), $packed_string);
441              
442             #Uncomment if you want clones to clone the locale, as well
443             #$obj->{locale} = $self->{locale};
444              
445 13         53 return $obj;
446             }
447              
448             ## printing
449              
450             sub toString {
451             # this could be more
452             # robust to take advantage of
453             # the module configurations
454 19     19 1 1975 my ($self) = @_;
455 19 100       123 return $self->{formatter}->($self) if $self->{formatter};
456 2         77 return scalar localtime($self->{internal});
457             }
458              
459             # return the unmolested object string
460             sub stringValue {
461 4     4 1 14 my ($self) = @_;
462 4         19 return overload::StrVal($self);
463             }
464              
465             ### overloaded operators
466              
467             # Addition and Subtraction operators are
468             # best used in conjunction with a Data object that
469             # has been create using the createTimeInterval
470             # constructor.
471              
472             sub add {
473 10     10 1 1532 my ($left, $right) = @_;
474 10 100 66     221 (blessed($right) && $right->isa("Date::Formatter"))
475             || die "Illegal Operation : Cannot add a date object to a non-date object.";
476 8         26 return _setTime($left->clone(), $left->{internal} + $right->{internal});
477             }
478              
479             # sub addEqual {
480             # my ($left, $right) = @_;
481             # ((ref($left) eq "Date::Formatter") && (ref($right) eq "Date::Formatter")) || die "IllegalOperation : IllegalOperation : Cannot add a date object to a non-date object.";
482             # return $left->_setTime($left->{internal} + $right->{internal});
483             # }
484              
485             sub subtract {
486 5     5 1 131 my ($left, $right) = @_;
487 5 100 66     72 (blessed($right) && $right->isa("Date::Formatter"))
488             || die "Illegal Operation : Cannot subtract a date object from a non-date object.";
489 3         13 return _setTime($left->clone(), $left->{internal} - $right->{internal});
490             }
491              
492             # sub subtractEqual {
493             # my ($left, $right) = @_;
494             # ((ref($left) eq "Date::Formatter") && (ref($right) eq "Date::Formatter")) || die "IllegalOperation : Cannot subtract a date object from a non-date object.";
495             # return $left->_setTime($left->{internal} - $right->{internal});
496             # }
497              
498             # compare two dates
499             sub compare {
500 19     19 1 130 my ($left, $right) = @_;
501 19 100 66     194 (blessed($right) && $right->isa("Date::Formatter"))
502             || die "Illegal Operation : Cannot compare a date object to a non-date object.";
503 13         92 return ($left->{internal} <=> $right->{internal});
504             }
505              
506             sub equal {
507 12     12 1 9362 my ($left, $right) = @_;
508 12 100       29 return ($left->compare($right) == 0) ? 1 : 0;
509             }
510              
511             sub notEqual {
512 6     6 1 133 my ($left, $right) = @_;
513 6 100       17 return ($left->equal($right)) ? 0 : 1;
514             }
515              
516              
517             1;
518              
519             __END__