File Coverage

blib/lib/DateTime/Format/W3CDTF.pm
Criterion Covered Total %
statement 55 61 90.1
branch 25 30 83.3
condition 8 9 88.8
subroutine 10 11 90.9
pod 4 4 100.0
total 102 115 88.7


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             DateTime::Format::W3CDTF - Parse and format W3CDTF datetime strings
4              
5             =head1 VERSION
6              
7             This document describes DateTime::Format::W3CDTF version 0.07_02
8              
9             =head1 SYNOPSIS
10              
11             use DateTime::Format::W3CDTF;
12              
13             my $w3c = DateTime::Format::W3CDTF->new(strict => 1);
14             my $dt = $w3c->parse_datetime( '2003-02-15T13:50:05-05:00' );
15              
16             # 2003-02-15T13:50:05-05:00
17             $w3c->format_datetime($dt);
18              
19             =head1 DESCRIPTION
20              
21             This module understands the W3CDTF date/time format, an ISO 8601 profile,
22             defined at http://www.w3.org/TR/NOTE-datetime. This format as the native
23             date format of RSS 1.0.
24              
25             It can be used to parse these formats in order to create the appropriate
26             objects.
27              
28             =cut
29              
30             package DateTime::Format::W3CDTF;
31              
32 3     3   740399 use v5.8;
  3         32  
33 3     3   17 use strict;
  3         7  
  3         76  
34 3     3   16 use warnings;
  3         6  
  3         103  
35              
36 3     3   24 use vars qw ($VERSION);
  3         5  
  3         197  
37              
38             $VERSION = '0.07_02';
39              
40 3     3   1931 use DateTime;
  3         1054594  
  3         133  
41 3     3   34 use DateTime::TimeZone;
  3         7  
  3         2171  
42              
43             sub new {
44 25     25 1 4863 my $class = shift;
45              
46 25         80 return bless {@_}, $class;
47             }
48              
49             sub parse_datetime {
50 34     34 1 19233 my ( $self, $date ) = @_;
51              
52             # Dummy self if called as class method.
53 34 100       114 $self = $self->new() unless ref $self;
54              
55 34         118 my @fields = qw/ year month day hour minute second fraction time_zone /;
56 34 100       363 my @values =
57             ( $date =~ /^(\d\d\d\d) # Year
58             (?:-(\d\d) # -Month
59             (?:-(\d\d) # -Day
60             (?:T
61             (\d\d):(\d\d) # Hour:Minute
62             (?:
63             :(\d\d) # :Second
64             (\.\d+)? # .Fractional_Second
65             )?
66             ( Z # UTC
67             | [+-]\d\d:\d\d # Hour:Minute TZ offset
68             (?::\d\d)? # :Second TZ offset
69             )?)?)?)?$/x )
70             or die "Invalid W3CDTF datetime string ($date)";
71 18         33 my %p;
72 18         50 for ( my $i=0; $i < @values; $i++ ) { # Oh how I wish Perl had zip
73 144 100       301 next unless defined $values[$i];
74 92         226 $p{$fields[$i]} = $values[$i];
75             }
76            
77             die "Invalid W3CDTF datetime string without timezone ($date)"
78 18 100 100     104 if $self->{strict} && $p{hour} && !$p{time_zone};
      100        
79              
80             ### support for YYYY-MM-DDT24:00:00 as a syntactic form for 00:00:00 on the day following YYYY-MM-DD
81             ### this is allowed in xsd dateTime syntactic forms, but not W3CDTF.
82             # my $next_day = 0;
83             # if (defined($p{hour}) and defined($p{minute}) and defined($p{second})) {
84             # if ($p{hour} eq '24') {
85             # if ($p{second} eq '00' and $p{minute} eq '00') {
86             # $p{hour} = '00';
87             # $next_day++;
88             # } else {
89             # die "Cannot use hour value '24' with non-zero minutes and seconds\n";
90             # }
91             # }
92             # }
93            
94 16 100       55 if ( !$p{time_zone} ) {
    100          
95 8         28 $p{time_zone} = 'floating';
96             } elsif ( $p{time_zone} eq 'Z' ) {
97 2         6 $p{time_zone} = 'UTC';
98             }
99              
100 16 100       70 if ( $p{fraction} ) {
101 2         20 $p{nanosecond} = $p{fraction} * 1_000_000_000;
102             delete $p{fraction}
103 2         5 }
104              
105 16         83 my $dt = DateTime->new( %p );
106             # if ($next_day) {
107             # $dt->add( day => 1 );
108             # }
109 16         7210 return $dt;
110             }
111              
112             sub format_datetime {
113 7     7 1 26867 my ( $self, $dt ) = @_;
114              
115             # Dummy self if called as class method.
116 7 50       26 $self = {} unless ref $self;
117              
118 7         19 my $base = sprintf(
119             '%04d-%02d-%02dT%02d:%02d:%02d',
120             $dt->year, $dt->month, $dt->day,
121             $dt->hour, $dt->minute, $dt->second
122             );
123              
124 7 50       190 if ( $dt->nanosecond ) {
125 0         0 my $secs = sprintf "%f", $dt->nanosecond / 1_000_000_000;
126 0         0 $secs =~ s/^0//;
127 0         0 $base .= $secs;
128             }
129              
130 7         49 my $tz = $dt->time_zone;
131              
132 7 50       41 return $base . 'Z' if $tz->is_utc;
133              
134 7         30 my $offset = $dt->offset;
135              
136 7 100 66     500 if ($tz->is_floating or !defined $offset) {
137             die qq[Strict W3CDTF format does not support "floating" timezones]
138 2 100       24 if $self->{strict};
139 1         4 return $base;
140             }
141              
142 5         39 return $base . _offset_as_string($offset);
143             }
144              
145             sub format_date {
146 0     0 1 0 my ( $self, $dt ) = @_;
147              
148 0         0 my $base = sprintf( '%04d-%02d-%02d', $dt->year, $dt->month, $dt->day );
149 0         0 return $base;
150             }
151              
152             # minor offset_as_string variant w/ :
153             #
154             sub _offset_as_string {
155 5     5   7 my $offset = shift;
156              
157 5 50       23 return undef unless defined $offset;
158              
159 5 100       16 my $sign = $offset < 0 ? '-' : '+';
160              
161 5         11 my $hours = $offset / ( 60 * 60 );
162 5         9 $hours = abs($hours) % 24;
163              
164 5         11 my $mins = ( $offset % ( 60 * 60 ) ) / 60;
165              
166 5         13 my $secs = $offset % 60;
167              
168             return (
169 5 50       37 $secs
170             ? sprintf( '%s%02d:%02d:%02d', $sign, $hours, $mins, $secs )
171             : sprintf( '%s%02d:%02d', $sign, $hours, $mins )
172             );
173             }
174              
175             1;
176              
177             __END__
178              
179             =head1 METHODS
180              
181             This API is currently experimental and may change in the future.
182              
183             =over 4
184              
185             =item * new()
186              
187             Returns a new W3CDTF parser object. Accepts a single C<strict> option:
188              
189             DateTime::Format::W3CDTF->new(strict => 1);
190              
191             If true, parse_datetime() and format_datetime() will only accept and
192             return strings in W3CDTF format, respectively. In particular, the
193             W3CDTF format requires all time components to have timezones.
194              
195             If false, timezones are optional.
196              
197             =item * parse_datetime($string)
198              
199             Given a W3CDTF datetime string, this method will return a new
200             C<DateTime> object.
201              
202             If given an improperly formatted string, this method may die.
203              
204             =item * format_datetime($datetime)
205              
206             Given a C<DateTime> object, this methods returns a W3CDTF datetime
207             string.
208              
209             NOTE: As of version 0.4, format_datetime no longer attempts to truncate
210             datetimes without a time component. This is due to the fact that C<DateTime>
211             doesn't distinguish between a date with no time component, and midnight.
212              
213             =item * format_date($datetime)
214              
215             Given a C<DateTime> object, return a W3CDTF datetime string without the time component.
216              
217             =back
218              
219             =head1 SUPPORT
220              
221             Support for this module is provided via the datetime@perl.org email
222             list. See http://datetime.perl.org/?MailingList for details.
223              
224             Please submit bugs to the CPAN RT system at
225             http://rt.cpan.org/NoAuth/ReportBug.html?Queue=datetime-format-w3cdtf or via
226             email at bug-datetime-format-w3cdtf@rt.cpan.org.
227              
228             =head1 AUTHOR
229              
230             Dave Rolsky E<lt>autarch@urth.orgE<gt>
231              
232             =head1 CREDITS
233              
234             This module is maintained by Gregory Todd Williams E<lt>gwilliams@cpan.orgE<gt>.
235             It was originally created by Kellan Elliott-McCrea E<lt>kellan@protest.netE<gt>.
236              
237             This module was inspired by L<DateTime::Format::ICal>
238              
239             =head1 COPYRIGHT
240              
241             Copyright (c) 2009 David Rolsky. All rights reserved. This
242             program is free software; you can redistribute it and/or modify it
243             under the same terms as Perl itself.
244              
245             Copyright (c) 2003 Kellan Elliott-McCrea
246              
247             Portions of the code in this distribution are derived from other
248             works. Please see the CREDITS file for more details.
249              
250             The full text of the license can be found in the LICENSE file included
251             with this module.
252              
253             =head1 SEE ALSO
254              
255             datetime@perl.org mailing list
256              
257             http://datetime.perl.org/
258              
259             =cut