File Coverage

blib/lib/DateTime/Format/ISO8601.pm
Criterion Covered Total %
statement 142 144 98.6
branch 42 44 95.4
condition 8 15 53.3
subroutine 33 34 97.0
pod 11 11 100.0
total 236 248 95.1


line stmt bran cond sub pod time code
1             # Copyright (C) 2003-2012 Joshua Hoblitt
2             package DateTime::Format::ISO8601;
3              
4 8     8   2956578 use strict;
  8         19  
  8         392  
5 8     8   53 use warnings;
  8         18  
  8         505  
6 8     8   3217 use namespace::autoclean;
  8         149562  
  8         85  
7              
8             our $VERSION = '0.19';
9              
10 8     8   790 use Carp qw( croak );
  8         22  
  8         608  
11 8     8   5858 use DateTime 1.45;
  8         3723522  
  8         509  
12 8     8   5884 use DateTime::Format::Builder 0.77;
  8         734877  
  8         63  
13 8     8   5064 use DateTime::Format::ISO8601::Types;
  8         112  
  8         66  
14 8     8   137974 use Params::ValidationCompiler 0.26 qw( validation_for );
  8         226  
  8         66186  
15              
16             {
17             my $validator = validation_for(
18             name => 'DefaultLegacyYear',
19             name_is_optional => 1,
20             params => [ { type => t('Bool') } ],
21             );
22              
23             my $default_legacy_year;
24              
25             sub DefaultLegacyYear {
26 482     482 1 638682 shift;
27 482 100       1909 ($default_legacy_year) = $validator->(@_)
28             if @_;
29              
30 476         1693 return $default_legacy_year;
31             }
32             }
33              
34             __PACKAGE__->DefaultLegacyYear(1);
35              
36             {
37             my $validator = validation_for(
38             name => 'DefaultCutOffYear',
39             name_is_optional => 1,
40             params => [ { type => t('CutOffYear') } ],
41             );
42              
43             my $default_cut_off_year;
44              
45             sub DefaultCutOffYear {
46 10890     10890 1 902528 shift;
47 10890 100       34877 ($default_cut_off_year) = $validator->(@_)
48             if @_;
49              
50 10884         26556 return $default_cut_off_year;
51             }
52             }
53              
54             # the same default value as DT::F::Mail
55             __PACKAGE__->DefaultCutOffYear(49);
56              
57             {
58             my $validator = validation_for(
59             name => '_check_new_params',
60             name_is_optional => 1,
61             params => {
62             base_datetime => {
63             type => t('DateTimeIsh'),
64             optional => 1,
65             },
66             legacy_year => {
67             type => t('Bool'),
68             optional => 1,
69             },
70             cut_off_year => {
71             type => t('CutOffYear'),
72             optional => 1,
73             },
74             },
75             );
76              
77             sub new {
78 575     575 1 7379116 my ($class) = shift;
79 575         21028 my %args = $validator->(@_);
80              
81             $args{legacy_year} = $class->DefaultLegacyYear
82 563 100       22520 unless exists $args{legacy_year};
83             $args{cut_off_year} = $class->DefaultCutOffYear
84 563 100       2527 unless exists $args{cut_off_year};
85              
86 563   33     2740 $class = ref($class) || $class;
87              
88 563         1588 my $self = bless( \%args, $class );
89              
90 563 100       1752 if ( $args{base_datetime} ) {
91 122         957 $self->set_base_datetime( object => $args{base_datetime} );
92             }
93              
94 557         2116 return ($self);
95             }
96             }
97              
98             # lifted from DateTime
99 0     0 1 0 sub clone { bless { %{ $_[0] } }, ref $_[0] }
  0         0  
100              
101 1000     1000 1 403595 sub base_datetime { $_[0]->{base_datetime} }
102              
103             {
104             my $validator = validation_for(
105             name => 'set_base_datetime',
106             name_is_optional => 1,
107             params => {
108             object => { type => t('DateTimeIsh') },
109             },
110             );
111              
112             sub set_base_datetime {
113 135     135 1 223 my $self = shift;
114              
115 135         3304 my %args = $validator->(@_);
116              
117             # ISO8601 only allows years 0 to 9999
118             # this implementation ignores the needs of expanded formats
119 135         5176 my $dt = DateTime->from_object( object => $args{object} );
120 135         102914 my $lower_bound = DateTime->new( year => 0 );
121 135         41425 my $upper_bound = DateTime->new( year => 10000 );
122              
123 135 100       41098 if ( $dt < $lower_bound ) {
124 6         320 croak 'base_datetime must be greater then or equal to ',
125             $lower_bound->iso8601;
126             }
127 129 100       10989 if ( $dt >= $upper_bound ) {
128 6         256 croak 'base_datetime must be less then ', $upper_bound->iso8601;
129             }
130              
131 123         7854 $self->{base_datetime} = $dt;
132              
133 123         1031 return $self;
134             }
135             }
136              
137 10     10 1 1629 sub legacy_year { $_[0]->{legacy_year} }
138              
139             {
140             my $validator = validation_for(
141             name => 'set_legacy_year',
142             name_is_optional => 1,
143             params => [ { type => t('Bool') } ],
144             );
145              
146             sub set_legacy_year {
147 9     9 1 20 my $self = shift;
148              
149 9         251 ( $self->{legacy_year} ) = $validator->(@_);
150              
151 3         49 return $self;
152             }
153             }
154              
155 501     501 1 152438 sub cut_off_year { $_[0]->{cut_off_year} }
156              
157             {
158             my $validator = validation_for(
159             name => 'set_cut_off_year',
160             name_is_optional => 1,
161             params => [ { type => t('CutOffYear') } ],
162             );
163              
164             sub set_cut_off_year {
165 106     106 1 183 my $self = shift;
166              
167 106         3304 ( $self->{cut_off_year} ) = $validator->(@_);
168              
169 100         1274 return $self;
170             }
171             }
172              
173             {
174             my $validator = validation_for(
175             name => 'format_datetime',
176             name_is_optional => 1,
177             params => [ { type => t('DateTime') } ],
178             );
179              
180             sub format_datetime {
181 5     5 1 536045 my $self = shift;
182 5         198 my ($dt) = $validator->(@_);
183              
184 5 100       107 my $cldr
    100          
185             = $dt->nanosecond % 1000000 ? 'yyyy-MM-ddTHH:mm:ss.SSSSSSSSS'
186             : $dt->nanosecond ? 'yyyy-MM-ddTHH:mm:ss.SSS'
187             : 'yyyy-MM-ddTHH:mm:ss';
188              
189 5         73 my $tz;
190 5 100       47 if ( $dt->time_zone->is_utc ) {
191 2         21 $tz = 'Z';
192             }
193             else {
194 3         37 $tz = q{};
195 3         15 $cldr .= 'ZZZZZ';
196             }
197              
198 5         31 return $dt->format_cldr($cldr) . $tz;
199             }
200             }
201              
202             DateTime::Format::Builder->create_class(
203             parsers => {
204             parse_datetime => [
205             {
206             #YYYYMMDD 19850412
207             length => 8,
208             regex => qr/^ (\d{4}) (\d\d) (\d\d) $/x,
209             params => [qw( year month day )],
210             },
211             {
212             # uncombined with above because
213             #regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d) $/x,
214             # was matching 152746-05
215              
216             #YYYY-MM-DD 1985-04-12
217             length => 10,
218             regex => qr/^ (\d{4}) - (\d\d) - (\d\d) $/x,
219             params => [qw( year month day )],
220             },
221             {
222             #YYYY-MM 1985-04
223             length => 7,
224             regex => qr/^ (\d{4}) - (\d\d) $/x,
225             params => [qw( year month )],
226             },
227             {
228             #YYYY 1985
229             length => 4,
230             regex => qr/^ (\d{4}) $/x,
231             params => [qw( year )],
232             },
233             {
234             #YY 19 (century)
235             length => 2,
236             regex => qr/^ (\d\d) $/x,
237             params => [qw( year )],
238             postprocess => \&_normalize_century,
239             },
240             {
241             #YYMMDD 850412
242             #YY-MM-DD 85-04-12
243             length => [qw( 6 8 )],
244             regex => qr/^ (\d\d) -?? (\d\d) -?? (\d\d) $/x,
245             params => [qw( year month day )],
246             postprocess => \&_fix_2_digit_year,
247             },
248             {
249             #-YYMM -8504
250             #-YY-MM -85-04
251             length => [qw( 5 6 )],
252             regex => qr/^ - (\d\d) -?? (\d\d) $/x,
253             params => [qw( year month )],
254             postprocess => \&_fix_2_digit_year,
255             },
256             {
257             #-YY -85
258             length => 3,
259             regex => qr/^ - (\d\d) $/x,
260             params => [qw( year )],
261             postprocess => \&_fix_2_digit_year,
262             },
263             {
264             #--MMDD --0412
265             #--MM-DD --04-12
266             length => [qw( 6 7 )],
267             regex => qr/^ -- (\d\d) -?? (\d\d) $/x,
268             params => [qw( month day )],
269             postprocess => \&_add_year,
270             },
271             {
272             #--MM --04
273             length => 4,
274             regex => qr/^ -- (\d\d) $/x,
275             params => [qw( month )],
276             postprocess => \&_add_year,
277             },
278             {
279             #---DD ---12
280             length => 5,
281             regex => qr/^ --- (\d\d) $/x,
282             params => [qw( day )],
283             postprocess => [ \&_add_year, \&_add_month ],
284             },
285             {
286             #+[YY]YYYYMMDD +0019850412
287             #+[YY]YYYY-MM-DD +001985-04-12
288             length => [qw( 11 13 )],
289             regex => qr/^ \+ (\d{6}) -?? (\d\d) -?? (\d\d) $/x,
290             params => [qw( year month day )],
291             },
292             {
293             #+[YY]YYYY-MM +001985-04
294             length => 10,
295             regex => qr/^ \+ (\d{6}) - (\d\d) $/x,
296             params => [qw( year month )],
297             },
298             {
299             #+[YY]YYYY +001985
300             length => 7,
301             regex => qr/^ \+ (\d{6}) $/x,
302             params => [qw( year )],
303             },
304             {
305             #+[YY]YY +0019 (century)
306             length => 5,
307             regex => qr/^ \+ (\d{4}) $/x,
308             params => [qw( year )],
309             postprocess => \&_normalize_century,
310             },
311             {
312             #YYYYDDD 1985102
313             #YYYY-DDD 1985-102
314             length => [qw( 7 8 )],
315             regex => qr/^ (\d{4}) -?? (\d{3}) $/x,
316             params => [qw( year day_of_year )],
317             constructor => [ 'DateTime', 'from_day_of_year' ],
318             },
319             {
320             #YYDDD 85102
321             #YY-DDD 85-102
322             length => [qw( 5 6 )],
323             regex => qr/^ (\d\d) -?? (\d{3}) $/x,
324             params => [qw( year day_of_year )],
325             postprocess => [ \&_fix_2_digit_year ],
326             constructor => [ 'DateTime', 'from_day_of_year' ],
327             },
328             {
329             #-DDD -102
330             length => 4,
331             regex => qr/^ - (\d{3}) $/x,
332             params => [qw( day_of_year )],
333             postprocess => [ \&_add_year ],
334             constructor => [ 'DateTime', 'from_day_of_year' ],
335             },
336             {
337             #+[YY]YYYYDDD +001985102
338             #+[YY]YYYY-DDD +001985-102
339             length => [qw( 10 11 )],
340             regex => qr/^ \+ (\d{6}) -?? (\d{3}) $/x,
341             params => [qw( year day_of_year )],
342             constructor => [ 'DateTime', 'from_day_of_year' ],
343             },
344             {
345             #YYYYWwwD 1985W155
346             #YYYY-Www-D 1985-W15-5
347             length => [qw( 8 10 )],
348             regex => qr/^ (\d{4}) -?? W (\d\d) -?? (\d) $/x,
349             params => [qw( year week day_of_week )],
350             postprocess => [ \&_normalize_week ],
351             constructor => [ 'DateTime', 'from_day_of_year' ],
352             },
353             {
354             #YYYYWww 1985W15
355             #YYYY-Www 1985-W15
356             length => [qw( 7 8 )],
357             regex => qr/^ (\d{4}) -?? W (\d\d) $/x,
358             params => [qw( year week )],
359             postprocess => [ \&_normalize_week ],
360             constructor => [ 'DateTime', 'from_day_of_year' ],
361             },
362             {
363             #YYWwwD 85W155
364             #YY-Www-D 85-W15-5
365             length => [qw( 6 8 )],
366             regex => qr/^ (\d\d) -?? W (\d\d) -?? (\d) $/x,
367             params => [qw( year week day_of_week )],
368             postprocess => [ \&_fix_2_digit_year, \&_normalize_week ],
369             constructor => [ 'DateTime', 'from_day_of_year' ],
370             },
371             {
372             #YYWww 85W15
373             #YY-Www 85-W15
374             length => [qw( 5 6 )],
375             regex => qr/^ (\d\d) -?? W (\d\d) $/x,
376             params => [qw( year week )],
377             postprocess => [ \&_fix_2_digit_year, \&_normalize_week ],
378             constructor => [ 'DateTime', 'from_day_of_year' ],
379             },
380             {
381             #-YWwwD -5W155
382             #-Y-Www-D -5-W15-5
383             length => [qw( 6 8 )],
384             regex => qr/^ - (\d) -?? W (\d\d) -?? (\d) $/x,
385             params => [qw( year week day_of_week )],
386             postprocess => [ \&_fix_1_digit_year, \&_normalize_week ],
387             constructor => [ 'DateTime', 'from_day_of_year' ],
388             },
389             {
390             #-YWww -5W15
391             #-Y-Www -5-W15
392             length => [qw( 5 6 )],
393             regex => qr/^ - (\d) -?? W (\d\d) $/x,
394             params => [qw( year week )],
395             postprocess => [ \&_fix_1_digit_year, \&_normalize_week ],
396             constructor => [ 'DateTime', 'from_day_of_year' ],
397             },
398             {
399             #-WwwD -W155
400             #-Www-D -W15-5
401             length => [qw( 5 6 )],
402             regex => qr/^ - W (\d\d) -?? (\d) $/x,
403             params => [qw( week day_of_week )],
404             postprocess => [ \&_add_year, \&_normalize_week ],
405             constructor => [ 'DateTime', 'from_day_of_year' ],
406             },
407             {
408             #-Www -W15
409             length => 4,
410             regex => qr/^ - W (\d\d) $/x,
411             params => [qw( week )],
412             postprocess => [ \&_add_year, \&_normalize_week ],
413             constructor => [ 'DateTime', 'from_day_of_year' ],
414             },
415             {
416             #-W-D -W-5
417             length => 4,
418             regex => qr/^ - W - (\d) $/x,
419             params => [qw( day_of_week )],
420             postprocess => [
421             \&_add_year,
422             \&_add_week,
423             \&_normalize_week,
424             ],
425             constructor => [ 'DateTime', 'from_day_of_year' ],
426             },
427             {
428             #+[YY]YYYYWwwD +001985W155
429             #+[YY]YYYY-Www-D +001985-W15-5
430             length => [qw( 11 13 )],
431             regex => qr/^ \+ (\d{6}) -?? W (\d\d) -?? (\d) $/x,
432             params => [qw( year week day_of_week )],
433             postprocess => [ \&_normalize_week ],
434             constructor => [ 'DateTime', 'from_day_of_year' ],
435             },
436             {
437             #+[YY]YYYYWww +001985W15
438             #+[YY]YYYY-Www +001985-W15
439             length => [qw( 10 11 )],
440             regex => qr/^ \+ (\d{6}) -?? W (\d\d) $/x,
441             params => [qw( year week )],
442             postprocess => [ \&_normalize_week ],
443             constructor => [ 'DateTime', 'from_day_of_year' ],
444             },
445             {
446             #hhmmss 232050 - skipped
447             #hh:mm:ss 23:20:50
448             length => [qw( 8 9 )],
449             regex => qr/^ T?? (\d\d) : (\d\d) : (\d\d) $/x,
450             params => [qw( hour minute second)],
451             postprocess => [
452             \&_add_year,
453             \&_add_month,
454             \&_add_day
455             ],
456             },
457              
458             #hhmm 2320 - skipped
459             #hh 23 -skipped
460             {
461             #hh:mm 23:20
462             length => [qw( 4 5 6 )],
463             regex => qr/^ T?? (\d\d) :?? (\d\d) $/x,
464             params => [qw( hour minute )],
465             postprocess => [
466             \&_add_year,
467             \&_add_month,
468             \&_add_day
469             ],
470             },
471             {
472             #hhmmss,ss 232050,5
473             #hh:mm:ss,ss 23:20:50,5
474             regex =>
475             qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d) [\.,] (\d+) $/x,
476             params => [qw( hour minute second nanosecond)],
477             postprocess => [
478             \&_add_year,
479             \&_add_month,
480             \&_add_day,
481             \&_fractional_second
482             ],
483             },
484             {
485             #hhmm,mm 2320,8
486             #hh:mm,mm 23:20,8
487             regex => qr/^ T?? (\d\d) :?? (\d\d) [\.,] (\d+) $/x,
488             params => [qw( hour minute second )],
489             postprocess => [
490             \&_add_year,
491             \&_add_month,
492             \&_add_day,
493             \&_fractional_minute
494             ],
495             },
496             {
497             #hh,hh 23,3
498             regex => qr/^ T?? (\d\d) [\.,] (\d+) $/x,
499             params => [qw( hour minute )],
500             postprocess => [
501             \&_add_year,
502             \&_add_month,
503             \&_add_day,
504             \&_fractional_hour
505             ],
506             },
507             {
508             #-mmss -2050 - skipped
509             #-mm:ss -20:50
510             length => 6,
511             regex => qr/^ - (\d\d) : (\d\d) $/x,
512             params => [qw( minute second )],
513             postprocess => [
514             \&_add_year,
515             \&_add_month,
516             \&_add_day,
517             \&_add_hour
518             ],
519             },
520              
521             #-mm -20 - skipped
522             #--ss --50 - skipped
523             {
524             #-mmss,s -2050,5
525             #-mm:ss,s -20:50,5
526             regex => qr/^ - (\d\d) :?? (\d\d) [\.,] (\d+) $/x,
527             params => [qw( minute second nanosecond )],
528             postprocess => [
529             \&_add_year,
530             \&_add_month,
531             \&_add_day,
532             \&_add_hour,
533             \&_fractional_second
534             ],
535             },
536             {
537             #-mm,m -20,8
538             regex => qr/^ - (\d\d) [\.,] (\d+) $/x,
539             params => [qw( minute second )],
540             postprocess => [
541             \&_add_year,
542             \&_add_month,
543             \&_add_day,
544             \&_add_hour,
545             \&_fractional_minute
546             ],
547             },
548             {
549             #--ss,s --50,5
550             regex => qr/^ -- (\d\d) [\.,] (\d+) $/x,
551             params => [qw( second nanosecond)],
552             postprocess => [
553             \&_add_year,
554             \&_add_month,
555             \&_add_day,
556             \&_add_hour,
557             \&_add_minute,
558             \&_fractional_second,
559             ],
560             },
561             {
562             #hhmmssZ 232030Z
563             #hh:mm:ssZ 23:20:30Z
564             length => [qw( 7 8 9 10 )],
565             regex => qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d) Z $/x,
566             params => [qw( hour minute second )],
567             extra => { time_zone => 'UTC' },
568             postprocess => [
569             \&_add_year,
570             \&_add_month,
571             \&_add_day,
572             ],
573             },
574              
575             {
576             #hhmmss.ssZ 232030.5Z
577             #hh:mm:ss.ssZ 23:20:30.5Z
578             regex =>
579             qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d) [\.,] (\d+) Z $/x,
580             params => [qw( hour minute second nanosecond)],
581             extra => { time_zone => 'UTC' },
582             postprocess => [
583             \&_add_year,
584             \&_add_month,
585             \&_add_day,
586             \&_fractional_second
587             ],
588             },
589              
590             {
591             #hhmmZ 2320Z
592             #hh:mmZ 23:20Z
593             length => [qw( 5 6 7 )],
594             regex => qr/^ T?? (\d\d) :?? (\d\d) Z $/x,
595             params => [qw( hour minute )],
596             extra => { time_zone => 'UTC' },
597             postprocess => [
598             \&_add_year,
599             \&_add_month,
600             \&_add_day,
601             ],
602             },
603             {
604             #hhZ 23Z
605             length => [qw( 3 4 )],
606             regex => qr/^ T?? (\d\d) Z $/x,
607             params => [qw( hour )],
608             extra => { time_zone => 'UTC' },
609             postprocess => [
610             \&_add_year,
611             \&_add_month,
612             \&_add_day,
613             ],
614             },
615             {
616             #hhmmss[+-]hhmm 152746+0100 152746-0500
617             #hh:mm:ss[+-]hh:mm 15:27:46+01:00 15:27:46-05:00
618             length => [qw( 11 12 14 15 )],
619             regex => qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d)
620             ([+-] \d\d :?? \d\d) $/x,
621             params => [qw( hour minute second time_zone )],
622             postprocess => [
623             \&_add_year,
624             \&_add_month,
625             \&_add_day,
626             \&_normalize_offset,
627             ],
628             },
629             {
630             #hhmmss.ss[+-]hhmm 152746.5+0100 152746.5-0500
631             #hh:mm:ss.ss[+-]hh:mm 15:27:46.5+01:00 15:27:46.5-05:00
632             regex => qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d) [\.,] (\d+)
633             ([+-] \d\d :?? \d\d) $/x,
634             params => [qw( hour minute second nanosecond time_zone )],
635             postprocess => [
636             \&_add_year,
637             \&_add_month,
638             \&_add_day,
639             \&_fractional_second,
640             \&_normalize_offset,
641             ],
642             },
643              
644             {
645             #hhmmss[+-]hh 152746+01 152746-05
646             #hh:mm:ss[+-]hh 15:27:46+01 15:27:46-05
647             length => [qw( 9 10 11 12 )],
648             regex => qr/^ T?? (\d\d) :?? (\d\d) :?? (\d\d)
649             ([+-] \d\d) $/x,
650             params => [qw( hour minute second time_zone )],
651             postprocess => [
652             \&_add_year,
653             \&_add_month,
654             \&_add_day,
655             \&_normalize_offset,
656             ],
657             },
658             {
659             #YYYYMMDDThhmmss 19850412T101530
660             #YYYY-MM-DDThh:mm:ss 1985-04-12T10:15:30
661             length => [qw( 15 19 )],
662             regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d)
663             T (\d\d) :?? (\d\d) :?? (\d\d) $/x,
664             params => [qw( year month day hour minute second )],
665             extra => { time_zone => 'floating' },
666             },
667             {
668             #YYYYMMDDThhmmss.ss 19850412T101530.123
669             #YYYY-MM-DDThh:mm:ss.ss 1985-04-12T10:15:30.123
670             regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d)
671             T (\d\d) :?? (\d\d) :?? (\d\d) [\.,] (\d+) $/x,
672             params =>
673             [qw( year month day hour minute second nanosecond )],
674             extra => { time_zone => 'floating' },
675             postprocess => [
676             \&_fractional_second,
677             ],
678             },
679             {
680             #YYYYMMDDThhmmssZ 19850412T101530Z
681             #YYYY-MM-DDThh:mm:ssZ 1985-04-12T10:15:30Z
682             length => [qw( 16 20 )],
683             regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d)
684             T (\d\d) :?? (\d\d) :?? (\d\d) Z $/x,
685             params => [qw( year month day hour minute second )],
686             extra => { time_zone => 'UTC' },
687             },
688             {
689             #YYYYMMDDThhmmss.ssZ 19850412T101530.5Z 20041020T101530.5Z
690             #YYYY-MM-DDThh:mm:ss.ssZ 1985-04-12T10:15:30.5Z 1985-04-12T10:15:30.5Z
691             regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d)
692             T?? (\d\d) :?? (\d\d) :?? (\d\d) [\.,] (\d+)
693             Z$/x,
694             params =>
695             [qw( year month day hour minute second nanosecond )],
696             extra => { time_zone => 'UTC' },
697             postprocess => [
698             \&_fractional_second,
699             ],
700             },
701             {
702             #YYYYMMDDThhmm[+-]hhmm 19850412T1015+0400
703             #YYYY-MM-DDThh:mm[+-]hh:mm 1985-04-12T10:15+04:00
704             length => [qw( 18 22 )],
705             regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d)
706             T (\d\d) :?? (\d\d) ([+-] \d\d :?? \d\d) $/x,
707             params => [qw( year month day hour minute time_zone )],
708             postprocess => \&_normalize_offset,
709             },
710             {
711             #YYYYMMDDThhmmss[+-]hhmm 19850412T101530+0400
712             #YYYY-MM-DDThh:mm:ss[+-]hh:mm 1985-04-12T10:15:30+04:00
713             length => [qw( 20 24 25 )],
714             regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d)
715             T (\d\d) :?? (\d\d) :?? (\d\d) ([+-] \d\d :?? \d\d) $/x,
716             params => [qw( year month day hour minute second time_zone )],
717             postprocess => \&_normalize_offset,
718             },
719             {
720             #YYYYMMDDThhmmss.ss[+-]hhmm 19850412T101530.5+0100 20041020T101530.5-0500
721             regex => qr/^ (\d{4}) (\d\d) (\d\d)
722             T?? (\d\d) (\d\d) (\d\d) [\.,] (\d+)
723             ([+-] \d\d \d\d) $/x,
724             params => [
725             qw( year month day hour minute second nanosecond time_zone )
726             ],
727             postprocess => [
728             \&_fractional_second,
729             \&_normalize_offset,
730             ],
731             },
732             {
733             #YYYY-MM-DDThh:mm:ss.ss[+-]hh 1985-04-12T10:15:30.5+01 1985-04-12T10:15:30.5-05
734             regex => qr/^ (\d{4}) - (\d\d) - (\d\d)
735             T?? (\d\d) : (\d\d) : (\d\d) [\.,] (\d+)
736             ([+-] \d\d ) $/x,
737             params => [
738             qw( year month day hour minute second nanosecond time_zone )
739             ],
740             postprocess => [
741             \&_fractional_second,
742             \&_normalize_offset,
743             ],
744             },
745             {
746             #YYYYMMDDThhmmss.ss[+-]hh 19850412T101530.5+01 20041020T101530.5-05
747             regex => qr/^ (\d{4}) (\d\d) (\d\d)
748             T?? (\d\d) (\d\d) (\d\d) [\.,] (\d+)
749             ([+-] \d\d ) $/x,
750             params => [
751             qw( year month day hour minute second nanosecond time_zone )
752             ],
753             postprocess => [
754             \&_fractional_second,
755             \&_normalize_offset,
756             ],
757             },
758             {
759             #YYYY-MM-DDThh:mm:ss.ss[+-]hh:mm 1985-04-12T10:15:30.5+01:00 1985-04-12T10:15:30.5-05:00
760             regex => qr/^ (\d{4}) - (\d\d) - (\d\d)
761             T?? (\d\d) : (\d\d) : (\d\d) [\.,] (\d+)
762             ([+-] \d\d : \d\d) $/x,
763             params => [
764             qw( year month day hour minute second nanosecond time_zone )
765             ],
766             postprocess => [
767             \&_fractional_second,
768             \&_normalize_offset,
769             ],
770             },
771              
772             {
773             #YYYYMMDDThhmmss[+-]hh 19850412T101530+04
774             #YYYY-MM-DDThh:mm:ss[+-]hh 1985-04-12T10:15:30+04
775             length => [qw( 18 22 )],
776             regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d)
777             T (\d\d) :?? (\d\d) :?? (\d\d) ([+-] \d\d) $/x,
778             params => [qw( year month day hour minute second time_zone )],
779             postprocess => \&_normalize_offset,
780             },
781             {
782             #YYYYMMDDThhmm 19850412T1015
783             #YYYY-MM-DDThh:mm 1985-04-12T10:15
784             length => [qw( 13 16 )],
785             regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d)
786             T (\d\d) :?? (\d\d) $/x,
787             params => [qw( year month day hour minute )],
788             extra => { time_zone => 'floating' },
789             },
790             {
791             #YYYYMMDDThhmmZ 19850412T1015
792             #YYYY-MM-DDThh:mmZ 1985-04-12T10:15
793             length => [qw( 14 17 )],
794             regex => qr/^ (\d{4}) -?? (\d\d) -?? (\d\d)
795             T (\d\d) :?? (\d\d) Z $/x,
796             params => [qw( year month day hour minute )],
797             extra => { time_zone => 'UTC' },
798             },
799             {
800             #YYYYDDDThhmm 1985102T1015
801             #YYYY-DDDThh:mm 1985-102T10:15
802             length => [qw( 12 14 )],
803             regex => qr/^ (\d{4}) -?? (\d{3}) T
804             (\d\d) :?? (\d\d) $/x,
805             params => [qw( year day_of_year hour minute )],
806             extra => { time_zone => 'floating' },
807             constructor => [ 'DateTime', 'from_day_of_year' ],
808             },
809             {
810             #YYYYDDDThhmmZ 1985102T1015Z
811             #YYYY-DDDThh:mmZ 1985-102T10:15Z
812             length => [qw( 13 15 )],
813             regex => qr/^ (\d{4}) -?? (\d{3}) T
814             (\d\d) :?? (\d\d) Z $/x,
815             params => [qw( year day_of_year hour minute )],
816             extra => { time_zone => 'UTC' },
817             constructor => [ 'DateTime', 'from_day_of_year' ],
818              
819             },
820             {
821             #YYYYWwwDThhmm[+-]hhmm 1985W155T1015+0400
822             #YYYY-Www-DThh:mm[+-]hh 1985-W15-5T10:15+04
823             length => [qw( 18 19 )],
824             regex => qr/^ (\d{4}) -?? W (\d\d) -?? (\d)
825             T (\d\d) :?? (\d\d) ([+-] \d{2,4}) $/x,
826             params => [qw( year week day_of_week hour minute time_zone)],
827             postprocess => [ \&_normalize_week, \&_normalize_offset ],
828             constructor => [ 'DateTime', 'from_day_of_year' ],
829             },
830             ],
831             parse_time => [
832             {
833             #hhmmss 232050
834             length => [qw( 6 7 )],
835             regex => qr/^ T?? (\d\d) (\d\d) (\d\d) $/x,
836             params => [qw( hour minute second )],
837             postprocess => [
838             \&_add_year,
839             \&_add_month,
840             \&_add_day,
841             ],
842             },
843             {
844             #hhmm 2320
845             length => [qw( 4 5 )],
846             regex => qr/^ T?? (\d\d) (\d\d) $/x,
847             params => [qw( hour minute )],
848             postprocess => [
849             \&_add_year,
850             \&_add_month,
851             \&_add_day,
852             ],
853             },
854             {
855             #hh 23
856             length => [qw( 2 3 )],
857             regex => qr/^ T?? (\d\d) $/x,
858             params => [qw( hour )],
859             postprocess => [
860             \&_add_year,
861             \&_add_month,
862             \&_add_day,
863             ],
864             },
865             {
866             #-mmss -2050
867             length => 5,
868             regex => qr/^ - (\d\d) (\d\d) $/x,
869             params => [qw( minute second )],
870             postprocess => [
871             \&_add_year,
872             \&_add_month,
873             \&_add_day,
874             \&_add_hour,
875             ],
876             },
877             {
878             #-mm -20
879             length => 3,
880             regex => qr/^ - (\d\d) $/x,
881             params => [qw( minute )],
882             postprocess => [
883             \&_add_year,
884             \&_add_month,
885             \&_add_day,
886             \&_add_hour,
887             ],
888             },
889             {
890             #--ss --50
891             length => 4,
892             regex => qr/^ -- (\d\d) $/x,
893             params => [qw( second )],
894             postprocess => [
895             \&_add_year,
896             \&_add_month,
897             \&_add_day,
898             \&_add_hour,
899             \&_add_minute,
900             ],
901             },
902             ],
903             }
904             );
905              
906             sub _fix_1_digit_year {
907 13     13   31426 my %p = @_;
908              
909 13         64 my $year = _base_dt( $p{self} )->year;
910              
911 13         2992 $year =~ s/.$//;
912 13         40 $p{parsed}{year} = $year . $p{parsed}{year};
913              
914 13         46 return 1;
915             }
916              
917             sub _fix_2_digit_year {
918 21033     21033   20887197 my %p = @_;
919              
920             # this is a mess because of the need to support parse_* being called
921             # as a class method
922 21033 100 66     120418 if ( ref $p{self} && exists $p{self}{legacy_year} ) {
923 11022 100       31734 if ( $p{self}{legacy_year} ) {
924             my $cutoff
925             = exists $p{self}{cut_off_year}
926             ? $p{self}{cut_off_year}
927 10022 50       26803 : $p{self}->DefaultCutOffYear;
928 10022 100       42404 $p{parsed}{year} += $p{parsed}{year} > $cutoff ? 1900 : 2000;
929             }
930             else {
931 1000   33     5125 my $century = ( $p{self}{base_datetime} || DateTime->now )
932             ->strftime('%C');
933 1000         44877 $p{parsed}{year} += $century * 100;
934             }
935             }
936             else {
937             my $cutoff
938             = ref $p{self} && exists $p{self}{cut_off_year}
939             ? $p{self}{cut_off_year}
940 10011 50 33     52104 : $p{self}->DefaultCutOffYear;
941 10011 100       48309 $p{parsed}{year} += $p{parsed}{year} > $cutoff ? 1900 : 2000;
942             }
943              
944 21033         77751 return 1;
945             }
946              
947             sub _add_minute {
948 11     11   139 my %p = @_;
949              
950 11         28 $p{parsed}{minute} = _base_dt( $p{self} )->minute;
951              
952 11         1311 return 1;
953             }
954              
955             sub _add_hour {
956 29     29   243 my %p = @_;
957              
958 29         60 $p{parsed}{hour} = _base_dt( $p{self} )->hour;
959              
960 29         5047 return 1;
961             }
962              
963             sub _add_day {
964 119     119   1065 my %p = @_;
965              
966 119         224 $p{parsed}{day} = _base_dt( $p{self} )->day;
967              
968 119         23489 return 1;
969             }
970              
971             sub _add_week {
972 8     8   65 my %p = @_;
973              
974 8         17 $p{parsed}{week} = _base_dt( $p{self} )->week;
975              
976 8         896 return 1;
977             }
978              
979             sub _add_month {
980 122     122   1139 my %p = @_;
981              
982 122         253 $p{parsed}{month} = _base_dt( $p{self} )->month;
983              
984 122         24540 return 1;
985             }
986              
987             sub _add_year {
988 145     145   406613 my %p = @_;
989              
990 145         404 $p{parsed}{year} = _base_dt( $p{self} )->year;
991              
992 145         31385 return 1;
993             }
994              
995             sub _base_dt {
996 447 100 100 447   2177 return $_[0]{base_datetime} if ref $_[0] && $_[0]{base_datetime};
997 275         1014 return DateTime->now;
998             }
999              
1000             sub _fractional_second {
1001 71     71   116273 my %p = @_;
1002              
1003 71         152 my $len = length( $p{parsed}{nanosecond} );
1004             $p{parsed}{nanosecond}
1005 71 100       324 = $p{parsed}{nanosecond} . ( '0' x ( $len >= 9 ? 0 : 9 - $len ) );
1006              
1007             # substr knowingly truncates sub-nanosecond values
1008 71         194 $p{parsed}{nanosecond} = substr( $p{parsed}{nanosecond}, 0, 9 );
1009              
1010 71         176 $p{parsed}{nanosecond} = int( $p{parsed}{nanosecond} );
1011              
1012 71         174 return 1;
1013             }
1014              
1015             sub _fractional_minute {
1016 9     9   105 my %p = @_;
1017              
1018             ## no critic (ValuesAndExpressions::ProhibitMismatchedOperators)
1019 9         68 $p{parsed}{second} = ".$p{ parsed }{ second }" * 60;
1020              
1021 9         21 return 1;
1022             }
1023              
1024             sub _fractional_hour {
1025 3     3   27 my %p = @_;
1026              
1027             ## no critic (ValuesAndExpressions::ProhibitMismatchedOperators)
1028 3         21 $p{parsed}{minute} = ".$p{ parsed }{ minute }" * 60;
1029              
1030 3         8 return 1;
1031             }
1032              
1033             sub _normalize_offset {
1034 81     81   60203 my %p = @_;
1035              
1036 81         247 $p{parsed}{time_zone} =~ s/://;
1037              
1038 81 100       260 if ( length $p{parsed}{time_zone} == 3 ) {
1039 27         54 $p{parsed}{time_zone} .= '00';
1040             }
1041              
1042 81         182 return 1;
1043             }
1044              
1045             sub _normalize_week {
1046 3981     3981   19651158 my %p = @_;
1047              
1048             # See
1049             # https://en.wikipedia.org/wiki/ISO_week_date#Calculating_an_ordinal_or_month_date_from_a_week_date
1050             # for the algorithm we're using here.
1051 3981         17711 my $od = $p{parsed}{week} * 7;
1052 3981 100       19245 $od += ( exists $p{parsed}{day_of_week} ? $p{parsed}{day_of_week} : 1 );
1053             $od -= DateTime->new(
1054             year => $p{parsed}{year},
1055 3981         20173 month => 1,
1056             day => 4,
1057             )->day_of_week + 3;
1058              
1059 3981         1579355 my ( $year, $day_of_year );
1060 3981 100       11950 if ( $od < 1 ) {
1061 20         63 $year = $p{parsed}{year} - 1;
1062 20         58 $day_of_year = DateTime->new( year => $year )->year_length + $od;
1063             }
1064             else {
1065             my $year_length
1066 3961         16578 = DateTime->new( year => $p{parsed}{year} )->year_length;
1067 3961 100       1490274 if ( $od > $year_length ) {
1068 6         23 $year = $p{parsed}{year} + 1;
1069 6         15 $day_of_year = $od - $year_length;
1070             }
1071             else {
1072 3955         11775 $year = $p{parsed}{year};
1073 3955         8667 $day_of_year = $od;
1074             }
1075             }
1076              
1077             # We need to leave the references in $p{parsed} as is. We cannot create a
1078             # new reference.
1079 3981         14206 $p{parsed}{year} = $year;
1080 3981         9216 $p{parsed}{day_of_year} = $day_of_year;
1081              
1082 3981         9517 delete $p{parsed}{week};
1083 3981         7373 delete $p{parsed}{day_of_week};
1084              
1085 3981         18354 return 1;
1086             }
1087              
1088             sub _normalize_century {
1089 6     6   92475 my %p = @_;
1090              
1091 6         24 $p{parsed}{year} .= '01';
1092              
1093 6         25 return 1;
1094             }
1095              
1096             1;
1097              
1098             # ABSTRACT: Parses ISO8601 formats
1099              
1100             __END__
1101              
1102             =pod
1103              
1104             =encoding UTF-8
1105              
1106             =head1 NAME
1107              
1108             DateTime::Format::ISO8601 - Parses ISO8601 formats
1109              
1110             =head1 VERSION
1111              
1112             version 0.19
1113              
1114             =head1 SYNOPSIS
1115              
1116             use DateTime::Format::ISO8601;
1117              
1118             my $datetime_str = '2020-07-25T11:32:31';
1119             my $dt = DateTime::Format::ISO8601->parse_datetime($datetime_str);
1120             say $dt;
1121              
1122             # This format is ambiguous and could be either a date or time, so use the
1123             # parse_time method.
1124             my $time_str = '113231';
1125             $dt = DateTime::Format::ISO8601->parse_time($time_str);
1126             say $dt;
1127              
1128             # or
1129              
1130             my $iso8601 = DateTime::Format::ISO8601->new;
1131             $dt = $iso8601->parse_datetime($datetime_str);
1132             say $dt;
1133              
1134             $dt = $iso8601->parse_time($time_str);
1135             say $dt;
1136              
1137             say DateTime::Format::ISO8601->format_datetime($dt);
1138              
1139             =head1 DESCRIPTION
1140              
1141             Parses almost all ISO8601 date and time formats. ISO8601 time-intervals will be
1142             supported in a later release.
1143              
1144             =head1 METHODS
1145              
1146             This class provides the following methods:
1147              
1148             =head2 Constructors
1149              
1150             =head3 DateTime::Format::ISO8601->new( ... )
1151              
1152             Accepts an optional hash.
1153              
1154             my $iso8601 = DateTime::Format::ISO8601->new(
1155             base_datetime => $dt,
1156             cut_off_year => 42,
1157             legacy_year => 1,
1158             );
1159              
1160             =over 4
1161              
1162             =item * base_datetime
1163              
1164             A C<DateTime> object that will be used to fill in missing information from
1165             incomplete date/time formats.
1166              
1167             This key is optional.
1168              
1169             =item * cut_off_year
1170              
1171             A integer representing the cut-off point between interpreting 2-digits years as
1172             19xx or 20xx.
1173              
1174             2-digit years < cut_off_year will be interpreted as 20xx
1175             2-digit years >= cut_off_year will be untreated as 19xx
1176              
1177             This key defaults to the value of C<DefaultCutOffYear>.
1178              
1179             =item * legacy_year
1180              
1181             A boolean value controlling if a 2-digit year is interpreted as being in the
1182             current century (unless a C<base_datetime> is set) or if C<cut_off_year> should
1183             be used to place the year in either 20xx or 19xx.
1184              
1185             If this is true, then the C<cut_off_year> is used. If this is false, then the
1186             year is always interpreted as being in the current century.
1187              
1188             This key defaults to the value of C<DefaultLegacyYear>.
1189              
1190             =back
1191              
1192             =head3 $iso8601->clone
1193              
1194             Returns a replica of the given object.
1195              
1196             =head2 Object Methods
1197              
1198             =head3 $iso8601->base_datetime
1199              
1200             Returns a C<DateTime> object if a C<base_datetime> has been set.
1201              
1202             =head3 $iso8601->set_base_datetime( object => $object )
1203              
1204             Accepts a C<DateTime> object that will be used to fill in missing information
1205             from incomplete date/time formats.
1206              
1207             =head3 $iso8601->cut_off_year
1208              
1209             Returns a integer representing the cut-off point between interpreting 2-digits
1210             years as 19xx or 20xx.
1211              
1212             =head3 $iso8601->set_cut_off_year($int)
1213              
1214             Accepts a integer representing the cut-off point between interpreting 2-digits
1215             years as 19xx or 20xx.
1216              
1217             2-digit years < legacy_year will be interpreted as 20xx
1218             2-digit years >= legacy_year will be interpreted as 19xx
1219              
1220             =head3 $iso8601->legacy_year
1221              
1222             Returns a boolean value indicating the 2-digit year handling behavior.
1223              
1224             =head3 $iso8601->set_legacy_year($bool)
1225              
1226             Accepts a boolean value controlling if a 2-digit year is interpreted as being
1227             in the current century (unless a C<base_datetime> is set) or if C<cut_off_year>
1228             should be used to place the year in either 20xx or 19xx.
1229              
1230             =head2 Class Methods
1231              
1232             =head3 DateTime::Format::ISO8601->DefaultCutOffYear($int)
1233              
1234             Accepts a integer representing the cut-off point for 2-digit years when calling
1235             C<parse_*> as class methods and the default value for C<cut_off_year> when
1236             creating objects. If called with no parameters this method will return the
1237             default value for C<cut_off_year>.
1238              
1239             =head3 DateTime::Format::ISO8601->DefaultLegacyYear($bool)
1240              
1241             Accepts a boolean value controlling the legacy year behavior when calling
1242             C<parse_*> as class methods and the default value for C<legacy_year> when
1243             creating objects. If called with no parameters this method will return the
1244             default value for C<legacy_year>.
1245              
1246             =head2 Parser(s)
1247              
1248             These methods may be called as either class or object methods.
1249              
1250             =head3 parse_datetime
1251              
1252             =head3 parse_time
1253              
1254             Please see the L</FORMATS> section.
1255              
1256             =head2 Formatter
1257              
1258             This may be called as either class or object method.
1259              
1260             =head3 format_datetime($dt)
1261              
1262             Formats the datetime in an ISO8601-compatible format. This differs from
1263             L<DateTime/iso8601> by including nanoseconds/milliseconds and the correct
1264             timezone offset.
1265              
1266             =head1 FORMATS
1267              
1268             There are 6 strings that can match against date only or time only formats. The
1269             C<parse_datetime> method will attempt to match these ambiguous strings against
1270             date only formats. If you want to match against the time only formats use the
1271             C<parse_time> method.
1272              
1273             =head2 Conventions
1274              
1275             =over 4
1276              
1277             =item * Expanded ISO8601
1278              
1279             These formats are supported with exactly 6 digits for the year. Support for a
1280             variable number of digits will be in a later release.
1281              
1282             =item * Precision
1283              
1284             If a format doesn't include a year all larger time unit up to and including the
1285             year are filled in using the current date/time or [if set] the C<base_datetime>
1286             object.
1287              
1288             =item * Fractional time
1289              
1290             There is no limit on the expressed precision.
1291              
1292             B<Note:> sub-nanosecond times are truncated.
1293              
1294             =back
1295              
1296             =head2 Supported via parse_datetime
1297              
1298             The supported formats are listed by the section of ISO 8601:2000(E) in which
1299             they appear.
1300              
1301             =head3 5.2 Dates
1302              
1303             =over 4
1304              
1305             =item * 5.2.1.1
1306              
1307             =over 8
1308              
1309             =item YYYYMMDD
1310              
1311             =item YYYY-MM-DD
1312              
1313             =back
1314              
1315             =item * 5.2.1.2
1316              
1317             =over 8
1318              
1319             =item YYYY-MM
1320              
1321             =item YYYY
1322              
1323             =item YY
1324              
1325             =back
1326              
1327             =item * 5.2.1.3
1328              
1329             =over 8
1330              
1331             =item YYMMDD
1332              
1333             =item YY-MM-DD
1334              
1335             =item -YYMM
1336              
1337             =item -YY-MM
1338              
1339             =item -YY
1340              
1341             =item --MMDD
1342              
1343             =item --MM-DD
1344              
1345             =item --MM
1346              
1347             =item ---DD
1348              
1349             =back
1350              
1351             =item * 5.2.1.4
1352              
1353             =over 8
1354              
1355             =item +[YY]YYYYMMDD
1356              
1357             =item +[YY]YYYY-MM-DD
1358              
1359             =item +[YY]YYYY-MM
1360              
1361             =item +[YY]YYYY
1362              
1363             =item +[YY]YY
1364              
1365             =back
1366              
1367             =item * 5.2.2.1
1368              
1369             =over 8
1370              
1371             =item YYYYDDD
1372              
1373             =item YYYY-DDD
1374              
1375             =back
1376              
1377             =item * 5.2.2.2
1378              
1379             =over 8
1380              
1381             =item YYDDD
1382              
1383             =item YY-DDD
1384              
1385             =item -DDD
1386              
1387             =back
1388              
1389             =item * 5.2.2.3
1390              
1391             =over 8
1392              
1393             =item +[YY]YYYYDDD
1394              
1395             =item +[YY]YYYY-DDD
1396              
1397             =back
1398              
1399             =item * 5.2.3.1
1400              
1401             =over 8
1402              
1403             =item YYYYWwwD
1404              
1405             =item YYYY-Www-D
1406              
1407             =back
1408              
1409             =item * 5.2.3.2
1410              
1411             =over 8
1412              
1413             =item YYYYWww
1414              
1415             =item YYYY-Www
1416              
1417             =item YYWwwD
1418              
1419             =item YY-Www-D
1420              
1421             =item YYWww
1422              
1423             =item YY-Www
1424              
1425             =item -YWwwD
1426              
1427             =item -Y-Www-D
1428              
1429             =item -YWww
1430              
1431             =item -Y-Www
1432              
1433             =item -WwwD
1434              
1435             =item -Www-D
1436              
1437             =item -Www
1438              
1439             =item -W-D
1440              
1441             =back
1442              
1443             =item * 5.2.3.4
1444              
1445             =over 8
1446              
1447             =item +[YY]YYYYWwwD
1448              
1449             =item +[YY]YYYY-Www-D
1450              
1451             =item +[YY]YYYYWww
1452              
1453             =item +[YY]YYYY-Www
1454              
1455             =back
1456              
1457             =back
1458              
1459             =head3 5.3 Time of Day
1460              
1461             =over 4
1462              
1463             =item * 5.3.1.1 - 5.3.1.3
1464              
1465             Values can optionally be prefixed with 'T'.
1466              
1467             =item * 5.3.1.1
1468              
1469             =over 8
1470              
1471             =item hh:mm:ss
1472              
1473             =back
1474              
1475             =item * 5.3.1.2
1476              
1477             =over 8
1478              
1479             =item hh:mm
1480              
1481             =back
1482              
1483             =item * 5.3.1.3 - 5.3.1.4
1484              
1485             fractional (decimal) separator maybe either ',' or '.'
1486              
1487             =item * 5.3.1.3
1488              
1489             =over 8
1490              
1491             =item hhmmss,ss
1492              
1493             =item hh:mm:ss,ss
1494              
1495             =item hhmm,mm
1496              
1497             =item hh:mm,mm
1498              
1499             =item hh,hh
1500              
1501             =back
1502              
1503             =item * 5.3.1.4
1504              
1505             =over 8
1506              
1507             =item -mm:ss
1508              
1509             =item -mmss,s
1510              
1511             =item -mm:ss,s
1512              
1513             =item -mm,m
1514              
1515             =item --ss,s
1516              
1517             =back
1518              
1519             =item * 5.3.3 - 5.3.4.2
1520              
1521             Values can optionally be prefixed with 'T'.
1522              
1523             =item * 5.3.3
1524              
1525             =over 8
1526              
1527             =item hhmmssZ
1528              
1529             =item hh:mm:ssZ
1530              
1531             =item hhmmZ
1532              
1533             =item hh:mmZ
1534              
1535             =item hhZ
1536              
1537             =item hhmmss.ssZ
1538              
1539             =item hh:mm:ss.ssZ
1540              
1541             =back
1542              
1543             =item * 5.3.4.2
1544              
1545             =over 8
1546              
1547             =item hhmmss[+-]hhmm
1548              
1549             =item hh:mm:ss[+-]hh:mm
1550              
1551             =item hhmmss[+-]hh
1552              
1553             =item hh:mm:ss[+-]hh
1554              
1555             =item hhmmss.ss[+-]hhmm
1556              
1557             =item hh:mm:ss.ss[+-]hh:mm
1558              
1559             =back
1560              
1561             =back
1562              
1563             =head3 5.4 Combinations of date and time of day
1564              
1565             =over 4
1566              
1567             =item * 5.4.1
1568              
1569             =over 8
1570              
1571             =item YYYYMMDDThhmmss
1572              
1573             =item YYYY-MM-DDThh:mm:ss
1574              
1575             =item YYYYMMDDThhmmssZ
1576              
1577             =item YYYY-MM-DDThh:mm:ssZ
1578              
1579             =item YYYYMMDDThhmmss[+-]hhmm
1580              
1581             =item YYYY-MM-DDThh:mm:ss[+-]hh:mm
1582              
1583             =item YYYYMMDDThhmmss[+-]hh
1584              
1585             =item YYYY-MM-DDThh:mm:ss[+-]hh
1586              
1587             =back
1588              
1589             =item * 5.4.2
1590              
1591             =over 8
1592              
1593             =item YYYYMMDDThhmmss.ss
1594              
1595             =item YYYY-MM-DDThh:mm:ss.ss
1596              
1597             =item YYYYMMDDThhmmss.ss[+-]hh
1598              
1599             =item YYYY-MM-DDThh:mm:ss.ss[+-]hh
1600              
1601             =item YYYYMMDDThhmmss.ss[+-]hhmm
1602              
1603             =item YYYY-MM-DDThh:mm:ss.ss[+-]hh:mm
1604              
1605             =back
1606              
1607             =item * 5.4.3
1608              
1609             Support for this section is not complete.
1610              
1611             =over 8
1612              
1613             =item YYYYMMDDThhmm
1614              
1615             =item YYYY-MM-DDThh:mm
1616              
1617             =item YYYYMMDDThhmmZ
1618              
1619             =item YYYY-MM-DDThh:mmZ
1620              
1621             =item YYYYDDDThhmm
1622              
1623             =item YYYY-DDDThh:mm
1624              
1625             =item YYYYDDDThhmmZ
1626              
1627             =item YYYY-DDDThh:mmZ
1628              
1629             =item YYYYWwwDThhmm[+-]hhmm
1630              
1631             =item YYYY-Www-DThh:mm[+-]hh
1632              
1633             =back
1634              
1635             =back
1636              
1637             =head3 5.5 Time-Intervals
1638              
1639             These are not currently supported
1640              
1641             =head2 Supported via parse_time
1642              
1643             =head3 5.3.1.1 - 5.3.1.3
1644              
1645             Values can optionally be prefixed with 'T'.
1646              
1647             =over 4
1648              
1649             =item * 5.3.1.1
1650              
1651             =over 8
1652              
1653             =item hhmmss
1654              
1655             =back
1656              
1657             =item * 5.3.1.2
1658              
1659             =over 8
1660              
1661             =item hhmm
1662              
1663             =item hh
1664              
1665             =back
1666              
1667             =item * 5.3.1.4
1668              
1669             =over 8
1670              
1671             =item -mmss
1672              
1673             =item -mm
1674              
1675             =item --ss
1676              
1677             =back
1678              
1679             =back
1680              
1681             =head1 STANDARDS DOCUMENT
1682              
1683             =head2 Title
1684              
1685             ISO8601:2000(E)
1686             Data elements and interchange formats - information exchange -
1687             Representation of dates and times
1688             Second edition 2000-12-15
1689              
1690             =head2 Reference Number
1691              
1692             ISO/TC 154 N 362
1693              
1694             =head1 CREDITS
1695              
1696             Iain 'Spoon' Truskett (SPOON) who wrote L<DateTime::Format::Builder>. That has
1697             grown into I<The Vacuum Energy Powered C<Swiss Army> Katana> of date and time
1698             parsing. This module was inspired by and conceived in honor of Iain's work.
1699              
1700             Tom Phoenix (PHOENIX) and PDX.pm for helping me solve the ISO week conversion
1701             bug. Not by fixing the code but motivation me to fix it so I could participate
1702             in a game of C<Zendo>.
1703              
1704             Jonathan Leffler (JOHNL) for reporting a test bug.
1705              
1706             Kelly McCauley for a patch to add 8 missing formats.
1707              
1708             Alasdair Allan (AALLAN) for complaining about excessive test execution time.
1709              
1710             Everyone at the DateTime C<Asylum>.
1711              
1712             =head1 SEE ALSO
1713              
1714             =over 4
1715              
1716             =item *
1717              
1718             L<DateTime>
1719              
1720             =item *
1721              
1722             L<DateTime::Format::Builder>
1723              
1724             =back
1725              
1726             =head1 SUPPORT
1727              
1728             Bugs may be submitted at L<https://github.com/houseabsolute/DateTime-Format-ISO8601/issues>.
1729              
1730             =head1 SOURCE
1731              
1732             The source code repository for DateTime-Format-ISO8601 can be found at L<https://github.com/houseabsolute/DateTime-Format-ISO8601>.
1733              
1734             =head1 AUTHORS
1735              
1736             =over 4
1737              
1738             =item *
1739              
1740             Joshua Hoblitt <josh@hoblitt.com>
1741              
1742             =item *
1743              
1744             Dave Rolsky <autarch@urth.org>
1745              
1746             =back
1747              
1748             =head1 CONTRIBUTORS
1749              
1750             =for stopwords Doug Bell joe Liam Widdowson Thomas Klausner Timothy Legge William Ricker
1751              
1752             =over 4
1753              
1754             =item *
1755              
1756             Doug Bell <doug@preaction.me>
1757              
1758             =item *
1759              
1760             joe <draxil@gmail.com>
1761              
1762             =item *
1763              
1764             Liam Widdowson <lbw@telstra.com>
1765              
1766             =item *
1767              
1768             Thomas Klausner <domm@plix.at>
1769              
1770             =item *
1771              
1772             Timothy Legge <timlegge@gmail.com>
1773              
1774             =item *
1775              
1776             William Ricker <bill.n1vux@gmail.com>
1777              
1778             =back
1779              
1780             =head1 COPYRIGHT AND LICENSE
1781              
1782             This software is copyright (c) 2026 by Joshua Hoblitt.
1783              
1784             This is free software; you can redistribute it and/or modify it under
1785             the same terms as the Perl 5 programming language system itself.
1786              
1787             The full text of the license can be found in the
1788             F<LICENSE> file included with this distribution.
1789              
1790             =cut