File Coverage

blib/lib/DMA/ISODate.pm
Criterion Covered Total %
statement 9 90 10.0
branch 0 36 0.0
condition 0 6 0.0
subroutine 3 24 12.5
pod 18 18 100.0
total 30 174 17.2


line stmt bran cond sub pod time code
1             #=============================== ISODate.pm ==================================
2             # Filename: ISODate.pm
3             # Description: ISO date handling.
4             # Original Author: Dale M. Amon
5             # Revised by: $Author: amon $
6             # Date: $Date: 2008-08-28 23:14:03 $
7             # Version: $Revision: 1.8 $
8             # License: LGPL 2.1, Perl Artistic or BSD
9             #
10             #=============================================================================
11 1     1   1147 use strict;
  1         11  
  1         53  
12              
13             package DMA::ISODate;
14 1     1   5 use vars qw{@ISA};
  1         2  
  1         63  
15             @ISA = qw( UNIVERSAL );
16              
17 1     1   1112 use POSIX;
  1         11873  
  1         7  
18              
19             #=============================================================================
20             # Class Methods
21             #=============================================================================
22              
23             sub new {
24 0     0 1   my ($class, $datestring) = @_;
25 0           return ($class->_new (0,$datestring));
26             }
27              
28             #------------------------------------------------------------------------------
29              
30             sub unix {
31 0     0 1   my ($class, $time,$utcflg) = @_;
32 0           my $self = bless {}, $class;
33              
34 0 0         defined $time || (return undef);
35 0 0         defined $utcflg || ($utcflg = 0);
36              
37 0           my($havedate,$havetime,$y2k) = (1,1,0);
38 0 0         my ($sec,$min,$hr,$day,$mon,$yr) =
39             ($utcflg) ? gmtime($time) : localtime ($time);
40 0           $yr+=1900; $mon+=1;
  0            
41              
42 0           @$self{'y2k','havetime','isUTC',
43             'yr','mon','day','hr','min','sec'} =
44             ($y2k,$havetime,$utcflg,
45             $yr,$mon,$day,$hr,$min,$sec);
46              
47 0           $self->_set_iso_strings;
48 0           return $self;
49             }
50              
51             #------------------------------------------------------------------------------
52              
53 0     0 1   sub now {return (shift->_new (0,undef));}
54 0     0 1   sub utc {return (shift->_new (1,undef));}
55              
56             #------------------------------------------------------------------------------
57              
58             sub new_formatted {
59 0     0 1   my ($class,$fmt,$string) = @_;
60 0           my $self = bless {}, $class;
61              
62 0           return $self;
63             }
64              
65             #=============================================================================
66             # Object Methods
67             #=============================================================================
68              
69             sub get {
70 0     0 1   my ($self) = @_;
71 0 0         return $self->{'date'} . (($self->{'havetime'}) ? $self->{'time'} : "");}
72              
73 0     0 1   sub canonical {my ($self) = @_; return $self->{'date'} . $self->{'time'};}
  0            
74              
75             #------------------------------------------------------------------------------
76              
77             sub yearly {
78 0     0 1   my ($self) = @_;
79 0           @$self{'mon','day','hr','min','sec','havetime'} = (0,0,0,0,0,0);
80 0           $self->_set_iso_strings;
81 0           return $self;
82             }
83              
84             sub monthly {
85 0     0 1   my ($self) = @_;
86 0           @$self{'day','hr','min','sec','havetime'} = (0,0,0,0,0);
87 0           $self->_set_iso_strings;
88 0           return $self;
89             }
90              
91             #------------------------------------------------------------------------------
92              
93             sub isyearly {
94 0     0 1   my ($self) = @_;
95 0 0         return (($self->{'mon'} + $self->{'day'} + $self->{'havetime'}) ? 0 : 1);
96             }
97              
98             sub ismonthly {
99 0     0 1   my ($self) = @_;
100 0 0         return (($self->{'day'} + $self->{'havetime'}) ? 0 : 1);
101             }
102              
103             #------------------------------------------------------------------------------
104              
105             my @Q = ("Q1-Q4",
106             "Q1", "Q1", "Q1",
107             "Q2", "Q2", "Q2",
108             "Q3", "Q3", "Q3",
109             "Q4", "Q4", "Q4");
110              
111 0     0 1   sub quarter {return $Q[shift->{'month'}];}
112              
113             #------------------------------------------------------------------------------
114              
115 0     0 1   sub date {return shift->{'date'};}
116 0     0 1   sub time {return shift->{'time'};}
117 0     0 1   sub y2k {return shift->{'y2k'};}
118 0     0 1   sub havetime {return shift->{'havetime'};}
119 0     0 1   sub isUTC {return shift->{'isUTC'};}
120              
121             #------------------------------------------------------------------------------
122              
123             sub timearray {
124 0     0 1   my ($self) = @_;
125 0           return (@$self{'yr','mon','day','hr','min','sec',
126             'havetime','isUTC','y2k'});
127             }
128              
129             #=============================================================================
130             # Internal Methods
131             #=============================================================================
132              
133             sub _new {
134 0     0     my ($class, $utcflg,$str) = @_;
135 0           my $self = bless {}, $class;
136              
137 0           my ($havedate,$havetime,$y2k,
138             $yr,$mon,$day,$hr,$min,$sec) =
139             (0,0,0,
140             0,0,0,0,0,0);
141              
142             # Times come back in 2-3 digit format which we treat as a y2k correction.
143 0 0         if (!defined $str) {
144 0           ($havedate,$havetime,$y2k) = (1,1,0);
145 0 0         ($sec,$min,$hr,$day,$mon,$yr) =
146             ($utcflg) ? gmtime(CORE::time) : localtime (CORE::time);
147 0           $yr+=1900; $mon+=1;
  0            
148             }
149             else {
150              
151             # Try ISO date format first.
152             # ***** THESE VALUES ARE NOT CHECKED FOR LIMITS OR THAT THE DAY OF THE
153             # MONTH EXISTS IN THAT MONTH AND YEAR.
154 0           ($havedate,$havetime,$y2k,
155             $yr,$mon,$day,$hr,$min,$sec) = $self->_isodate($str);
156              
157             # ***** Later on fill this in so it handles other formats.
158 0 0         if (!$havedate) {return undef;}
  0            
159             }
160              
161 0 0         $havedate || return undef;
162              
163 0           @$self{'y2k','havetime','isUTC',
164             'yr','mon','day','hr','min','sec'} =
165             ($y2k,$havetime,$utcflg,
166             $yr,$mon,$day,$hr,$min,$sec);
167              
168 0           $self->_set_iso_strings;
169 0           return $self;
170             }
171              
172             #------------------------------------------------------------------------------
173             # See if we can make an ISODATE out of the string with no chars left over.
174             # An ISODATE must be at least 6 digits long; it may be for 1 Million AD,
175             # so we allow lots of digits. Of course you can't stuff that in a Unix
176             # timval, but we don't need to anyway.
177             #
178             # The return values are in a canonical form:
179             # havedate => true if we found the date
180             # havetime => true if we found the time
181             # y2k => true if we had a 2 digit year on input.
182             #
183             # We could get fancier if we had to. It would not be hard to deal with
184             # ISO time and date seperated by delimiters; we could also check potential
185             # MM,DD,YY,HH, MM, SS for validity if we needed to. We will let the caller
186             # use a standard Perl Module of some sort for that job rather than redoing
187             # it. We just assume that if it looks ISODATE and is not, it was wrong
188             # and could not have been parsed in an alternative format. Until someone
189             # points out an exception, that's my story and I'm sticking to it.
190             #
191             # I am leaving extra conditionals here as hooks for in case I was wrong.
192             # Otherwise I could simplify the routine by a number of lines. Likewise,
193             #
194             # ASSUME: I assume two or three digit years should always be replaced
195             # by yr+1900. Two digit is assumed to be a Y2K problem; 3 digit
196             # is assumed to be a Unix timval that really is yr-1900. Perhaps
197             # we'll need a U2K for 2038...
198             #
199             # ASSUME: There is no such thing as an ISODATE that only has the time
200             # portion HHMMSS.
201             #
202             # Args: self
203             # string
204             # Returns: (havedate, havetime, y2k,
205             # year, month, day, hour, minute, second,
206             # remaining_chars)
207              
208             sub _isodate {
209 0     0     my ($self, $str) = @_;
210 0           my $r = $str;
211              
212             # See if we've got a possible ISO date, at least 6 chars.
213 0 0         if ($str =~ /^(\d{6,})$/) {
214 0           my ($a1,$a2,$a3,$b1,$b2,$b3,$b4,$b5,$b6);
215 0           my ($iso, $len) = ($1, length $1);
216            
217             # The 3 item (minimum 6 digits) parse
218 0 0         if ($iso =~ /^(\d{2,})(\d\d)(\d\d)(.*)/) {($a1,$a2,$a3,$r) = ($1,$2,$3,$4);}
  0            
219            
220             # The 6 item (minimum 12 digits) parse
221 0 0 0       if (($len > 6) &&
222             ($iso =~ /^(\d{2,})(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(.*)$/)) {
223 0           ($b1,$b2,$b3,$b4,$b5,$b6,$r) = ($1,$2,$3,$4,$5,$6,$7);}
224            
225             # 3 item: YYMMDD; (or HHMMSS if we allowed that). This is a Y2K.
226 0 0         if ($len == 6) {return (1,0,1, $a1+1900,$a2,$a3, 0,0,0, $r);}
  0            
227              
228             # 3 item: YYYMMDD, probably a Unix year after 1900. Not a y2k.
229 0 0         if ($len == 7) {return (1,0,0, $a1+1900,$a2,$a3, 0,0,0, $r);}
  0            
230            
231             # 3 item: YYYYMMDD to YYYYYYYYMMDD, the later being rather unlikely
232 0 0 0       if (($len >= 8) && ($len < 12)) {return (1,0,0, $a1,$a2,$a3, 0,0,0, $r);}
  0            
233            
234             # 6 item: YYMMDDHHMMSS, a y2k date or 3 item: YYYYYYYYMMDD, the later
235             # being rather unlikely but an annoying loss.
236 0 0         if ($len == 12) {return (1,1,1, $b1+1900,$b2,$b3, $b4,$b5,$b6, $r);}
  0            
237            
238             # YYYMMDDHHMMSS or YYYYYYYYYYMMDD, the first being a format error
239             # with a Unix year after 1900 but more likely than the later.
240 0 0         if ($len == 13) {return (1,1,0, $b1+1900,$b2,$b3, $b4,$b5,$b6, $r);}
  0            
241            
242             # YYYYMMDDHHMMSS to {Y...}YYYYMMDDHHMMSS to infinity and beyond...
243 0           return (1,1,0, $b1,$b2,$b3, $b4,$b5,$b6, $r);
244             }
245 0           return (0,0,0, 0,0,0, 0,0,0, $r);
246             }
247              
248             #------------------------------------------------------------------------------
249             # Update the date and time strings from the yr,mon,day,hr,min,sec fields.
250              
251             sub _set_iso_strings {
252 0     0     my ($self) = @_;
253 0           @$self{'date','time','havetime'} =
254             (sprintf ("%04d%02d%02d", @$self{'yr','mon','day'}),
255             sprintf ("%02d%02d%02d", @$self{'hr','min','sec'}),
256             $self->{'havetime'});
257 0           return $self;
258             }
259            
260             #=============================================================================
261             # Pod Documentation
262             #=============================================================================
263             # You may extract and format the documentation section with the 'perldoc' cmd.
264              
265             =head1 NAME
266              
267             DMA::ISODate.pm - ISO date handling.
268              
269             =head1 SYNOPSIS
270              
271             use DMA::ISODate;
272              
273             $obj = DMA::ISOdate->new ($datestring);
274             $obj = DMA::ISOdate->now;
275             $obj = DMA::ISOdate->utc;
276             $obj = DMA::ISOdate->unix ($time, $gmflag);
277             $obj = DMA::ISOdate->new_formatted ($fmt, $string);
278              
279             $datestring = $obj->get;
280             $datestring = $obj->canonical;
281             $obj = $obj->yearly;
282             $obj = $obj->monthly;
283             $obj = $obj->isyearly;
284             $obj = $obj->ismonthly;
285             $quarter = $obj->quarter;
286             $season = $obj->season;
287             $datestring = $obj->date;
288             $timestring = $obj->time;
289             $havetime = $obj->havetime;
290             $y2k = $obj->y2k;
291             $utc = $obj->isUTC;
292              
293             ($yr,$mon,$day,$hr,$min,$sec,$havetime,$utc,$y2k) = $obj->timearray;
294              
295             =head1 Inheritance
296              
297             UNIVERSAL
298              
299             =head1 Description
300              
301             The primary date time we use is the ISO date, almost always in the basic
302             form of YYYYMMDD , like 20021209, but the DMA::ISOdate class will attempt to
303             create an ISODate from what ever you give it:
304              
305             Input data Canonical ISO result
306             YYMMDD => 19YYMMDD000000
307             YYYMMDD => (1900+YYY)MMDD000000
308             YYYYMMDD => YYYYMMDD000000
309             YYYYYMMDD => YYYYYMMDD000000
310             YYYYYYMMDD => YYYYYYMMDD000000
311             YYYYYYYMMDD => YYYYYYYMMDD000000
312             YYMMDDHHMMSS => 19YYMMDDHHMMSS
313             YYYMMDDHHMMSS => (1900+YY)MMDDHHMMSS
314             YYYYMMDDHHMMSS => YYYYMMDDHHMMSS
315             {Y..}YYYYMMDDHHMMSS => {Y..}YYYYMMDDHHMMSS
316              
317             Note that a minimum of 4 digits is needed to correctly express years like
318             40AD so as to differentiate it from 1940AD which is what the Y2K correction
319             would do with "401209". There are also problems: years cannot be expressed
320             beyond 9999999 in the date only format.
321              
322             Two digit years (00-99) are assume to be Y2K legacy dates. We set the y2k
323             flag and add 1900 to the year value when we see one.
324              
325             Three digit years (000-999) are likely to be uncorrected Unix date returns.
326             We do not set the y2k but we do add 1900. This is safe until we hit what
327             I'll call the "U2K" date of 2038 when Unix 32b int timevals roll over. This
328             is not a problem for this Class; we follow the philosophy of "be liberal on
329             inputs and conservative on outputs".
330              
331             If this all seems very ad hoc -- it is. Date formats are very ad hoc with
332             ambiguities which can only be decided with contextual information. That's a
333             job for people, not a poor wee ISODate Class.
334              
335             Four digit year formats are not limited to 4 digits. We can express dates far
336             into the future. In any place hereafter where we use "YYYY", any number of
337             extra digits are possible.
338              
339             [We aren't affected by the size of Unix timval (ie the 2038 max year) except
340             it is not convenient right now to do a perpetual calendar of my own to check
341             the validity of a date.]
342              
343             We do not, however, have any means of representing dates BC. For this we might
344             consider using the Peter Kokh dating system which adds 10000 to the AD date to
345             represent all of human history after the end of the most recent Ice Age. This
346             allows much easier translation between all modern and ancient dating systems
347             if you remember there was no year zero as they had not gotten around to
348             inventing nothing back then. (Given some recent discoveries offshore in India,
349             I might prefer adding 20000 years!)
350              
351             =head1 Examples
352              
353             None.
354              
355             =head1 Class Variables
356              
357             None.
358              
359             =head1 Instance Variables
360              
361             y2k Set if external input was in two digit year format, t/f.
362             havetime Set if input included the time, t/f
363             isUTC date/time is known to be UTC, t/f.
364             (What should the default be since we will
365             only know this if we got the time via newgm.)
366             date "YYYYMMDD"
367             time "HHMMSS", default is "000000"
368             yr integer year, 0 -size of int
369             mon integer month, 1-12,; 0=no month
370             day integer day, 1-28,29,30 or 31; 0=no day
371             hr integer hour, 0-23
372             min integer minute, 0-59
373             sec integer second, 0-59
374              
375             =head1 Class Methods
376              
377             =over 4
378              
379             =item B<$obj = DMA::ISOdate-Enew ($datestring)>
380              
381             Assume the $datestring is a local ISO date or date/time in one of the formats
382             discussed earlier. Returns undef if $datestring can't be parsed our way; 1900
383             is added to the year if 2 or 3 digits are found and the y2k flag set for 2
384             digit years. havetime is set if there was an HHMMSS in the string.
385              
386             Returns a new object or undef on failure.
387              
388             =item B<$obj = DMA::ISOdate-Enew_formatted ($fmt, $string)>
389              
390             Use a Perl date format string to identify the date format we believe $string
391             to be in. It returns undef instead of creating a new object if the date
392             doesn't work in the given format.
393              
394             =item B<$obj = DMA::ISOdate-Enow>
395              
396             Create an object with the current time set to right now in local time. Always
397             succeeds, always Y2K compliant and has HHMMSS available.
398              
399             =item B<$obj = DMA::ISOdate-Eunix ($time, $gmflag)>
400              
401             Create an object for a unix timeval. $time is required and assumed to be a
402             unix time integer. If $gmflag is present and set, make it a UTC time,
403             otherwise it is local time. Always succeeds, always Y2K compliant and has
404             HHMMSS available.
405              
406             This routine is useful when dealing with info from archive file directory
407             stats.
408              
409             =item B<$obj = DMA::ISOdate-Eutc>
410              
411             Create an object with the current time set to right now in UTC time. Always
412             succeeds, always Y2K compliant and has HHMMSS available.
413              
414             =back 4
415              
416             =head1 Instance Methods
417              
418             =over 4
419              
420             =item B<$datestring = $obj-Ecanonical>
421              
422             Returns an the object's ISODATE. In a canonical form: YYYYMMDD HHMMSS . If
423             havetime is not set, we get YYYYMMDD000000.
424              
425             =item B<$datestring = $obj-Edate>
426              
427             Returns the ISO date as YYYYMMDD .
428              
429             =item B<$datestring = $obj-Eget>
430              
431             Returns an the object's ISODATE. In one of two forms, either YYYYMMDD if
432             havetime is not set or YYYYMMDDHHMMSS if it is.
433              
434             =item B<$havetime = $obj-Ehavetime>
435              
436             True if we have a time of day stored.
437              
438             =item B<$obj = $obj-Eismonthly>
439              
440             Test if the ISO date is suitable for things like monthly magazines. Returns
441             true if havetime and day of month are clear. It means your ISO date is of the
442             form "19950500".
443              
444             =item B<$utc = $obj-EisUTC>
445              
446             True if the time we stored was UTC.
447              
448             =item B<$obj = $obj-Eisyearly>
449              
450             Test if the ISO date is suitable for things like yearly reports. Returns true
451             if havetime, month and day of month are clear. It means your ISO date is of
452             the form "19950000".
453              
454             =item B<$obj = $obj-Emonthly>
455              
456             Change the ISO date so it is of use for things like monthly magazines.
457             havetime is cleared. All time and date field below month are zeroed. Your
458             ISO date will now look like "19950500".
459              
460             =item B<$quarter = $obj-Equarter>
461              
462             Returns the quarter string for the date. Q1,Q2,Q3,Q4 or Q1-Q4 if the date has
463             no month, eg "19950000".
464              
465             =item B<$season = $obj-Eseason>
466              
467             Returns the season: winter, spring,summer,fall.
468              
469             =item B<$timestring = $obj-Etime>
470              
471             Returns the time as HHMMSS if havetime is set; otherwise the midnight time
472             string "000000".
473              
474             =item B<($yr,$mon,$day,$hr,$min,$sec,$havetime,$utc,$y2k) = $obj-Etimearray>
475              
476             Return the date/time information.
477              
478             =item B<$y2k = $obj-Ey2k>
479              
480             True if we applied a Y2K correction to the year in our stored date.
481              
482             =item B<$obj = $obj-Eyearly>
483              
484             Change the ISO date so it is of use for things like yearly reports. havetime
485             is cleared. All time and date field below year are zeroed. Your ISO date
486             will now look like "19950000".
487              
488             =back 4
489              
490             =head1 Private Class Methods
491              
492             =over 4
493              
494             =item B<$obj = DMA::ISOdate-E_new ($type,$gmflag,$datestring)>
495              
496             Internal base initializer method which all the other initializer methods
497             call.
498              
499             Not part of the advertised interface for this class, so don't try to use it
500             directly.
501              
502             Returns self or undef if no date found/created.
503              
504             =back 4
505              
506             =head1 Private Instance Methods
507              
508             None, although I may wish to include the code comments from _isodate here as
509             it is quite extensive.
510              
511             =head1 KNOWN BUGS
512              
513             See TODO.
514              
515             =head1 SEE ALSO
516              
517             None.
518              
519             =head1 AUTHOR
520              
521             Dale Amon
522              
523             =cut
524            
525             #=============================================================================
526             # CVS HISTORY
527             #=============================================================================
528             # $Log: ISODate.pm,v $
529             # Revision 1.8 2008-08-28 23:14:03 amon
530             # perldoc section regularization.
531             #
532             # Revision 1.7 2008-08-15 21:47:52 amon
533             # Misc documentation and format changes.
534             #
535             # Revision 1.6 2008-04-18 14:07:54 amon
536             # Minor documentation format changes
537             #
538             # Revision 1.5 2008-04-11 22:25:23 amon
539             # Add blank line after cut.
540             #
541             # Revision 1.4 2008-04-11 18:56:35 amon
542             # Fixed quoting problem with formfeeds.
543             #
544             # Revision 1.3 2008-04-11 18:39:15 amon
545             # Implimented new standard for headers and trailers.
546             #
547             # Revision 1.2 2008-04-10 15:01:08 amon
548             # Added license to headers, removed claim that the documentation section still
549             # relates to the old doc file.
550             #
551             # Revision 1.1.1.1 2004-09-19 21:59:12 amon
552             # Dale's library of primitives in Perl
553             #
554             # 20040813 Dale Amon
555             # Moved to DMA:: from Archivist::
556             # to make it easier to enforce layers.
557             #
558             # 20021207 Dale Amon
559             # Created.
560             1;