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   45330 use Moose;
  2         976029  
  2         14  
3 2     2   16415 use namespace::autoclean;
  2         16701  
  2         10  
4              
5 2     2   120 use Class::Load qw( try_load_class );
  2         9  
  2         1637  
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.02
15              
16             =cut
17              
18             our $VERSION = '0.02';
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;
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 16     16   27 my $self = shift;
99              
100 16         740 foreach my $locale ( $self->locale, 'en' ) {
101 16         37 my $locale_class = 'DateTime::Format::Human::Duration::Simple::Locale::' . $locale;
102              
103 16 50       52 if ( try_load_class($locale_class) ) {
104 16         1198 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 16     16   25 my $self = shift;
136              
137 16         721 return $self->to - $self->from;
138             }
139              
140             =head2 formatted
141              
142             Returns the locale specific string describing the span of the duration in
143             question.
144              
145             =cut
146              
147             has 'formatted' => (
148             isa => 'Maybe[Str]',
149             is => 'ro',
150             lazy => 1,
151             builder => '_build_formatted',
152             );
153              
154             sub _build_formatted {
155 16     16   22 my $self = shift;
156              
157 16         787 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 16         751 $years = abs( $years );
162 16         21 $months = abs( $months );
163 16         22 $weeks = abs( $weeks );
164 16         19 $days = abs( $days );
165 16         17 $hours = abs( $hours );
166 16         17 $minutes = abs( $minutes );
167 16         17 $seconds = abs( $seconds );
168 16         17 $nanoseconds = abs( $nanoseconds );
169              
170             # Calculate the number of milliseconds.
171 16         20 my $milliseconds = 0;
172              
173 16 100       133 if ( $nanoseconds > 0 ) {
174 4         9 $milliseconds = int( $nanoseconds * 0.000001 );
175 4         6 $nanoseconds -= $milliseconds * 1_000_000;
176             }
177              
178 16 100       36 if ( $years > 0 ) {
179 7         16 $months = int( $months / $years );
180             }
181              
182 16 50       762 if ( my $locale_class = $self->locale_class ) {
183              
184             # Localize the units.
185 16         35 my @formatted = ();
186              
187 16 100       73 push( @formatted, $years . ' ' . $locale_class->get_translation_for_value('year', $years) ) if ( $years > 0 );
188 16 100       57 push( @formatted, $months . ' ' . $locale_class->get_translation_for_value('month', $months) ) if ( $months > 0 );
189 16 100       43 push( @formatted, $weeks . ' ' . $locale_class->get_translation_for_value('week', $weeks) ) if ( $weeks > 0 );
190 16 100       52 push( @formatted, $days . ' ' . $locale_class->get_translation_for_value('day', $days) ) if ( $days > 0 );
191 16 100       75 push( @formatted, $hours . ' ' . $locale_class->get_translation_for_value('hour', $hours) ) if ( $hours > 0 );
192 16 100       66 push( @formatted, $minutes . ' ' . $locale_class->get_translation_for_value('minute', $minutes) ) if ( $minutes > 0 );
193 16 100       81 push( @formatted, $seconds . ' ' . $locale_class->get_translation_for_value('second', $seconds) ) if ( $seconds > 0 );
194 16 100       54 push( @formatted, $milliseconds . ' ' . $locale_class->get_translation_for_value('millisecond', $seconds) ) if ( $milliseconds > 0 );
195 16 100       47 push( @formatted, $nanoseconds . ' ' . $locale_class->get_translation_for_value('nanosecond', $seconds) ) if ( $nanoseconds > 0 );
196              
197 16         51 my $and = $locale_class->get_translation_for_value( 'and' );
198 16         54 my $formatted = join( ', ', @formatted );
199              
200             # Use a serial comma?
201 16 100 100     726 if ( scalar(@formatted) == 2 || not $self->serial_comma ) {
202 8         85 $formatted =~ s/(.+),/$1 \Q$and\E/;
203             }
204             else {
205 8         48 $formatted =~ s/(.+,)/$1 \Q$and\E/;
206             }
207              
208             # Return.
209 16         795 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;