File Coverage

blib/lib/DateTime/Format/Human/Duration/Simple.pm
Criterion Covered Total %
statement 49 51 96.0
branch 26 28 92.8
condition 3 3 100.0
subroutine 6 6 100.0
pod n/a
total 84 88 95.4


line stmt bran cond sub pod time code
1             package DateTime::Format::Human::Duration::Simple;
2 2     2   49716 use Moose;
  2         990594  
  2         13  
3 2     2   15951 use namespace::autoclean;
  2         16988  
  2         10  
4              
5 2     2   121 use Class::Load qw( try_load_class );
  2         9  
  2         1693  
6              
7             =head1 NAME
8              
9             DateTime::Format::Human::Duration::Simple - Get a locale specific string
10             describing the span of a given datetime duration.
11              
12             =head1 VERSION
13              
14             Version 0.01
15              
16             =cut
17              
18             our $VERSION = '0.01';
19              
20             =head1 SYNOPSIS
21              
22             use DateTime::Format::Human::Duration::Simple;
23             use DateTime;
24              
25             my $from = DateTime->now;
26             my $to = $from->clone->add(
27             years => 1,
28             months => 2,
29             days => 3,
30             hours => 4,
31             minutes => 5,
32             seconds => 6,
33             );
34              
35             my $duration = DateTime::Format::Human::Duration::Simple->new(
36             from => $from, # required
37             to => $to, # required
38             # locale => 'en', # optional, default is 'en' (English)
39             # serial_comma => 1, # optional, default is 1 (true)
40             );
41              
42             say $duration->formatted_duration;
43             # 1 year, 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds
44              
45             =head1 DESCRIPTION
46              
47             This is a simple class for getting a localized string representing the duration
48             between two L<DateTime> objects.
49              
50             This class is inspired by L<DateTime::Format::Human::Duration>, and shares its
51             namespace. I feel, however, that L<DateTime::Format::Human::Duration> is a bit
52             "heavy", and it's not updated very often. I also don't like its interface, so I
53             created this class for an alternative that better suited me. If it will suit
54             others, I don't know (or care), but there's always nice with alternatives. :)
55              
56             =head1 METHODS
57              
58             =head2 new( %args )
59              
60             Constructs a new DateTime::Format::Human::Duration::Simple object;
61              
62             my $duration = DateTime::Format::Human::Duration::Simple->new(
63             from => DateTime->new( ... ), # required
64             to => DateTime->new( ... ), # required
65             locale => 'de', # optional (default = 'en')
66             # serial_comma => 1, # optional, default is 1 (true)
67             );
68              
69             =cut
70              
71             has 'from' => (
72             isa => 'DateTime',
73             is => 'ro',
74             required => 1,
75             );
76              
77             has 'to' => (
78             isa => 'DateTime',
79             is => 'ro',
80             required => 1,
81             );
82              
83             has 'locale' => (
84             isa => 'Str',
85             is => 'ro',
86             required => 1,
87             default => 'en',
88             );
89              
90             has 'locale_class' => (
91             isa => 'DateTime::Format::Human::Duration::Simple::Locale',
92             is => 'ro',
93             lazy => 1,
94             builder => '_build_locale_class',
95             );
96              
97             sub _build_locale_class {
98 15     15   22 my $self = shift;
99              
100 15         685 foreach my $locale ( $self->locale, 'en' ) {
101 15         32 my $locale_class = 'DateTime::Format::Human::Duration::Simple::Locale::' . $locale;
102              
103 15 50       51 if ( try_load_class($locale_class) ) {
104 15         1096 return $locale_class->new;
105             }
106             else {
107 0         0 warn "Failed to load localization class: " . $locale_class;
108             }
109             }
110              
111 0         0 die "Failed to create an instance of a localization class!";
112             }
113              
114             has 'serial_comma' => (
115             isa => 'Bool',
116             is => 'ro',
117             default => sub { shift->locale_class->serial_comma; }
118             );
119              
120             =head2 duration
121              
122             Returns the current L<DateTime::Duration> object, which is used by this class
123             behind the scenes to generate the localized output.
124              
125             =cut
126              
127             has 'duration' => (
128             isa => 'DateTime::Duration',
129             is => 'ro',
130             lazy => 1,
131             builder => '_build_duration',
132             );
133              
134             sub _build_duration {
135 15     15   22 my $self = shift;
136              
137 15         663 return $self->to - $self->from;
138             }
139              
140             =head2 formatted_duration
141              
142             Returns the locale specific string describing the span of the duration in
143             question.
144              
145             =cut
146              
147             has 'formatted_duration' => (
148             isa => 'Maybe[Str]',
149             is => 'ro',
150             lazy => 1,
151             builder => '_build_formatted_duration',
152             );
153              
154             sub _build_formatted_duration {
155 15     15   20 my $self = shift;
156              
157 15         689 my ( $years, $months, $weeks, $days, $hours, $minutes, $seconds, $nanoseconds ) = $self->duration->in_units( 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds', 'nanoseconds' );
158              
159             # Convert the values to absolute values in case there
160             # are negatives.
161 15         703 $years = abs( $years );
162 15         21 $months = abs( $months );
163 15         17 $weeks = abs( $weeks );
164 15         16 $days = abs( $days );
165 15         23 $hours = abs( $hours );
166 15         18 $minutes = abs( $minutes );
167 15         18 $seconds = abs( $seconds );
168 15         19 $nanoseconds = abs( $nanoseconds );
169              
170             # Calculate the number of milliseconds.
171 15         19 my $milliseconds = 0;
172              
173 15 100       38 if ( $nanoseconds > 0 ) {
174 4         6 $milliseconds = int( $nanoseconds * 0.000001 );
175 4         10 $nanoseconds -= $milliseconds * 1_000_000;
176             }
177              
178 15 100       39 if ( $years > 0 ) {
179 6         13 $months = int( $months / $years );
180             }
181              
182 15 50       712 if ( my $locale_class = $self->locale_class ) {
183              
184             # Localize the units.
185 15         27 my @formatted = ();
186              
187 15 100       67 push( @formatted, $years . ' ' . $locale_class->get_unit_for_value('year', $years) ) if ( $years > 0 );
188 15 100       54 push( @formatted, $months . ' ' . $locale_class->get_unit_for_value('month', $months) ) if ( $months > 0 );
189 15 100       36 push( @formatted, $weeks . ' ' . $locale_class->get_unit_for_value('week', $weeks) ) if ( $weeks > 0 );
190 15 100       56 push( @formatted, $days . ' ' . $locale_class->get_unit_for_value('day', $days) ) if ( $days > 0 );
191 15 100       68 push( @formatted, $hours . ' ' . $locale_class->get_unit_for_value('hour', $hours) ) if ( $hours > 0 );
192 15 100       62 push( @formatted, $minutes . ' ' . $locale_class->get_unit_for_value('minute', $minutes) ) if ( $minutes > 0 );
193 15 100       70 push( @formatted, $seconds . ' ' . $locale_class->get_unit_for_value('second', $seconds) ) if ( $seconds > 0 );
194 15 100       78 push( @formatted, $milliseconds . ' ' . $locale_class->get_unit_for_value('millisecond', $seconds) ) if ( $milliseconds > 0 );
195 15 100       45 push( @formatted, $nanoseconds . ' ' . $locale_class->get_unit_for_value('nanosecond', $seconds) ) if ( $nanoseconds > 0 );
196              
197 15         48 my $and = $locale_class->get_unit_for_value( 'and' );
198 15         53 my $formatted = join( ', ', @formatted );
199              
200             # Use a serial comma?
201 15 100 100     643 if ( scalar(@formatted) == 2 || not $self->serial_comma ) {
202 8         75 $formatted =~ s/(.+),/$1 \Q$and\E/;
203             }
204             else {
205 7         37 $formatted =~ s/(.+,)/$1 \Q$and\E/;
206             }
207              
208             # Return.
209 15         745 return $formatted;
210              
211             }
212             }
213              
214             =head1 What is the "serial comma"?
215              
216             The "serial comma", also called the "oxford comma", is an optional comma before
217             the word "and" (and/or other separating words) at the end of the list. Consider
218             not using a serial comma:
219              
220             1 hour, 2 minutes and 3 seconds
221              
222             ...vs. using a serial comma:
223              
224             1 hour, 2 minutes, and 3 seconds
225              
226             This value is defined per locale, i.e. from what's most normal (...) in each
227             locale, but you can override when generating an instance of this class;
228              
229             my $human = DateTime::Format::Human::Duration::Simple->new(
230             from => $from_datetime,
231             to => $to_datetime,
232             serial_comma => 0, # turn it off for all locales
233             );
234              
235             You can read more about the serial comma L<on Wikipedia|https://en.wikipedia.org/wiki/Serial_comma>.
236              
237             =head1 AUTHOR
238              
239             Tore Aursand, C<< <toreau at gmail.com> >>
240              
241             =head1 BUGS
242              
243             Please report any bugs or feature requests to the web interface at L<https://rt.cpan.org/Dist/Display.html?Name=DateTime-Format-Human-Duration-Simple>
244              
245             =head1 SUPPORT
246              
247             You can find documentation for this module with the perldoc command.
248              
249             perldoc DateTime-Format-Human-Duration-Simple
250              
251             You can also look for information at:
252              
253             =over 4
254              
255             =item * AnnoCPAN: Annotated CPAN documentation
256              
257             L<http://annocpan.org/dist/DateTime-Format-Human-Duration-Simple>
258              
259             =item * CPAN Ratings
260              
261             L<http://cpanratings.perl.org/d/DateTime-Format-Human-Duration-Simple>
262              
263             =item * Search CPAN
264              
265             L<http://search.cpan.org/dist/DateTime-Format-Human-Duration-Simple/>
266              
267             =back
268              
269             =head1 SEE ALSO
270              
271             =over 4
272              
273             =item * L<DateTime>
274              
275             =item * L<DateTime::Duration>
276              
277             =item * L<DateTime::Format::Human::Duration>
278              
279             =back
280              
281             =head1 LICENSE AND COPYRIGHT
282              
283             The MIT License (MIT)
284              
285             Copyright (c) 2015 Tore Aursand
286              
287             Permission is hereby granted, free of charge, to any person obtaining a copy
288             of this software and associated documentation files (the "Software"), to deal
289             in the Software without restriction, including without limitation the rights
290             to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
291             copies of the Software, and to permit persons to whom the Software is
292             furnished to do so, subject to the following conditions:
293              
294             The above copyright notice and this permission notice shall be included in all
295             copies or substantial portions of the Software.
296              
297             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
298             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
299             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
300             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
301             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
302             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
303             SOFTWARE.
304             =cut
305              
306             __PACKAGE__->meta->make_immutable;
307              
308             1;