File Coverage

blib/lib/Time/Ago.pm
Criterion Covered Total %
statement 81 83 97.5
branch 43 50 86.0
condition 1 3 33.3
subroutine 15 15 100.0
pod 1 2 50.0
total 141 153 92.1


line stmt bran cond sub pod time code
1             package Time::Ago;
2             # ABSTRACT: Approximate duration in words
3              
4             # Port of Rails distance_of_time_in_words and time_ago_in_words
5             # http://apidock.com/rails/v4.2.1/ActionView/Helpers/DateHelper/distance_of_time_in_words
6              
7 1     1   1235 use strict;
  1         3  
  1         48  
8 1     1   5 use warnings;
  1         2  
  1         45  
9 1     1   919 use utf8;
  1         12  
  1         6  
10 1     1   34 use Carp;
  1         2  
  1         103  
11 1     1   737 use Encode;
  1         11731  
  1         131  
12 1     1   744 use Locale::Messages qw/ bind_textdomain_filter /;
  1         6121  
  1         123  
13 1     1   793 use Locale::TextDomain 'Time-Ago';
  1         2874  
  1         9  
14 1     1   8708 use Scalar::Util qw/ blessed /;
  1         4  
  1         167  
15              
16             our $VERSION = '0.08';
17              
18             BEGIN {
19 1     1   12 $ENV{OUTPUT_CHARSET} = 'UTF-8';
20 1         7 bind_textdomain_filter 'Time-Ago' => \&Encode::decode_utf8;
21             }
22              
23             use constant {
24 1         1198 MINUTES_IN_QUARTER_YEAR => 131400, # 91.25 days
25             MINUTES_IN_THREE_QUARTERS_YEAR => 394200, # 273.75 days
26             MINUTES_IN_YEAR => 525600,
27 1     1   45 };
  1         2  
28              
29              
30             sub new {
31 1     1 0 507 my $class = shift;
32              
33 1   33     9 $class = ref($class) || $class;
34 1         4 my $self = bless {}, $class;
35              
36 1         5 while (@_) {
37 0         0 my ($method, $val) = splice @_, 0, 2;
38 0 0       0 $self->$method(ref $val eq 'ARRAY' ? @$val : $val);
39             }
40              
41 1         6 return $self;
42             }
43              
44              
45             {
46             my %locale = (
47             about_x_hours => sub {
48             __nx('about {count} hour', 'about {count} hours', $_, count => $_);
49             },
50              
51             about_x_months => sub {
52             __nx('about {count} month', 'about {count} months', $_, count => $_);
53             },
54              
55             about_x_years => sub {
56             __nx('about {count} year', 'about {count} years', $_, count => $_);
57             },
58              
59             almost_x_years => sub {
60             __nx('almost {count} year', 'almost {count} years', $_, count => $_);
61             },
62              
63             half_a_minute => sub { __('half a minute') },
64              
65             less_than_x_minutes => sub {
66             __nx('less than a minute', 'less than {count} minutes', $_, count => $_);
67             },
68              
69             less_than_x_seconds => sub {
70             __nx(
71             'less than {count} second',
72             'less than {count} seconds',
73             $_,
74             count => $_,
75             );
76             },
77              
78             over_x_years => sub {
79             __nx('over {count} year', 'over {count} years', $_, count => $_);
80             },
81              
82             x_days => sub {
83             __nx('{count} day', '{count} days', $_, count => $_);
84             },
85              
86             x_minutes => sub {
87             __nx('{count} minute', '{count} minutes', $_, count => $_);
88             },
89              
90             x_months => sub {
91             __nx('{count} month', '{count} months', $_, count => $_);
92             },
93             );
94              
95             sub _locale {
96 134     134   148 my $self = shift;
97              
98             return sub {
99 134 50   134   300 my $string_id = shift or croak 'no string id supplied';
100 134         277 my %args = @_;
101              
102 134 50       300 my $sub = $locale{ $string_id }
103             or croak "unknown locale string_id '$string_id'";
104              
105 134         157 local $_ = $args{count};
106 134         231 return $sub->();
107 134         433 };
108             }
109             }
110              
111              
112             sub in_words {
113 134     134 1 175905 my $self = shift;
114 134 50       562 my %args = (@_ % 2 ? (duration => @_) : @_);
115              
116 134 50       299 defined $args{duration} or croak 'no duration supplied';
117 134         132 my $duration = $args{duration};
118              
119 134 100       374 if (blessed $duration) {
120 2 100       16 if ($duration->can('epoch')) { # DateTime/Time::Piece-like object
    50          
121 1         4 $duration = time - $duration->epoch;
122             } elsif ($duration->can('delta_months')) { # DateTime::Duration-like
123             # yes, we're treating every month as 30.41 days
124 1         4 $duration = ($duration->delta_months * (86400 * 365 / 12)) +
125             ($duration->delta_days * 86400) +
126             ($duration->delta_minutes * 60) +
127             $duration->delta_seconds
128             ;
129             }
130             }
131              
132 134     278   490 my $round = sub { int($_[0] + 0.5) };
  278         470  
133              
134 134         144 $duration = abs $duration;
135 134         303 my $mins = $round->($duration / 60);
136 134         192 my $secs = $round->($duration);
137              
138 134         282 my $locale = $self->_locale;
139              
140 134 100       294 if ($mins <= 1) {
141 103 100       189 unless ($args{include_seconds}) {
142 91 100       218 return $mins == 0 ?
143             $locale->('less_than_x_minutes', count => 1) :
144             $locale->('x_minutes', count => $mins)
145             ;
146             }
147              
148 12 100       21 return $locale->('less_than_x_seconds', count => 5) if $secs <= 4;
149 10 100       17 return $locale->('less_than_x_seconds', count => 10) if $secs <= 9;
150 8 100       16 return $locale->('less_than_x_seconds', count => 20) if $secs <= 19;
151 6 100       13 return $locale->('half_a_minute', count => 20) if $secs <= 39;
152 4 100       9 return $locale->('less_than_x_minutes', count => 1) if $secs <= 59;
153 2         7 return $locale->('x_minutes', count => 1);
154             }
155              
156 31 100       103 return $locale->('x_minutes', count => $mins) if $mins <= 44;
157 27 100       48 return $locale->('about_x_hours', count => 1) if $mins <= 89;
158              
159             # 90 mins up to 24 hours
160 25 100       44 if ($mins <= 1439) {
161 2         8 return $locale->('about_x_hours', count => $round->($mins/60));
162             }
163              
164             # 24 hours up to 42 hours
165 23 100       46 return $locale->('x_days', count => 1) if $mins <= 2519;
166              
167             # 42 hours up to 30 days
168 21 100       42 return $locale->('x_days', count => $round->($mins / 1440)) if $mins <= 43199;
169              
170             # 30 days up to 60 days
171 19 100       30 if ($mins <= 86399) {
172 4         10 return $locale->('about_x_months', count => $round->($mins / 43200));
173             }
174              
175             # 60 days up to 365 days
176 15 100       28 if ($mins <= 525600) {
177 2         5 return $locale->('x_months', count => $round->($mins / 43200));
178             }
179              
180             # XXX does not implement leap year stuff that Rails implementation has
181              
182 13         16 my $remainder = $mins % MINUTES_IN_YEAR;
183 13         19 my $years = int($mins / MINUTES_IN_YEAR);
184              
185 13 100       31 if ($remainder < MINUTES_IN_QUARTER_YEAR) {
186 4         9 return $locale->('about_x_years', count => $years);
187             }
188              
189 9 100       19 if ($remainder < MINUTES_IN_THREE_QUARTERS_YEAR) {
190 4         9 return $locale->('over_x_years', count => $years);
191             }
192            
193 5         14 return $locale->('almost_x_years', count => $years + 1);
194             }
195              
196              
197             1;
198              
199             __END__