File Coverage

blib/lib/Date/EzDate2.pm
Criterion Covered Total %
statement 396 495 80.0
branch 204 292 69.8
condition 67 95 70.5
subroutine 40 48 83.3
pod 0 9 0.0
total 707 939 75.2


line stmt bran cond sub pod time code
1             package Date::EzDate2;
2 1     1   1456 use strict;
  1         1  
  1         23  
3 1     1   3 use Carp 'croak';
  1         0  
  1         61  
4              
5             # debugging tools
6             # use Debug::ShowStuff ':all';
7             # use Debug::ShowStuff::ShowVar;
8              
9             # version
10             our $VERSION = '0.02';
11              
12              
13             #------------------------------------------------------------------------------
14             # pod
15             #
16              
17             =head1 NAME
18              
19             Date::EzDate2 - Date and time manipulation made easy
20              
21             =head1 EzDate2 vs EzDate
22              
23             EzDate2 is the next generation of the module Date::EzDate. EzDate2 provides
24             the same easy interface for dealing with date and time, but uses DateTime
25             to provide a wider and more accurate set of services.
26              
27             EzDate2 is *NOT* a drop-in replacement for EzDate. That's why it's not just
28             an updated version of EzDate.
29              
30             This early release is just to test EzDate2's distribution on CPAN. It's not yet
31             ready for prime time.
32              
33             =head1 SYNOPSIS
34              
35             An EzDate2 object represents a single point in time and exposes all properties
36             of that point. It also makes it easy to change those properties to produce
37             a different point in time. EzDate2 has many features, here are a few:
38              
39             use Date::EzDate2;
40             my $mydate = Date::EzDate2->new();
41              
42             # output some date information
43             print $mydate, "\n"; # e.g. output: Fri Jul 22, 2016 16:53:33
44              
45             =cut
46              
47             #
48             # pod
49             #------------------------------------------------------------------------------
50              
51              
52             # warn levels
53 1     1   3 use constant WARN_NONE => 0;
  1         2  
  1         58  
54 1     1   3 use constant WARN_STDERR => 1;
  1         8  
  1         37  
55 1     1   3 use constant WARN_CROAK => 2;
  1         1  
  1         92  
56             our $default_warning = WARN_STDERR;
57              
58             # default time zone
59             our $default_time_zone = undef;
60              
61              
62             #------------------------------------------------------------------------------
63             # object overloading
64             #
65             use overload
66 6     6   60 '""' => sub{$_[0]->{'default'}}, # stringification
67 1         7 '<=>' => \&compare, # comparison
68             '+' => \&addition, # addition
69             '-' => \&subtraction, # subtraction
70 1     1   897 fallback => 1; # operations not defined here
  1         740  
71             #
72             # object overloading
73             #------------------------------------------------------------------------------
74              
75              
76             #------------------------------------------------------------------------------
77             # new
78             #
79             sub new {
80 40     40 0 11222 my ($class, $time, %opts) = @_;
81 40         38 my ($rv, %tiehash);
82            
83             # TESTING
84             # println subname(); ##i
85            
86             # create tied hash
87 40 50       209 tie(%tiehash, $class . '::Tie', $time, %opts) or return undef;
88            
89             # create blessed reference to tied hash
90 40         74 $rv = bless(\%tiehash, $class);
91            
92             # return
93 40         97 return $rv;
94             }
95             #
96             # new
97             #------------------------------------------------------------------------------
98              
99              
100             #------------------------------------------------------------------------------
101             # clone
102             #
103             sub clone {
104 16     16 0 133 my ($ezdate) = @_;
105            
106             # TESTING
107             # println subname(), ' - ', __PACKAGE__; ##i
108            
109             # call tied object's new()
110 16         48 return ref($ezdate)->new($ezdate);
111             }
112             #
113             # clone
114             #------------------------------------------------------------------------------
115              
116              
117             #------------------------------------------------------------------------------
118             # next_month
119             #
120             sub next_month {
121 3     3 0 21 my ($ezdate, $count) = @_;
122            
123             # TESTING
124             # println subname(); ##i
125            
126             # call tied object's next_month()
127 3         7 return tied(%$ezdate)->next_month($count);
128             }
129             #
130             # next_month
131             #------------------------------------------------------------------------------
132              
133              
134              
135             #------------------------------------------------------------------------------
136             # settings and utility subs
137             #
138 0     0 0 0 sub zero_hour_ampm {return $_[0]->tie_ob->{'zero_hour_ampm'} = $_[1]}
139              
140 1     1 0 7 sub set_format {return $_[0]->tie_ob->set_format(@_[1..$#_])}
141              
142             sub get_format {
143 0     0 0 0 my ($self, $key) = @_;
144 0         0 my $ob = $self->tie_ob;
145 0         0 $key =~ s|\s||gs;
146 0         0 $key = lc($key);
147 0         0 return join('', @{$ob->{'formats'}->{$key}});
  0         0  
148             }
149              
150 0     0 0 0 sub del_format {return delete $_[0]->{$_[1]}}
151 18     18 0 17 sub tie_ob{return tied(%{$_[0]})}
  18         56  
152              
153             # warnings level
154             sub set_warnings {
155 1     1 0 6 my ($ezdate, $level) = @_;
156            
157             # TESTING
158             # println subname(); ##i
159            
160             # level must be defined
161 1 50       4 if (! defined $level)
162 0         0 { croak 'level-not-defined: warning level is not defined' }
163            
164             # normalize
165 1         2 $level = lc($level);
166 1         2 $level =~ s|\s||gs;
167            
168             # plain english
169 1 50       6 if ($level eq 'none')
    50          
    50          
    0          
170 0         0 { $level = WARN_NONE }
171             elsif ($level eq 'stderr')
172 0         0 { $level = WARN_STDERR }
173             elsif ($level eq 'croak')
174 1         1 { $level = WARN_CROAK }
175             elsif ($level eq 'default')
176 0         0 { $level = $default_warning }
177            
178             # set tied object
179 1         4 return $ezdate->tie_ob->{'warnings'} = $level;
180             }
181              
182             #
183             # settings and utility subs
184             #------------------------------------------------------------------------------
185              
186              
187             ###############################################################################
188             # Date::EzDate2::Tie
189             #
190             package Date::EzDate2::Tie;
191 1     1   379 use strict;
  1         1  
  1         21  
192 1     1   840 use DateTime;
  1         72743  
  1         32  
193 1     1   5 use DateTime::TimeZone;
  1         2  
  1         17  
194 1     1   3 use Carp qw{carp croak};
  1         1  
  1         50  
195 1     1   403 use Clone;
  1         1937  
  1         100  
196 1     1   4 use base 'Tie::StdHash';
  1         2  
  1         426  
197              
198             # debugging tools
199             # use Debug::ShowStuff ':all';
200              
201              
202             #------------------------------------------------------------------------------
203             # globals
204             #
205             our (
206             @ltimefields, @OrdWords, $OrdWordsRx, %PCodes, %WeekDayNums,
207             %OrdWordsNums, @OrdNums, %MonthNums, $pcode,
208             );
209              
210             # localtime() fields
211             # @ltimefields = qw[sec min hour dayofmonth monthnum year weekdaynum yearday dst];
212             @ltimefields = qw[sec min hour dayofmonth monthnum year];
213              
214             # words for the days of the month
215             @OrdWords = qw[
216             Zeroth First Second Third Fourth Fifth Sixth Seventh Eighth Ninth Tenth
217             Eleventh Twelfth Thirteenth Fourteenth Fifteenth Sixteenth Seventeenth
218             Eighteenth Ninteenth Twentieth Twentyfirst Twentysecond Twentythird
219             Twentyfourth Twentyfifth Twentysixth Twentyseventh Twentyeighth Twentyninth
220             Thirtieth Thirtyfirst
221             ];
222              
223             # regular expression versions of OrdWords
224             $OrdWordsRx = '\b(' . join('|', @OrdWords[1..$#OrdWords]) . ')\b';
225              
226             # build hash or ord words
227             foreach my $i (1..$#OrdWords)
228             { $OrdWordsNums{lc($OrdWords[$i])} = $i }
229              
230             # number ordinals
231             @OrdNums = qw[
232             0th 1st 2nd 3rd 4th 5th 6th 7th 8th 9th 10th 11th 12th 13th 14th 15th 16th
233             17th 18th 19th 20th 21st 22nd 23rd 24th 25th 26th 27th 28th 29th 30th 31st
234             ];
235              
236             # month numbers
237             @MonthNums{qw[jan feb mar apr may jun jul aug sep oct nov dec]} = (1..12);
238              
239             # weekday numbers
240             $WeekDayNums{'mon'} = 1;
241             $WeekDayNums{'tue'} = 2;
242             $WeekDayNums{'wed'} = 3;
243             $WeekDayNums{'thu'} = 4;
244             $WeekDayNums{'fri'} = 5;
245             $WeekDayNums{'sat'} = 6;
246             $WeekDayNums{'sun'} = 7;
247              
248             # @WeekDayShort = qw[Sun Mon Tue Wed Thu Fri Sat];
249             # @WeekDayLong = qw[Sunday Monday Tuesday Wednesday Thursday Friday Saturday];
250             # @MonthShort = qw[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec];
251             # @MonthLong = qw[January February March April May June July August September October November December];
252             # @MonthDays = qw[31 x 31 30 31 30 31 31 30 31 30 31];
253              
254             # percent code regex
255             $pcode = '^\%[\w\%]$';
256              
257             # warn levels
258 1     1   4 use constant WARN_NONE => Date::EzDate2::WARN_NONE;
  1         2  
  1         63  
259 1     1   8 use constant WARN_STDERR => Date::EzDate2::WARN_STDERR;
  1         1  
  1         37  
260 1     1   7 use constant WARN_CROAK => Date::EzDate2::WARN_CROAK;
  1         1  
  1         4470  
261              
262             %PCodes = qw[
263             yearlong year
264             yearshort yeartwodigit
265             dayofyear yearday
266             dayofyear3d yearday3d
267             dayofweeknum weekdaynum
268             dayofweeknum2d weekdaynum2d
269             %Y year
270             %y yeartwodigit
271             %a weekdayshort
272             %A weekdaylong
273             %d dayofmonth
274             %D {monthnum2d}/{dayofmonth2d}/{year2d}
275             %H hour2d
276             %h monthshort
277             %b ampmhournozero
278             %B hournozero
279             %e monthnum
280             %m monthnum2d
281             %j dayofyear3d
282             %f dayofmonthnozero
283             %k ampmhour
284             %M min2d
285             %P ampmuc
286             %p ampmlc
287             %s epochsec
288             %S sec2d
289             %w weekdaynum
290             %y yeartwodigit
291             %T %H:%M:%S
292             %n newline
293             %t tab
294             %% percent
295             ];
296              
297             $PCodes{'%c'} = '{weekdayshort} %h %d {hour2d}:{min2d}:{sec2d} %Y';
298             $PCodes{'%r'} = '%k:%M:%S %P';
299              
300             #
301             # globals
302             #------------------------------------------------------------------------------
303              
304              
305             #------------------------------------------------------------------------------
306             # TIEHASH
307             #
308             sub TIEHASH {
309 40     40   67 my ($class, $time, %opts) = @_;
310 40         76 my $eztied = bless ({}, $class);
311            
312             # TESTING
313             # println subname(); ##i
314            
315             # set some non-date properties
316             # $eztied->{'zero_hour_ampm'} = defined($opts{'zero_hour_ampm'}) ? $opts{'zero_hour_ampm'} : 1;
317 40         92 $eztied->{'formats'} = {};
318 40         85 $eztied->{'settings'} = {'dst_kludge' => 1};
319            
320             # default builtin formats
321 40         94 $eztied->set_format('fullday', '{month short} {day of month no zero}, {year}');
322 40         68 $eztied->set_format('fulldate', '{fullday}');
323 40         71 $eztied->set_format('dayandtime', '{month short} {day of month}, {year} {ampmhour no zero}:{minute}{ampm}');
324 40         73 $eztied->set_format('default', '{full}');
325            
326             # if clone
327 40 100       190 if (UNIVERSAL::isa $time, 'Date::EzDate2'){
328 16         43 $eztied->clone($time);
329             }
330            
331             # else
332             else {
333             # set DateTime object to current time
334 24   33     129 $eztied->{'dt'} = DateTime->now(
335             time_zone =>
336             $Date::EzDate::default_time_zone ||
337             DateTime::TimeZone->new( name => 'local' )->name(),
338             );
339            
340             # if time is a scalar and has content, call timefromstring()
341 24 50 66     19761 if ( defined($time) && (! ref $time) && ($time =~ m|\S|s) ) {
      66        
342 23         68 $eztied->timefromstring($time);
343             }
344             }
345            
346             # return
347 40         145 return $eztied;
348             }
349             #
350             # TIEHASH
351             #------------------------------------------------------------------------------
352              
353              
354             #------------------------------------------------------------------------------
355             # clone
356             #
357             sub clone {
358 16     16   21 my ($new, $old) = @_;
359            
360             # TESTING
361             # println subname(), ' - ', __PACKAGE__; ##i
362            
363             # ensure we're using the tied hash
364 16 50       50 if ( UNIVERSAL::isa $old, 'Date::EzDate2' )
365 16         64 { $old = $old->tie_ob() }
366            
367             # formats and settings
368 16         446 $new->{'formats'} = Clone::clone($old->{'formats'});
369 16         120 $new->{'settings'} = Clone::clone($old->{'settings'});
370            
371             # datetime object
372 16         74 $new->{'dt'} = $old->{'dt'}->clone();
373            
374             # return
375 16         149 return $new;
376             }
377             #
378             # clone
379             #------------------------------------------------------------------------------
380              
381              
382              
383             #------------------------------------------------------------------------------
384             # STORE
385             #
386             our $left_brace_rx = quotemeta('{');
387              
388             sub STORE {
389 98     98   685 my ($eztied, $key, $val) = @_;
390 98         80 my (%set, $orgkey, $orgval, $dt);
391            
392             # TESTING
393             # println subname(); ##i
394            
395             # hold on to original values
396 98         91 $orgkey = $key;
397 98         72 $orgval = $val;
398            
399             # error checking
400 98 50       185 if (! defined $val) {
401 0         0 return $eztied->warn(
402             'value-not-defined-in-store: ' .
403             'Must send a defined value when setting a ' .
404             'property of an EzDate object'
405             );
406             }
407            
408             # if value contains {, assume they're assigning a format
409 98 50       324 $val =~ m|$left_brace_rx| and return $eztied->set_format($key, $val);
410            
411             # normalize key
412 98         152 normalize_key($key);
413 98         169 $key = $eztied->get_alias($key, 'strip_no_zero'=>1);
414            
415             # TESTING
416             # showvar $key;
417            
418             # normalize value
419 98         99 $val = lc($val);
420 98         204 $val =~ s|^\s+||gs;
421 98         134 $val =~ s|\s+$||gs;
422 98         83 $val =~ s|\s+| |gs;
423            
424             # TESTING
425             # println $key, ': ', $val;
426            
427             # get DateTime object
428 98         107 $dt = $eztied->{'dt'};
429            
430             # day of month
431 98 100 100     1146 if ($key eq 'dayofmonth') {
    100 100        
    100 100        
    100 66        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    50 100        
432 17         30 $set{'day'} = $val;
433             }
434            
435             # year
436             elsif ($key eq 'year') {
437 17         33 $set{'year'} = $val;
438             }
439            
440             # month
441             elsif ( ($key eq 'month') || ($key eq 'monthnum') || ($key eq 'monthlong') || ($key eq 'monthshort') ) {
442             # letters
443 21 100       51 unless ( $val =~ m|^\d+$|si ) {
444             # get just first three letters
445 18         24 $val =~ s|\s||gs;
446 18         35 $val = substr($val, 0, 3);
447            
448             # if a month number exists, use it, else throw error
449             # KLUDGE: assuming English names for now, will work with
450             # DateTime's locale functions later.
451 18 50       35 if (my $num = $MonthNums{$val}) {
452 18         30 $val = $num;
453             }
454             else {
455 0         0 return $eztied->warn(
456             "invalid-month-name: do not know this month name: $orgval"
457             )
458             }
459             }
460            
461             # set month
462 21         32 $set{'month'} = $val;
463             }
464            
465             # weekday
466             elsif ( ($key eq 'weekday') || ($key eq 'weekdaynum') || ($key eq 'weekdayshort') || ($key eq 'weekdaylong') ) {
467             # if not integer, get integer value
468 6 100       23 unless ($val =~ m|^\d+$|s) {
469             # get just first three letters
470 5         9 $val = lc($val);
471 5         10 $val =~ s|\s||gs;
472 5         10 $val = substr($val, 0, 3);
473            
474             # attempt to get value
475 5 50       17 unless ($val = $WeekDayNums{$val}) {
476 0         0 return $eztied->warn(
477             "invalid-weekday-name: do not know this weekday name: $orgval"
478             )
479             }
480             }
481            
482             # set day
483 6 100       22 unless ( $val == $dt->day_of_week ) {
484 5         30 $set{'day'} = $dt->day + $val - $dt->day_of_week;
485             }
486             }
487            
488             # yearday
489             elsif ( ($key eq 'yearday') || ($key eq 'yearday3d') ) {
490 3         5 my ($current);
491            
492             # check for integer and range
493 3 50       8 $eztied->integer_check($key, $val)
494             or return 0;
495            
496             # get current value
497 3         11 $current = $dt->day_of_year();
498            
499             # if they're not equal, set to new day of year
500 3 50       14 unless ($current == $val) {
501             # add days
502 3         10 $dt->add(
503             days => $val - $current,
504             );
505            
506             # return
507 3         1618 return 1;
508             }
509             }
510            
511             # hour
512             elsif ( ($key eq 'hour') || ($key eq 'hour2d') ) {
513             # check for integer and range
514 3 50       8 $eztied->integer_check($key, $val)
515             or return 0;
516            
517             # assign value
518 3         7 $set{'hour'} = $val;
519             }
520            
521             # minute
522             elsif ( ($key eq 'min') || ($key eq 'min2d') ) {
523             # check for integer
524 3 50       6 $eztied->integer_check($key, $val)
525             or return 0;
526            
527             # assign value
528 3         5 $set{'minute'} = $val;
529             }
530            
531             # second
532             elsif ( ($key eq 'sec') || ($key eq 'sec2d') ) {
533             # check for integer and range
534 3 50       7 $eztied->integer_check($key, $val)
535             or return 0;
536            
537             # assign value
538 3         7 $set{'second'} = $val;
539             }
540            
541             # ampm
542             elsif ( ($key eq 'ampm') || ($key eq 'ampmlc') || ($key eq 'ampmuc') ) {
543 20         22 my ($current);
544            
545             # add 'm' to just or or p
546 20 100       43 if (length($val) == 1)
547 1         2 {$val .= 'm'}
548            
549             # normalize
550 20         35 $val =~ s|\s||gs;
551            
552             # error checking
553 20 50 66     81 unless ( ($val eq 'am') || ($val eq 'pm') )
554 0         0 { return $eztied->warn('invalid-ampm: ampm may only be set to am or pm') }
555            
556             # get dt object
557 20         71 $current = $dt->hour;
558            
559             # if no change, we're done
560 20 100       104 if ($current < 12) {
561 16 100       40 if ($val eq 'am')
562 1         3 { return 1}
563             }
564             else {
565 4 50       11 if ($val eq 'pm')
566 0         0 { return 1}
567             }
568            
569             # set change
570 19 100       41 if ($val eq 'am')
571 4         6 { $set{'hour'} = $current - 12 }
572             else
573 15         26 { $set{'hour'} = $current + 12 }
574             }
575            
576             # ampmhour
577             elsif ($key eq 'ampmhour') {
578             # check for integer
579 2 50       6 $eztied->integer_check($key, $val)
580             or return 0;
581            
582             # add twelve if necessary
583 2 50       6 if ($dt->hour >= 12)
584 2         12 { $val += 12 }
585            
586             # set
587 2         6 $set{'hour'} = $val;
588             }
589            
590             # miltime
591             elsif ($key eq 'miltime') {
592             # must ve exactly four digits
593 2 50       9 unless ($val =~ m|^\d{4}$|s)
594 0         0 { return $eztied->warn('invalid-miltime: miltime must consist of exactly four digits') }
595            
596             # set
597 2         7 $set{'hour'} = substr($val, 0, 2);
598 2         5 $set{'minute'} = substr($val, 2, 2);
599             }
600            
601             # dmy
602             elsif ($key eq 'dmy') {
603 1         1 my (@tokens, $day, $month, $year);
604            
605             # split
606 1         2 $val = lc($val);
607 1         7 @tokens = split(m/(\d+)|([a-z]+)/si, $val);
608 1         2 @tokens = grep {defined $_} @tokens;
  8         10  
609 1         2 @tokens = grep {m|\S|s} @tokens;
  6         9  
610            
611             # get values
612 1         2 ($day, $month, $year) = @tokens;
613            
614             # check day
615 1 50       3 $eztied->integer_check("$key - day", $day)
616             or return 0;
617            
618             # normalize month
619 1         3 $month = substr($month, 0, 3);
620            
621             # if a month number exists, use it, else throw error
622             # KLUDGE: assuming English names for now, will work with
623             # DateTime's locale functions later.
624 1 50       4 if (my $num = $MonthNums{$month}) {
625 1         1 $month = $num;
626             }
627             else {
628 0         0 return $eztied->warn(
629             "invalid-month-name: do not know this month name: $orgval"
630             )
631             }
632            
633             # check year
634 1 50       3 $eztied->integer_check("$key - year", $year)
635             or return 0;
636            
637             # set
638 1         2 $set{'day'} = $day;
639 1         3 $set{'month'} = $month;
640 1         2 $set{'year'} = $year;
641             }
642            
643             # elsif ($key eq 'minofday') {
644             # $eztied->setfromtime (
645             # DST_ADJUST_NO,
646             # $eztied->{'epochsec'} - ($eztied->{'hour'} * t_60_60) - ($eztied->{'min'} * 60) + ($val * 60)
647             # )
648             # }
649             #
650             # elsif ($key eq 'hour') {
651             # $val = timelocal(
652             # $eztied->{'sec'},
653             # $eztied->{'min'},
654             # $val,
655             # $eztied->{'dayofmonth'},
656             # $eztied->{'monthnum'},
657             # $eztied->{'year'},
658             # );
659             #
660             # $eztied->setfromtime(DST_ADJUST_NO, $val);
661             # }
662             #
663             # # hour and minute
664             # elsif ( ($key eq 'clocktime') || ($key =~ m|^mil(itary)?time$|) ) {
665             # my ($changed, $hour, $min, $sec) = $eztied->gettime($val);
666             #
667             # unless (defined $hour)
668             # {$hour = $eztied->{'hour'}}
669             # unless (defined $min)
670             # {$min = $eztied->{'min'}}
671             # unless (defined $sec)
672             # {$sec = $eztied->{'sec'}}
673             #
674             # $eztied->setfromtime
675             # (
676             # 0,
677             # $eztied->{'epochsec'}
678             #
679             # - ($eztied->{'sec'})
680             # - ($eztied->{'min'} * 60)
681             # - ($eztied->{'hour'} * t_60_60)
682             #
683             # + ($sec)
684             # + ($min * 60)
685             # + ($hour * t_60_60)
686             # );
687             # }
688             #
689             # elsif ($key eq 'dst')
690             # {return $eztied->warn('dst property is read-only')}
691             #
692             # elsif ($key eq 'epochsec')
693             # {$eztied->setfromtime(DST_ADJUST_NO, $val)}
694             #
695             # elsif ($key eq 'epochmin')
696             # {$eztied->setfromtime(DST_ADJUST_NO, $eztied->{'epochsec'} - ($eztied->getepochmin * 60) + ($val * 60) )}
697             #
698             # elsif ($key eq 'epochhour')
699             # {$eztied->setfromtime(DST_ADJUST_NO, $eztied->{'epochsec'} - ($eztied->getepochhour * t_60_60) + ($val * t_60_60) )}
700             #
701             # elsif ($key eq 'epochday') {
702             # my ($oldhour, $oldepochsec, $oldmin);
703             #
704             # $eztied->setfromtime(
705             # DST_ADJUST_YES,
706             # $eztied->{'epochsec'} - ($eztied->getepochday * t_60_60_24) + (int($val) * t_60_60_24)
707             # );
708             # }
709             #
710             # # ordinals
711             # elsif ($key =~ m/dayofmonthord(word|num)?/) {
712             # # if numeric
713             # if ($val =~ s|^(\d+)\s*\w*$|$1|s)
714             # {$eztied->STORE('dayofmonth', $val)}
715             #
716             # # else word
717             # else {
718             # my $nval = $val;
719             # $nval =~ tr/A-Z/a-z/;
720             # $nval =~ s|\W||gs;
721             #
722             # # if no such ordinal exists
723             # unless ($nval = $OrdWordsNums{$nval})
724             # { return $eztied->warn("Invalid ordinal: $val") }
725             #
726             # $eztied->STORE('dayofmonth', $nval);
727             # }
728             # }
729             #
730             # elsif ($key eq 'year') {
731             # my ($maxday, $targetday);
732             #
733             # # if same year, nothing to do
734             # if ($eztied->{'year'} == $val)
735             # {return}
736             #
737             # # make sure day of month isn't greater than maximum day of target month
738             # $maxday = daysinmonth($eztied->{'monthnum'}, $val);
739             #
740             # if ($eztied->{'dayofmonth'} > $maxday) {
741             # $eztied->warn(
742             # "Changing the year sets day of month ($eztied->{'dayofmonth'}) to higher than days in month ($maxday); ",
743             # "setting the day down to $maxday"
744             # );
745             # $targetday = $maxday;
746             # }
747             # else
748             # {$targetday = $eztied->{'dayofmonth'}}
749             #
750             # $val = timelocal($eztied->{'sec'}, $eztied->{'min'}, $eztied->{'hour'}, $targetday, $eztied->{'monthnum'}, $val);
751             # $eztied->setfromtime(DST_ADJUST_YES, $val);
752             # }
753             #
754             # elsif ($key =~ m/^year(two|2)digit/) {
755             # $val =~ s|^.*(..)$|$1|;
756             # $eztied->STORE('year', substr($eztied->{'year'}, 0, 2) . zeropad_2($val));
757             # }
758             #
759             # elsif ($key =~ m/^monthnumbase(one|1)/)
760             # {$eztied->STORE('monthnum', $val - 1)}
761             #
762             #
763             # #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
764             # # monthnum
765             # #
766             # elsif ($key eq 'monthnum') {
767             # my ($target, $epoch_second);
768             # my $month = $eztied->{'monthnum'};
769             # my $year = $eztied->{'year'};
770             # my $orgday = $eztied->{'dayofmonth'};
771             # my $dayofmonth = $eztied->{'dayofmonth'};
772             # my $jumps = $val - $month;
773             #
774             # # if nothing to do
775             # $jumps or return;
776             #
777             # $target = $jumps;
778             # $target = abs($target);
779             #
780             # # jumping forward
781             # if ($jumps > 0) {
782             # foreach (1..$target) {
783             # # if end of year
784             # if ($month == 11) {
785             # $month = 0;
786             # $year++;
787             # }
788             # else
789             # {$month++}
790             # }
791             # }
792             #
793             # # jumping backward
794             # else {
795             # foreach (1..$target) {
796             # # if beginning of year
797             # if ($month == 0) {
798             # $month = 11;
799             # $year--;
800             # }
801             # else
802             # {$month--}
803             # }
804             # }
805             #
806             #
807             # # adjust day for shorter month (if necessary)
808             # if ($dayofmonth > 28) {
809             # my $dim = daysinmonth($month, $year);
810             #
811             # if ($dim < $dayofmonth)
812             # { $dayofmonth = $dim }
813             # }
814             #
815             # # get epoch second from timelocal
816             # $epoch_second = timelocal($eztied->{'sec'}, $eztied->{'min'}, $eztied->{'hour'}, $dayofmonth, $month, $year);
817             # $eztied->setfromtime(DST_ADJUST_NO, $epoch_second);
818             # }
819             # #
820             # # monthnum
821             # #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
822             #
823             #
824             # elsif ( ($key eq 'monthshort') || ($key eq 'monthlong') )
825             # {$eztied->STORE('monthnum', $MonthNums{lc(substr($val, 0, 3))})}
826             #
827             # elsif (
828             # ($key eq 'weekdayshort') ||
829             # ($key eq 'weekdaylong') ||
830             # ($key eq 'dayofweekshort') ||
831             # ($key eq 'dayofweeklong')
832             # ) {
833             # $eztied->STORE(
834             # 'weekdaynum',
835             # $WeekDayNums{lc(substr($val, 0, 3))})
836             # }
837             #
838             # else
839             # { return $eztied->warn("Do not understand key: $orgkey") }
840            
841             # else don't know key
842             else {
843             # TESTING
844             # die "unknown-name-for-store: do not know this property name: $orgkey";
845            
846             # return with warning
847 0         0 return $eztied->warn(
848             "unknown-name-for-store: do not know this property name: $orgkey"
849             )
850             }
851            
852             # set new date and time
853 94         184 $eztied->new_date_time(\%set);
854            
855             # return value
856 94         390 return $val;
857             }
858             #
859             # STORE
860             #------------------------------------------------------------------------------
861              
862              
863             #------------------------------------------------------------------------------
864             # integer_check
865             #
866             sub integer_check {
867 16     16   21 my ($eztied, $key, $val) = @_;
868            
869             # TESTING
870             # println subname(); ##i
871            
872             # must be all digit and greater than zero
873 16 50 33     127 unless ( defined($val) && ($val =~ m|^\d+$|s) && ($val > 0) ) {
      33        
874             # warn
875 0 0       0 $eztied->warn(
876             "not-integer-for-$key: " .
877             "the value for $key must be a positive integer but is " .
878             (defined($val) ? "\"$val\"" : 'undef') . ' ' .
879             "instead"
880             );
881            
882             # return false
883 0         0 return 0;
884             }
885            
886             # return true
887 16         51 return 1;
888             }
889             #
890             # integer_check
891             #------------------------------------------------------------------------------
892              
893              
894              
895             #------------------------------------------------------------------------------
896             # timefromstring
897             #
898              
899             # today, now, yesterday, tomorrow
900             our %relative = (
901             today => 1,
902             now => 1,
903             yesterday => 1,
904             tomorrow => 1,
905             );
906              
907             # map time units to duractions
908             #my %duration_map = (
909             # year => 'years',
910             # month => 'months',
911             # day => 'days',
912             # hour => 'hours',
913             # minute => 'minutes',
914             # second => 'seconds',
915             #);
916              
917             sub timefromstring {
918 23     23   46 my ($eztied, $val, %opts) = @_;
919            
920             # TESTING
921             # println subname(); ##i
922            
923             # error checking
924 23 50       44 if (! defined $val)
925 0         0 { return $eztied->warn('time-string-not-defined: did not get a defined time string') }
926            
927             # alias hour am/pm to hour:00 am/pm
928             # $eztied->{'zero_hour_ampm'} and $val =~ s/(^|[^:\d])(\d+)\s*([ap]m)/$1$2:00:00 $3/gis;
929             # $eztied->{'zero_hour_ampm'} and $val =~ s/(^|[^:\d])(\d+)\s*([ap]m?\b)/$1$2:00:00 $3/gis;
930            
931             # normalize
932             # $val = lc($val);
933 23         51 $val =~ s|^\s+||s;
934 23         57 $val =~ s|\s+$||s;
935 23         63 $val =~ s|\s+| |s;
936            
937             # if just an integer
938 23 50       118 if ($val =~ m/^\d+$/i) {
    50          
939 0         0 my ($year, $month, $day, $hour, $min, $sec, $doy, $dow, $dst) = Gmtime($val);
940            
941             # set from gmtime
942 0         0 $eztied->{'year'} = $year;
943 0         0 $eztied->{'monthnum'} = $month;
944 0         0 $eztied->{'dayofmonth'} = $day;
945 0         0 $eztied->{'hour'} = $hour;
946 0         0 $eztied->{'min'} = $min;
947 0         0 $eztied->{'sec'} = $sec;
948             }
949            
950             # elsif today, now, yesterday, tomorrow
951             elsif ($relative{lc $val}) {
952             # lowercase
953 0         0 $val = lc($val);
954            
955             # today
956 0 0 0     0 if ( ($val eq 'today') || ($val eq 'now') ) {
957 0         0 $eztied->setfromtime();
958             }
959            
960             # tomorrow
961 0 0       0 if ($val eq 'tomorrow') {
962 0         0 $eztied->setfromtime();
963            
964             @$eztied{qw{year monthnum dayofmonth}} =
965 0         0 Add_Delta_Days(@$eztied{qw{year monthnum dayofmonth}}, 1);
966             }
967            
968             # yesterday
969 0 0       0 if ($val eq 'yesterday') {
970 0         0 $eztied->setfromtime();
971            
972             @$eztied{qw{year monthnum dayofmonth}} =
973 0         0 Add_Delta_Days(@$eztied{qw{year monthnum dayofmonth}}, -1);
974             }
975             }
976            
977             # else a date string
978             else {
979 23         26 my (%set);
980            
981             # TESTING
982             # println 'date string'; ##i
983             # showvar $val;
984            
985             # special case: ##:##.#####
986             # In some time formats, the hour, min, second is
987             # followed by fractional seconds. We don't handle those
988             # fractions, so we'll just remove them.
989 23         80 $val =~ s/(\d+\:\d+)\.[\d\-]+/$1/g;
990            
991             # Another special case: A.M. to AM and P.M. to PM
992 23         41 $val =~ s/a\.m\b/am/gis;
993 23         39 $val =~ s/p\.m\b/pm/gis;
994            
995             # remove time zone if it exists
996 23 50       56 if ( $val =~ s|[a-z_]+\s*/\s*[a-z_]+$||si ) {
997 0         0 $set{'time_zone'} = $&;
998 0         0 $val =~ s|\s+$||s;
999             }
1000            
1001             # normalize
1002 23         26 $val = lc($val);
1003 23         98 $val =~ s/[^\w:]/ /g;
1004 23         97 $val =~ s/\s*:\s*/:/g;
1005            
1006             # change ordinals to numbers
1007 23         222 $val =~ s|$OrdWordsRx|$OrdWordsNums{$1}|gis;
1008 23         73 $val =~ s/(\d)(th|rd|st|nd)\b/$1/gis;
1009            
1010             # noon to 12:00:00
1011             # midnight to 00:00:00
1012 23         56 $val =~ s/\bnoon\b/ 12:00:00 /gis;
1013 23         45 $val =~ s/\bmidnight\b/ 00:00:00 /gis;
1014            
1015             # normalize some more
1016 23         58 $val =~ s/(\d)([a-z])/$1 $2/g;
1017 23         51 $val =~ s/([a-z])(\d)/$1 $2/g;
1018 23         82 $val =~ s/\s+/ /g;
1019 23         45 $val =~ s/^\s*//;
1020 23         102 $val =~ s/\s*$//;
1021 23         75 $val =~ s/([a-z]{3})[a-z]+/$1/gs;
1022            
1023             # remove weekday
1024             # TOD0: use localized names of weekdays
1025 23         145 $val =~ s/((sun)|(mon)|(tue)|(wed)|(thu)|(fri)|(sat))\s*//;
1026 23         107 $val =~ s/\s*$//;
1027            
1028             # attempt to get time
1029 23 50       53 unless ($opts{'dateonly'}) {
1030 23         62 ($val, @set{qw{hour minute second}}) = $eztied->gettime($val, 'skipjustdigits'=>1);
1031             }
1032            
1033             # attempt to get date
1034 23 50       53 unless ($opts{'timeonly'}) {
1035 23 50       46 if (length $val) {
1036 23         44 ($val, @set{qw{day month year}}) = getdate($val);
1037             }
1038             }
1039            
1040             # attempt to get time again
1041 23 50       59 unless ($opts{'dateonly'}) {
1042 23 100 66     94 if (length($val) && (! defined($set{'hour'})) ) {
1043 22         21 my ($hour_new, $min_new, $sec_new);
1044            
1045             # parse again
1046 22         42 ($val, $hour_new, $min_new, $sec_new) = $eztied->gettime($val, 'skipjustdigits'=>1, 'croakonfail'=>1);
1047            
1048             # set from new valued if they are defined
1049 22 50       57 defined($hour_new) and $set{'hour'} = $hour_new;
1050 22 50       43 defined($min_new) and $set{'minute'} = $min_new;
1051 22 50       42 defined($sec_new) and $set{'second'} = $sec_new;
1052             }
1053             }
1054            
1055             # is somehow we don't still have a defined string, return
1056 23 50       39 if (! defined $val)
1057 0         0 { return 0 }
1058            
1059             # trim
1060 23         51 $val =~ s/^\s*//;
1061            
1062             # create new datetime object
1063 23 50       47 if (%set) {
1064 23         50 $eztied->new_date_time(\%set);
1065             }
1066             }
1067            
1068             # return
1069 23         52 return 1;
1070             }
1071             #
1072             # timefromstring
1073             #------------------------------------------------------------------------------
1074              
1075              
1076             #------------------------------------------------------------------------------
1077             # new_date_time
1078             #
1079             sub new_date_time {
1080 117     117   115 my ($eztied, $set) = @_;
1081 117         86 my ($old, $new, %offset);
1082            
1083             # TESTING
1084             # println subname(); ##i
1085            
1086             # old datetime object
1087 117         114 $old = $eztied->{'dt'};
1088            
1089             # create new datetime object with just year
1090             $new = DateTime->new(
1091             year => coal($set->{'year'}, $old->year),
1092 117   33     328 time_zone => $set->{'time_zone'} || $old->time_zone,
1093             );
1094            
1095             # delete year
1096 117         26374 delete $set->{'year'};
1097            
1098             # build duration
1099 117         260 foreach my $key (keys %$set) {
1100 194 100       304 if (defined $set->{$key}) {
1101 191         356 $offset{$key . 's'} = $set->{$key};
1102             }
1103             }
1104            
1105             # decrement months and days
1106 117         148 foreach my $key (qw{months days}) {
1107 234 100       402 if (defined $offset{$key})
1108 91         119 { $offset{$key}-- }
1109             }
1110            
1111             # add offset to new datetime object
1112             $new->add(
1113             months => coal($offset{'months'}, $old->month - 1 ),
1114             days => coal($offset{'days'}, $old->day - 1 ),
1115             hours => coal($offset{'hours'}, $old->hour ),
1116             minutes => coal($offset{'minutes'}, $old->minute ),
1117 117         323 seconds => coal($offset{'seconds'}, $old->second ),
1118             );
1119            
1120             # set new dattime object
1121 117         71525 $eztied->{'dt'} = $new;
1122             }
1123             #
1124             # new_date_time
1125             #------------------------------------------------------------------------------
1126              
1127              
1128             #------------------------------------------------------------------------------
1129             # coal
1130             # short for coalesce
1131             #
1132             sub coal {
1133 702     702   2177 my ($new, $old) = @_;
1134 702 100       1973 return defined($new) ? $new : $old;
1135             }
1136             #
1137             # coal
1138             #------------------------------------------------------------------------------
1139              
1140              
1141             #------------------------------------------------------------------------------
1142             # getdate
1143             # attempt to get date
1144             # supported date formats
1145             # 14 Jan 2001
1146             # 14 JAN 01
1147             # 14JAN2001
1148             # Jan 14, 2001
1149             # Jan 14, 01
1150             # 01-14-01
1151             # 1-14-01
1152             # 1-7-01
1153             # 01-14-2001
1154             #
1155             sub getdate {
1156 23     23   22 my ($val) = @_;
1157 23         26 my ($day, $month, $year);
1158            
1159             # TESTING
1160             # println subname(); ##i
1161            
1162             # Tue Jun 12 13:03:28 2012
1163 23 50       186 if ($val =~ s/^([a-z]+) (\d+) (\S+) (\d+)$/$3/) {
    100          
    50          
    0          
    0          
    0          
1164 0         0 $year = $4;
1165 0         0 $month = $MonthNums{$1};
1166 0         0 $day = $2;
1167             }
1168            
1169             # 14 Jan 2001
1170             # 14 JAN 01
1171             # 14JAN2001 # will be normalized to have spaces
1172             elsif ($val =~ m/^\d+ [a-z]+ \d{4}$/) {
1173 1         4 my @tokens = split(' ', $val);
1174 1         3 $day = $tokens[0];
1175 1         2 $month = $MonthNums{$tokens[1]};
1176 1         2 $year = $tokens[2];
1177 1         3 $val = '';
1178             }
1179            
1180             # Jan 14, 2001
1181             # Jan 14, 01
1182             elsif ($val =~ s/^([a-z]+) (\d+) (\d+)//) {
1183 22         57 $month = $MonthNums{$1};
1184 22         34 $day = $2;
1185 22         32 $year = $3;
1186             }
1187            
1188             # Jan 2001
1189             # Jan 01
1190             elsif ($val =~ s/^([a-z]+) (\d+)//) {
1191 0         0 $month = $MonthNums{$1};
1192 0         0 $year = $2;
1193             }
1194            
1195             # 2001-01-14
1196             elsif ($val =~ s/^(\d{4}) (\d+) (\d+)//) {
1197 0         0 $year = $1;
1198 0         0 $month = $2 - 1;
1199 0         0 $day = $3;
1200             }
1201            
1202             # 01-14-01
1203             # 1-14-01
1204             # 1-7-01
1205             # 01-14-2001
1206             elsif ($val =~ s/^(\d+) (\d+) (\d+)//) {
1207 0         0 $month = $1 - 1;
1208 0         0 $day = $2;
1209 0         0 $year = $3;
1210             }
1211            
1212             # return
1213 23         94 return ($val, $day, $month, $year);
1214             }
1215             #
1216             # getdate
1217             #------------------------------------------------------------------------------
1218              
1219              
1220             #------------------------------------------------------------------------------
1221             # gettime
1222             # supported time formats:
1223             # 5pm
1224             # 5:34 pm
1225             # 17:34
1226             # 17:34:13
1227             # 5:34:13
1228             # 5:34:13 pm
1229             # 2330 (military time)
1230             #
1231             sub gettime {
1232 45     45   95 my ($eztied, $str, %opts)= @_;
1233 45         35 my ($hour, $min, $sec);
1234            
1235             # TESTING
1236             # println subname(); ##i
1237            
1238             # string must be defined
1239 45 50       80 unless (defined $str) {
1240 0         0 croak 'strin-not-defined: $str is not defined';
1241             }
1242            
1243             # clean up a little
1244 45         68 $str =~ s/^\s+//;
1245 45         83 $str =~ s/\s+$//;
1246 45         41 $str =~ s/^://;
1247 45         38 $str =~ s/:$//;
1248 45         103 $str =~ s/(\d)(am|pm)/$1 $2/;
1249            
1250             # 5:34:13 pm
1251             # 5:34:13 p
1252 45 100 33     343 if ($str =~ s/^(\d+):(\d+):(\d+) (a|p)(m|\b)\s*//) {
    100          
    50          
    50          
    50          
    50          
    50          
1253 19         36 $hour = ampmhour($1, $4);
1254 19         23 $min = $2;
1255 19         21 $sec = $3;
1256             }
1257            
1258             # 17:34:13
1259             elsif ($str =~ s/^(\d+):(\d+):(\d+)\s*//) {
1260 3         6 $hour = $1;
1261 3         2 $min = $2;
1262 3         3 $sec = $3;
1263             }
1264            
1265             # 5:34 pm
1266             elsif ($str =~ s/^(\d+):(\d+) (a|p)m?\s*//) {
1267 0         0 $hour = ampmhour($1, $3);
1268 0         0 $min = $2;
1269             }
1270            
1271             # 17:34
1272             elsif ($str =~ s/^(\d+):(\d+)\s*//) {
1273 0         0 $hour = $1;
1274 0         0 $min = $2;
1275             }
1276            
1277             # 5 pm
1278             elsif ($str =~ s/^(\d+) (a|p)m?\b\s*//) {
1279 0         0 $hour = ampmhour($1, $2);
1280 0         0 $min = 0;
1281 0         0 $sec = 0;
1282             }
1283            
1284             # elsif just digits
1285             elsif ( (! $opts{'skipjustdigits'}) && ($str =~ m/^\d+$/) ) {
1286 0         0 $str = zeropad_open($str, 4);
1287 0         0 $hour = substr($str, 0, 2);
1288 0         0 $min = substr($str, 2, 2);
1289             }
1290            
1291             # else don't recognize format
1292             elsif ($opts{'croakonfail'}) {
1293 0         0 return $eztied->warn("unrecognized-format: don't recognize time format: $str");
1294             }
1295            
1296             # return
1297 45         173 return ($str, $hour, $min, $sec);
1298             }
1299             #
1300             # gettime
1301             #------------------------------------------------------------------------------
1302              
1303              
1304             #------------------------------------------------------------------------------
1305             # ampmhour
1306             #
1307             sub ampmhour {
1308 19     19   43 my ($hour, $ampm) = @_;
1309            
1310             # if 12
1311 19 100       72 if ($hour == 12) {
    100          
1312             # if am, set to 0
1313 3 50       10 if ($ampm =~ m/^a/)
1314 0         0 {$hour = 0}
1315             }
1316            
1317             # else if pm, add 12
1318             elsif ($ampm =~ m/^p/) {
1319 5         6 $hour += 12;
1320             }
1321            
1322             # return
1323 19         28 return $hour;
1324             }
1325             #
1326             # ampmhour
1327             #------------------------------------------------------------------------------
1328              
1329              
1330             #------------------------------------------------------------------------------
1331             # set_format
1332             #
1333             sub set_format {
1334 161     161   161 my ($eztied, $name, $format) = @_;
1335            
1336             # normalize name
1337 161         179 $name =~ s|\s||g;
1338 161         1392 $name =~ lc($name);
1339            
1340 161         225 $eztied->{'formats'}->{$name} = format_split($format);
1341             }
1342              
1343             sub format_split {
1344             # split
1345 223     223   1787 my @rv = split(m/(\{[^\{\}]*\}|\%.)/, $_[0]);
1346            
1347             # normalize
1348 223         270 foreach my $el (@rv) {
1349 1417 100       2283 if ($el =~ m|^\{.*\}$|s)
1350 558         592 { normalize_key($el) }
1351             }
1352            
1353             # remove empties
1354 223         246 @rv = grep {length $_} @rv;
  1417         1437  
1355            
1356             # return
1357 223         462 return \@rv;
1358             }
1359             #
1360             # set_format
1361             #------------------------------------------------------------------------------
1362              
1363              
1364             #------------------------------------------------------------------------------
1365             # setfromtime
1366             #
1367             sub setfromtime {
1368 0     0   0 my ($eztied, $time) = @_;
1369            
1370             # TESTING
1371             # println subname(); ##i
1372            
1373             # set time fields
1374 0         0 @$eztied{@ltimefields} = localtime();
1375            
1376             # add 1900 to year
1377 0         0 $eztied->{'year'} += 1900;
1378            
1379             # increment monthnum, which was base zero, to base one
1380 0         0 $eztied->{'monthnum'}++;
1381             }
1382             #
1383             # setfromtime
1384             #------------------------------------------------------------------------------
1385              
1386              
1387             #------------------------------------------------------------------------------
1388             # normalize_key
1389             #
1390             sub normalize_key {
1391 1581     1581   1914 $_[0] =~ s|\s||gs;
1392 1581 100       2713 $_[0] =~ tr/A-Z/a-z/ unless $_[0] =~ m|^\%\w$|;
1393 1581         1441 $_[0] =~ s|ordinal|ord|sg;
1394            
1395 1581         1093 $_[0] =~ s|hours|hour|sg;
1396            
1397 1581         1192 $_[0] =~ s|minute|min|sg;
1398 1581         1161 $_[0] =~ s|mins|min|sg;
1399            
1400 1581         1073 $_[0] =~ s|second|sec|sg;
1401 1581         1127 $_[0] =~ s|secs|sec|sg;
1402            
1403 1581         1164 $_[0] =~ s/two/2/gs;
1404 1581         1130 $_[0] =~ s/three/3/gs;
1405 1581         1327 $_[0] =~ s/digits?/d/gs;
1406            
1407 1581         982 $_[0] =~ s/timezone/tz/gs;
1408            
1409 1581         1412 $_[0] =~ s|number|num|sg;
1410             }
1411             #
1412             # normalize_key
1413             #------------------------------------------------------------------------------
1414              
1415              
1416              
1417              
1418             #------------------------------------------------------------------------------
1419             # zeropad_open, zeropad_2
1420             #
1421             sub zeropad_open {
1422 30     30   87 my ($rv, $length) = @_;
1423 30   50     48 $length ||= 2;
1424             # return ('0' x ($length - length($rv))) . $rv;
1425 30         140 return sprintf "%0${length}d", $rv;
1426             }
1427              
1428             sub zeropad_2 {
1429 456     456   1490 my ($val) = @_;
1430            
1431             # $val must be defined
1432 456 50       573 if (! defined $val) {
1433 0         0 croak 'zeropad_2~val-not-defined: the value sent to zeropad_2 is not defined';
1434             }
1435            
1436             # return
1437 456         1729 return sprintf "%02d", $_[0];
1438             }
1439             #
1440             # zeropad_open, zeropad_2
1441             #------------------------------------------------------------------------------
1442              
1443              
1444             #------------------------------------------------------------------------------
1445             # month_short
1446             #
1447             #sub month_short {
1448             # my ($eztied) = @_;
1449             # my ($rv);
1450             #
1451             # # get name of month, return just the first three letters
1452             # $rv = Month_to_Text($eztied->{'monthnum'});
1453             # $rv = substr($rv, 0, 3);
1454             #
1455             # # return
1456             # return $rv;
1457             #}
1458             #
1459             # month_short
1460             #------------------------------------------------------------------------------
1461              
1462              
1463             #------------------------------------------------------------------------------
1464             # FETCH
1465             #
1466             sub FETCH {
1467 1268     1268   19118 my ($eztied, $key, %opts) = @_;
1468 1268         796 my ($orgkey, $dt);
1469            
1470             # TESTING
1471             # println subname(); ##i
1472            
1473             # hold on to original key
1474 1268         890 $orgkey = $key;
1475            
1476             # get key from aliases if necessary
1477 1268         1287 $key = $eztied->get_alias($key);
1478            
1479            
1480             #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1481             # nested properties
1482             #
1483 1268 100 100     4618 if ( (! ref $key) && ($key =~ m|[\{\%]|) && ($key !~ m|$pcode|o) )
      66        
1484 62         79 { $key = format_split($key) }
1485            
1486 1268 100       1626 if (ref $key) {
1487 69         157 my @rv = @$key;
1488            
1489 69         70 foreach my $el (@rv) {
1490             # if this is one of the format elements
1491             # then fetch the value of the given key
1492 480 100 100     2061 if (
1493             ($el =~ s|\{([^\}]+)\}|$1|) || # if it is enclosed in {}
1494             ($el =~ m|$pcode|o) # if it is a %x code
1495             ) {
1496 274         294 $el =~ s|['"\s]||g;
1497 274         334 $el = $eztied->FETCH($el, normalized=>1);
1498             }
1499             }
1500            
1501             # ensure defined values in @rv
1502 69         192 foreach my $val (@rv) {
1503 480 50       534 if (! defined $val)
1504 0         0 { $val = '' }
1505             }
1506            
1507             # return
1508 69         322 return join('', @rv);
1509             }
1510             #
1511             # nested properties
1512             #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1513            
1514            
1515             # clean up key
1516 1199 100       1931 $opts{'normalized'} or normalize_key($key);
1517            
1518             # datetime object
1519 1199         1074 $dt = $eztied->{'dt'};
1520            
1521             # datetime object
1522 1199 50 33     3181 if ( ($key eq 'dt') || $key eq 'datetime' ) {
1523 0         0 return $dt;
1524             }
1525            
1526             # already or mostly calculated
1527             #if (exists $eztied->{$key}) {
1528             # # ensure two digits for several properties
1529             # if ( $key =~ m/^(dayofmonth|monthnum|hour|min|sec)$/ )
1530             # { return zeropad_2($eztied->{$key}) }
1531             #
1532             # # all other properties, return key as-is
1533             # return $eztied->{$key};
1534             #}
1535            
1536             # character codes
1537 1199 100       1445 $key eq 'newline' and return "\n";
1538 1184 100       1323 $key eq 'tab' and return "\t";
1539 1169 50       1308 $key eq 'leftbrace' and return '{';
1540 1169 50       1269 $key eq 'lb' and return '{';
1541 1169 50       1254 $key eq 'rightbrace' and return '}';
1542 1169 50       1291 $key eq 'rb' and return '}';
1543 1169 100       1330 $key eq 'percent' and return '%';
1544            
1545             # nozero's
1546 1154 100       1662 if ($key =~ s/no(zero|0)//)
1547 75         110 { return $eztied->FETCH($key) + 0 }
1548            
1549             # day of month
1550 1079 100       1208 if ($key eq 'dayofmonth')
1551 66         121 { return $dt->day }
1552            
1553             # day of month, two digits
1554 1013 100       1042 if ($key eq 'dayofmonth2d')
1555 30         56 { return zeropad_2($dt->day) }
1556            
1557             # day of month ord
1558 983 50       1119 if ($key =~ m|^dayofmonthord(word)?$|)
1559 0         0 { return $OrdWords[$eztied->{'dayofmonth'}] }
1560 983 50       1016 if ($key eq 'dayofmonthordnum')
1561 0         0 { return $OrdNums[$eztied->{'dayofmonth'}] }
1562            
1563             # weekdaynum
1564 983 100       1292 if ($key eq 'weekdaynum')
1565 30         81 { return $dt->day_of_week() }
1566            
1567             # weekdaynum2d
1568 953 50       1002 if ($key eq 'weekdaynum2d')
1569 0         0 { return zeropad_2($dt->day_of_week()) }
1570            
1571             # weekday
1572 953 100       1163 if ($key =~ m/^(weekdayshort|dayofweekshort|dayofweek)$/)
1573 45         108 { return $dt->day_abbr() }
1574 908 100       1025 if ($key =~ m/^(weekdaylong|dayofweeklong)$/)
1575 31         80 { return $dt->day_name() }
1576            
1577             # month
1578 877 100       890 if ($key eq 'monthshort')
1579 47         104 { return $dt->month_abbr() }
1580 830 100 66     1995 if ( ($key eq 'month') || ($key eq 'monthlong') )
1581 16         48 { return $dt->month_name() }
1582            
1583             # month number
1584 814 100       949 if ($key eq 'monthnum')
1585 31         57 { return $dt->month }
1586 783 100       828 if ($key eq 'monthnum2d')
1587 45         87 { return zeropad_2($dt->month) }
1588            
1589             # day of year
1590 738 100       785 if ($key eq 'yearday')
1591 16         55 { return $dt->day_of_year() }
1592 722 100       884 if ($key eq 'yearday3d')
1593 30         55 { return zeropad_open($dt->day_of_year(), 3) }
1594            
1595             # year
1596 692 100       820 if ($key eq 'year')
1597 48         106 { return $dt->year }
1598 644 100       723 if ($key eq 'year2d')
1599 45         82 { return substr($dt->year, -2) }
1600            
1601             # epoch
1602 599 50 33     1475 if ( ($key eq 'epoch') || ($key eq 'epochsec') )
1603 0         0 { return Date_to_Time(@$eztied{qw{year monthnum dayofmonth hour min sec}}) }
1604            
1605             # leapyear
1606 599 100       824 if ($key =~ m/^(is)?leapyear/)
1607 30         73 { return $dt->is_leap_year() }
1608            
1609             # days in month
1610 569 100       671 if ($key eq 'daysinmonth') {
1611 30         78 return DateTime->last_day_of_month(
1612             year => $dt->year,
1613             month => $dt->month,
1614             time_zone => $dt->time_zone_long_name()
1615             )->day;
1616             }
1617            
1618             # DMY: eg 15JAN2001
1619 539 100       619 if ($key eq 'dmy') {
1620             return
1621 1         4 zeropad_2($dt->day) .
1622             uc($dt->month_abbr()) .
1623             $dt->year;
1624             }
1625            
1626             # full
1627 538 100       627 if ($key eq 'full') {
1628 11         7 my ($weekday);
1629            
1630             # return
1631             return
1632 11         29 $dt->day_abbr() . ' ' .
1633             $dt->month_abbr() . ' ' .
1634             $dt->day() . ', ' .
1635             $dt->year() . ' ' .
1636             zeropad_2($dt->hour) . ':' .
1637             zeropad_2($dt->minute) . ':' .
1638             zeropad_2($dt->second);
1639             }
1640            
1641             # military time, aka "miltime"
1642 527 100       721 if ($key =~ m|^mil(itary)?time$|)
1643 30         60 { return zeropad_2($dt->hour) . zeropad_2($dt->minute) }
1644            
1645             # iso8601
1646 497 50       646 if ($key eq 'iso8601') {
1647             return
1648             $eztied->{'year'} . '-' .
1649             zeropad_2($eztied->{'monthnum'}) . '-' .
1650             zeropad_2($eztied->{'dayofmonth'}) . ' ' .
1651            
1652             zeropad_2($eztied->{'hour'}) . ':' .
1653             zeropad_2($eztied->{'min'}) . ':' .
1654 0         0 zeropad_2($eztied->{'sec'});
1655             }
1656            
1657             # hour
1658 497 100       594 if ($key eq 'hour')
1659 15         34 { return $dt->hour }
1660            
1661             # hour two digits
1662 482 100       605 if ($key eq 'hour2d')
1663 60         128 { return zeropad_2($dt->hour) }
1664            
1665             # minute
1666 422 100       475 if ($key eq 'min')
1667 45         101 { return $dt->minute }
1668            
1669             # minute two digits
1670 377 100       468 if ($key eq 'min2d')
1671 75         151 { return zeropad_2($dt->minute) }
1672            
1673             # second
1674 302 100       342 if ($key eq 'sec')
1675 60         121 { return $dt->second }
1676            
1677             # second two digits
1678 242 100       324 if ($key eq 'sec2d')
1679 75         145 { return zeropad_2($dt->second) }
1680            
1681             # variable
1682 167         111 my ($ampm);
1683            
1684             # calculate ampm, which is needed in most results from here down
1685 167 100       320 $ampm = ($dt->hour >= 12) ? 'pm' : 'am';
1686            
1687             # am/pm
1688 167 100 100     974 if ( ($key eq 'ampm') || ($key eq 'ampmlc') )
1689 45         132 { return $ampm }
1690            
1691             # AM/PM uppercase
1692 122 100       168 if ($key eq 'ampmuc')
1693 45         128 { return uc($ampm) }
1694            
1695             # variable
1696 77         49 my ($ampmhour);
1697            
1698             # calculate ampmhour, which is needed from here down
1699 77 100 66     108 if ( ($dt->hour == 0) || ($dt->hour == 12) )
    50          
1700 2         14 { $ampmhour = 12 }
1701             elsif ($dt->hour > 12)
1702 0         0 { $ampmhour = $dt->hour - 12 }
1703             else
1704 75         755 { $ampmhour = $dt->hour }
1705            
1706             # am/pm hour
1707 77 100       248 if ($key eq 'ampmhour')
1708 60         72 { return zeropad_2($ampmhour) }
1709            
1710             # hour and minute with ampm
1711 17 50 33     48 if (
1712             ($key eq 'clocktime') ||
1713             ($key eq 'clocktimestrict')
1714             ) {
1715            
1716             return
1717 17         49 $ampmhour . ':' .
1718             zeropad_2($dt->minute) . ' ' .
1719             $ampm;
1720             }
1721            
1722             # tz
1723 0 0       0 if ( $key eq 'tz' ) {
1724 0         0 return $dt->time_zone_short_name;
1725             }
1726            
1727             # olson
1728 0 0       0 if ( $key eq 'olson' ) {
1729 0         0 return $dt->time_zone_long_name;
1730             }
1731            
1732             # else we don't know what property is needed
1733 0         0 return $eztied->warn("unknown-format: do not know this format: $orgkey");
1734             }
1735             #
1736             # FETCH
1737             #------------------------------------------------------------------------------
1738              
1739              
1740             #------------------------------------------------------------------------------
1741             # warn
1742             #
1743             sub warn {
1744 0     0   0 my $eztied = shift;
1745 0 0       0 my $level = defined($eztied->{'warnings'}) ? $eztied->{'warnings'} : $Date::EzDate2::default_warning;
1746            
1747             # TESTING
1748             # println subname(); ##i
1749            
1750             # if no level, return undef
1751 0 0       0 $level or return undef;
1752            
1753 0 0       0 if ($level == WARN_STDERR) {
1754             # showstack();
1755 0         0 carp 'WARNING: ', @_;
1756 0         0 return undef;
1757             }
1758            
1759             # croak
1760 0         0 croak @_;
1761             }
1762             #
1763             # warn
1764             #------------------------------------------------------------------------------
1765              
1766              
1767             #------------------------------------------------------------------------------
1768             # get_alias
1769             #
1770             sub get_alias {
1771 1970     1970   1836 my ($eztied, $key, %opts) = @_;
1772            
1773             # normalize
1774 1970 100       3554 unless ($key =~ m|[\{\%]|) {
1775 1352         1598 $key =~ s|\s||g;
1776 1352         1212 $key = lc($key);
1777            
1778             # strip "nozero" if that option was sent
1779 1352 100       1938 $opts{'strip_no_zero'} and $key =~ s|nozero||g;
1780             }
1781            
1782             # if this key has an alias
1783 1970 100       2537 if (exists $PCodes{$key})
1784 604         922 {return $eztied->get_alias($PCodes{$key}, %opts)}
1785            
1786             # if this is a named format
1787 1366 100       1957 if (exists $eztied->{'formats'}->{$key})
1788 7         15 {return $eztied->{'formats'}->{$key}}
1789            
1790 1359         1915 return $key;
1791             }
1792             #
1793             # get_alias
1794             #------------------------------------------------------------------------------
1795              
1796              
1797             #------------------------------------------------------------------------------
1798             # ym, ymd
1799             #
1800             sub ym {
1801 0     0   0 my ($eztied) = @_;
1802 0         0 return @$eztied{qw{year monthnum}};
1803             }
1804              
1805             sub ymd {
1806 0     0   0 my ($eztied) = @_;
1807 0         0 return @$eztied{qw{year monthnum dayofmonth}};
1808             }
1809             #
1810             # ym, ymd
1811             #------------------------------------------------------------------------------
1812              
1813              
1814             #------------------------------------------------------------------------------
1815             # next_month
1816             #
1817             sub next_month {
1818 3     3   3 my ($eztied, $count) = @_;
1819            
1820             # TESTING
1821             # println subname(); ##i
1822            
1823             # add months and return
1824 3         8 return $eztied->{'dt'}->add(months => $count, end_of_month=>'limit');
1825             }
1826             #
1827             # next_month
1828             #------------------------------------------------------------------------------
1829              
1830              
1831             #------------------------------------------------------------------------------
1832             # DELETE
1833             #
1834             sub DELETE {
1835 0     0     my ($eztied, $key) = @_;
1836            
1837             # normalize key
1838 0           $key =~ s|\s||gs;
1839 0           $key = lc($key);
1840            
1841             # delete from formats, but not properties
1842 0           return delete $eztied->{'formats'}->{$key};
1843             }
1844             #
1845             # DELETE
1846             #------------------------------------------------------------------------------
1847              
1848             #
1849             # Date::EzDate2::Tie
1850             ###############################################################################
1851              
1852              
1853             # return true
1854             1;
1855              
1856             __END__