File Coverage

blib/lib/DateTime/Format/RFC3339.pm
Criterion Covered Total %
statement 95 96 98.9
branch 36 42 85.7
condition 8 11 72.7
subroutine 15 15 100.0
pod 3 3 100.0
total 157 167 94.0


line stmt bran cond sub pod time code
1              
2             package DateTime::Format::RFC3339;
3              
4 3     3   1645458 use strict;
  3         12  
  3         142  
5 3     3   22 use warnings;
  3         6  
  3         224  
6              
7 3     3   682 use version; our $VERSION = qv( 'v1.10.0' );
  3         3377  
  3         27  
8              
9 3     3   360 use Carp qw( croak );
  3         9  
  3         302  
10 3     3   1144 use DateTime qw( );
  3         843570  
  3         105  
11              
12              
13 3     3   24 use constant FIRST_IDX => 0;
  3         14  
  3         350  
14 3     3   24 use constant IDX_FORMAT => FIRST_IDX + 0;
  3         7  
  3         293  
15 3     3   26 use constant IDX_DECIMALS => FIRST_IDX + 1;
  3         9  
  3         273  
16 3     3   19 use constant IDX_SEP => FIRST_IDX + 2;
  3         7  
  3         219  
17 3     3   20 use constant IDX_SEP_RE => FIRST_IDX + 3;
  3         6  
  3         197  
18 3     3   17 use constant IDX_UC_ONLY => FIRST_IDX + 4;
  3         6  
  3         169  
19 3     3   18 use constant NEXT_IDX => FIRST_IDX + 5;
  3         18  
  3         4000  
20              
21              
22             my $default_self;
23              
24              
25             sub new {
26 20     20 1 786041 my $class = shift;
27 20         63 my %opts = @_;
28              
29 20         46 my $decimals = delete( $opts{ decimals } );
30 20         47 my $sep = delete( $opts{ sep } );
31 20         52 my $sep_re = delete( $opts{ sep_re } );
32 20         34 my $uc_only = delete( $opts{ uc_only } );
33              
34 20   100     103 $sep //= "T";
35 20   66     88 $sep_re //= quotemeta( $sep );
36 20 50       43 $uc_only = $uc_only ? 1 : 0;
37              
38 20         43 my $self = bless( [], $class );
39              
40             #$self->[ IDX_FORMAT ] = undef;
41 20         57 $self->[ IDX_DECIMALS ] = $decimals;
42 20         35 $self->[ IDX_SEP ] = $sep;
43 20         40 $self->[ IDX_SEP_RE ] = $sep_re;
44 20         36 $self->[ IDX_UC_ONLY ] = $uc_only;
45              
46 20         69 return $self;
47             }
48              
49              
50             sub parse_datetime {
51 11     11 1 14293 my $self = shift;
52 11         23 my $str = shift;
53              
54 11 100 66     50 $self = $default_self //= $self->new()
55             if !ref( $self );
56              
57 11 50       43 $str = uc( $str )
58             if !$self->[ IDX_UC_ONLY ];
59              
60 11 100       367 my ( $Y, $M, $D ) = $str =~ s/^([0-9]{4})-([0-9]{2})-([0-9]{2})// ? ( 0+$1, 0+$2, 0+$3 ) : ()
    100          
61             or croak( "Incorrectly formatted date" );
62              
63 10 50       146 $str =~ s/^$self->[ IDX_SEP_RE ]//
64             or croak( "Incorrectly formatted datetime" );
65              
66 10 100       207 my ( $h, $m, $s ) = $str =~ s/^([0-9]{2}):([0-9]{2}):([0-9]{2})// ? ( 0+$1, 0+$2, 0+$3 ) : ()
    100          
67             or croak( "Incorrectly formatted time" );
68              
69 9 100       26 my $ns = $str =~ s/^\.([0-9]{1,9})[0-9]*// ? 0+substr( $1.( '0' x 8 ), 0, 9 ) : 0;
70              
71 9         17 my $tz;
72 9 100       33 if ( $str =~ s/^Z// ) { $tz = 'UTC'; }
  8 50       14  
73 0         0 elsif ( $str =~ s/^([+-])([0-9]{2}):([0-9]{2})// ) { $tz = "$1$2$3"; }
74 1         134 else { croak( "Incorrect or missing time zone offset" ); }
75              
76 8 100       162 $str =~ /^\z/
77             or croak( "Incorrectly formatted datetime" );
78              
79 7         37 return DateTime->new(
80             year => $Y,
81             month => $M,
82             day => $D,
83             hour => $h,
84             minute => $m,
85             second => $s,
86             nanosecond => $ns,
87             time_zone => $tz,
88             formatter => $self,
89             );
90             }
91              
92              
93             sub format_datetime {
94 20     20 1 23323 my $self = shift;
95 20         44 my $dt = shift;
96              
97 20 100 66     72 $self = $default_self //= $self->new()
98             if !ref( $self );
99              
100 20         47 my $format = $self->[ IDX_FORMAT ];
101 20 50       77 if ( !$format ) {
102 20         38 my $decimals = $self->[ IDX_DECIMALS ];
103 20         37 my $sep = $self->[ IDX_SEP ];
104              
105 20 50       53 $sep = "%%" if $sep eq "%";
106              
107 20 100       53 if ( defined( $decimals ) ) {
108 10 100       22 if ( $decimals ) {
109 9         33 $self->[ IDX_FORMAT ] = $format = "%Y-%m-%d${sep}%H:%M:%S.%${decimals}N";
110             } else {
111 1         4 $self->[ IDX_FORMAT ] = $format = "%Y-%m-%d${sep}%H:%M:%S";
112             }
113             } else {
114 10 100       31 if ( $dt->nanosecond() ) {
115 1         8 $format = "%Y-%m-%d${sep}%H:%M:%S.%9N";
116             } else {
117 9         65 $format = "%Y-%m-%d${sep}%H:%M:%S";
118             }
119             }
120             }
121              
122 20         35 my $tz;
123 20 100       64 if ( $dt->time_zone()->is_utc() ) {
124 16         172 $tz = 'Z';
125             } else {
126 4         44 my $secs = $dt->offset();
127              
128             # TODO Maybe we could cache this.
129             # There are only so many offests, and most
130             # programs probably only uses one or two.
131 4 100       529 my $sign = $secs < 0 ? '-' : '+'; $secs = abs( $secs );
  4         9  
132 4         12 my $mins = int( $secs / 60 ); $secs %= 60;
  4         8  
133 4         10 my $hours = int( $mins / 60 ); $mins %= 60;
  4         8  
134 4 100       13 if ( $secs ) {
135 1         7 ( $dt = $dt->clone() )
136             ->set_time_zone( 'UTC' );
137 1         346 $tz = 'Z';
138             } else {
139 3         17 $tz = sprintf( '%s%02d:%02d', $sign, $hours, $mins );
140             }
141             }
142              
143 20         69 return $dt->strftime( $format ) . $tz;
144             }
145              
146              
147             1;
148              
149              
150             __END__
151              
152             =head1 NAME
153              
154             DateTime::Format::RFC3339 - Parse and format RFC3339 datetime strings
155              
156              
157             =head1 VERSION
158              
159             Version 1.10.0
160              
161              
162             =head1 SYNOPSIS
163              
164             use DateTime::Format::RFC3339;
165              
166             my $format = DateTime::Format::RFC3339->new();
167             my $dt = $format->parse_datetime( '2002-07-01T13:50:05Z' );
168              
169             # 2002-07-01T13:50:05Z
170             say $format->format_datetime( $dt );
171              
172              
173             =head1 DESCRIPTION
174              
175             This module understands the RFC3339 date/time format, an ISO 8601 profile,
176             defined at L<http://tools.ietf.org/html/rfc3339>.
177              
178             It can be used to parse these formats in order to create the appropriate
179             objects.
180              
181              
182             =head1 CONSTRUCTOR
183              
184             =head2 new
185              
186             my $format = DateTime::Format::RFC3339->new();
187             my $format = DateTime::Format::RFC3339->new( %options );
188              
189             A number of options are supported:
190              
191             =over
192              
193             =item * decimals
194              
195             decimals => undef [default]
196             decimals => $decimals
197              
198             Date-time strings generated by <format_datetime> will have
199             this many decimals (an integer from zero to nine). If C<undef>,
200             zero will be used if the date-time has no decimals, nine otherwise.
201              
202             =item * sep
203              
204             sep => "T" [default]
205             sep => $sep
206              
207             =item * sep_re
208              
209             sep_re => $sep_re
210              
211             The spec allows for a separator other than "C<T>"
212             to be used between the date and the time.
213              
214             The string provided to the C<sep> option is used
215             when formatting date-time objects into strings, and
216             the regex pattern provided to the C<sep_re> option
217             is used when parsing strings into date-time objects.
218              
219             The default for C<sep_re> is a regex pattern that
220             matches the separator (which is "C<T>" by default).
221              
222             =item * uc_only
223              
224             uc_only => 0 [default]
225             uc_only => 1
226              
227             Only an uppercase date and time separator and an uppercase timezone offset "Z"
228             will be accepted by C<parse_datetime> when this option is true.
229              
230             =back
231              
232              
233             =head1 METHODS
234              
235             =head2 parse_datetime
236              
237             my $dt = DateTime::Format::RFC3339->parse_datetime( $string );
238             my $dt = $format->parse_datetime( $string );
239              
240             Given a RFC3339 datetime string, this method will return a new
241             L<DateTime> object.
242              
243             If given an improperly formatted string, this method will croak.
244              
245             For a more flexible parser, see L<DateTime::Format::ISO8601>.
246              
247              
248             =head2 format_datetime
249              
250             my $string = DateTime::Format::RFC3339->format_datetime( $dt );
251             my $string = $format->format_datetime( $dt );
252              
253             Given a L<DateTime> object, this methods returns a RFC3339 datetime
254             string.
255              
256              
257             =head1 SEE ALSO
258              
259             =over 4
260              
261             =item * L<DateTime>
262              
263             =item * L<DateTime::Format::ISO8601>
264              
265             =item * L<http://tools.ietf.org/html/rfc3339>, "Date and Time on the Internet: Timestamps"
266              
267             =back
268              
269              
270             =head1 DOCUMENTATION AND SUPPORT
271              
272             You can find documentation for this module with the perldoc command.
273              
274             perldoc DateTime::Format::RFC3339
275              
276             You can also find it online at this location:
277              
278             =over
279              
280             =item * L<https://metacpan.org/dist/Datetime-Format-RFC3339>
281              
282             =back
283              
284             If you need help, the following are great resources:
285              
286             =over
287              
288             =item * L<https://stackoverflow.com/|StackOverflow>
289              
290             =item * L<http://www.perlmonks.org/|PerlMonks>
291              
292             =item * You may also contact the author directly.
293              
294             =back
295              
296              
297             =head1 BUGS
298              
299             Please report any bugs or feature requests using L<https://github.com/ikegami/perl-Datetime-Format-RFC3339/issues>.
300             I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
301              
302              
303             =head1 REPOSITORY
304              
305             =over
306              
307             =item * Web: L<https://github.com/ikegami/perl-Datetime-Format-RFC3339>
308              
309             =item * git: L<https://github.com/ikegami/perl-Datetime-Format-RFC3339.git>
310              
311             =back
312              
313              
314             =head1 AUTHOR
315              
316             Eric Brine, C<< <ikegami@adaelis.com> >>
317              
318              
319             =head1 COPYRIGHT AND LICENSE
320              
321             No rights reserved.
322              
323             The author has dedicated the work to the Commons by waiving all of his
324             or her rights to the work worldwide under copyright law and all related or
325             neighboring legal rights he or she had in the work, to the extent allowable by
326             law.
327              
328             Works under CC0 do not require attribution. When citing the work, you should
329             not imply endorsement by the author.
330              
331              
332             =cut