File Coverage

blib/lib/DateTime/Format/Duration/ISO8601.pm
Criterion Covered Total %
statement 55 56 98.2
branch 17 20 85.0
condition 7 12 58.3
subroutine 10 10 100.0
pod 3 4 75.0
total 92 102 90.2


line stmt bran cond sub pod time code
1             package DateTime::Format::Duration::ISO8601;
2              
3             our $DATE = '2017-08-02'; # DATE
4             our $VERSION = '0.005'; # VERSION
5              
6 1     1   467277 use 5.010001;
  1         3  
7 1     1   4 use strict;
  1         2  
  1         21  
8 1     1   4 use warnings;
  1         2  
  1         497  
9              
10             sub new {
11 7     7 1 17246 my ($class, %args) = @_;
12              
13             # Default error handler
14 7 100       20 unless (exists $args{ on_error }) {
15 6     3   22 $args{ on_error } = sub { die shift };
  3         23  
16             }
17              
18 7         21 return bless \%args, $class;
19             }
20              
21             sub format_duration {
22 5     5 1 410 my ($self, $dtdur) = @_;
23              
24 5 100       8 unless (eval { $dtdur->isa('DateTime::Duration') }) {
  5         24  
25 1         6 return $self->_error(sprintf
26             '"%s": not a DateTime::Duration instance',
27             $dtdur
28             );
29             }
30              
31 4         11 my ($y, $m, $w, $d, $H, $M, $S, $ns) = (
32             $dtdur->years,
33             $dtdur->months,
34             $dtdur->weeks,
35             $dtdur->days,
36             $dtdur->hours,
37             $dtdur->minutes,
38             $dtdur->seconds,
39             $dtdur->nanoseconds,
40             );
41              
42 4         765 $S += $ns / 1_000_000_000;
43 4         8 $d += $w * 7;
44              
45 4   33     19 my $has_date = $y || $m || $w || $d;
46 4   66     14 my $has_time = $H || $M || $S;
47              
48 4 100 100     15 return "PT0H0M0S" if !$has_date && !$has_time;
49              
50 3         26 join(
51             "",
52             "P",
53             ($y, "Y") x !!$y,
54             ($m, "M") x !!$m,
55             ($d, "D") x !!$d,
56             (
57             "T",
58             ($H, "H") x !!$H,
59             ($M, "M") x !!$M,
60             ($S, "S") x !!$S,
61             ) x !!$has_time,
62             );
63             }
64              
65             sub parse_duration {
66 6     6 1 29 my ($self, $duration_string) = @_;
67              
68 6         15 my $duration_args = $self->parse_duration_as_deltas($duration_string);
69              
70 5 50       16 return unless defined $duration_args;
71              
72 5 100       16 if ($duration_args->{ repeats }) {
73 1         7 return $self->_error(sprintf(
74             '"%s": duration repetitions are not supported',
75             $duration_string
76             ));
77             }
78              
79             # Convert ss.sss floating seconds to seconds and nanoseconds
80 3 100       7 if (exists $duration_args->{ seconds }) {
81 2         11 my ($seconds, $floating) = $duration_args->{ seconds } =~ qr{(?x)
82             ([0-9]+)
83             (\.[0-9]+)
84             };
85              
86 2 100       8 if ($floating) {
87 1         4 my $nanoseconds = $floating * 1_000_000_000;
88              
89 1         2 $duration_args->{ seconds } = $seconds;
90 1         9 $duration_args->{ nanoseconds } = $nanoseconds;
91             }
92             }
93              
94             # DateTime::Duration only accepts integer values
95 3         5 for my $field (keys %{ $duration_args }) {
  3         9  
96 10         19 $duration_args->{ $field } = int($duration_args->{ $field });
97             }
98              
99 3         7 return DateTime::Duration->new(%{ $duration_args });
  3         10  
100             }
101              
102             sub parse_duration_as_deltas {
103 6     6 0 11 my ($self, $duration_string) = @_;
104              
105 6 50       18 unless (defined $duration_string) {
106 0         0 return $self->_error('Duration string undefined');
107             }
108              
109 6         22 my $regex = qr{(?x)
110             ^
111             (?:(?<repeats>R(?<repetitions>[0-9]+)?))?
112             P
113             (?:(?<years>[0-9]+)Y)?
114             (?:(?<months>[0-9]+)M)?
115             (?:(?<days>[0-9]+)D)?
116             (?:T
117             (?:(?<hours>[0-9]+)H)?
118             (?:(?<minutes>[0-9]+)M)?
119             (?:(?<seconds>[0-9]+(?:\.([0-9]+))?)S)?
120             )?
121             $
122             };
123              
124 6 100       52 unless ($duration_string =~ $regex) {
125 2         19 return $self->_error(sprintf(
126             '"%s": not a valid ISO 8601 duration string',
127             $duration_string
128             ));
129             }
130              
131 1     1   234 my %fields = map { $_ => $+{ $_ } }
  1         303  
  1         109  
  11         44  
132 4         38 grep { defined $+{ $_ } }
  11         48  
133             keys %+;
134              
135 4         17 return \%fields;
136             }
137              
138             sub _error {
139 4     4   11 my ($self, @args) = @_;
140              
141 4 50 33     24 die @args unless ref $self and ref $self->{ on_error } eq 'CODE';
142              
143 4         10 return $self->{ on_error }->(@args);
144             }
145              
146             1;
147             # ABSTRACT: Format DateTime::Duration object as ISO8601 duration string
148              
149             __END__
150              
151             =pod
152              
153             =encoding UTF-8
154              
155             =head1 NAME
156              
157             DateTime::Format::Duration::ISO8601 - Format DateTime::Duration object as ISO8601 duration string
158              
159             =head1 VERSION
160              
161             This document describes version 0.005 of DateTime::Format::Duration::ISO8601 (from Perl distribution DateTime-Format-Duration-ISO8601), released on 2017-08-02.
162              
163             =head1 SYNOPSIS
164              
165             use DateTime::Format::Duration::ISO8601;
166              
167             my $format = DateTime::Format::Duration::ISO8601->new;
168             say $format->format_duration(
169             DateTime::Duration->new(years=>3, months=>5, seconds=>10),
170             ); # => P3Y5MT10S
171              
172             my $d = $format->parse_duration('P1Y1M1DT1H1M1S');
173             say $d->in_units('minutes'); # => 61
174              
175             =head1 DESCRIPTION
176              
177             This module formats and parses ISO 8601 durations to and from
178             L<DateTime::Duration> instances.
179              
180             ISO 8601 intervals are B<not> supported.
181              
182             =for Pod::Coverage ^(parse_duration_as_deltas)$
183              
184             =head1 METHODS
185              
186             =head2 new(C<%args>) => C<DateTime::Duration::Format::ISO8601>
187              
188             =head3 Arguments
189              
190             =over
191              
192             =item * on_error (C<CODE>, optional)
193              
194             Subroutine reference that will receive an error message if parsing fails.
195              
196             The default implementation simply C<die>s with the message.
197              
198             Set to C<undef> to disable error dispatching.
199              
200             =back
201              
202             =head2 format_duration (C<DateTime::Duration>) => C<string>
203              
204             =head2 parse_duration (C<string>) => C<DateTime::Duration>
205              
206             =head1 HOMEPAGE
207              
208             Please visit the project's homepage at L<https://metacpan.org/release/DateTime-Format-Duration-ISO8601>.
209              
210             =head1 SOURCE
211              
212             Source repository is at L<https://github.com/perlancar/perl-DateTime-Format-Duration-ISO8601>.
213              
214             =head1 BUGS
215              
216             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=DateTime-Format-Duration-ISO8601>
217              
218             When submitting a bug or request, please include a test-file or a
219             patch to an existing test-file that illustrates the bug or desired
220             feature.
221              
222             =head1 SEE ALSO
223              
224             L<DateTime::Format::ISO8601> to format L<DateTime> object into ISO8601 date/time
225             string. At the time of this writing, there is no support to format
226             L<DateTime::Duration> object, hence this module.
227              
228             L<DateTime::Format::Duration> to format DateTime::Duration object using
229             strftime-style formatting.
230              
231             =head1 AUTHOR
232              
233             perlancar <perlancar@cpan.org>
234              
235             =head1 COPYRIGHT AND LICENSE
236              
237             This software is copyright (c) 2017, 2016 by perlancar@cpan.org.
238              
239             This is free software; you can redistribute it and/or modify it under
240             the same terms as the Perl 5 programming language system itself.
241              
242             =cut