File Coverage

blib/lib/Date/Period/Human.pm
Criterion Covered Total %
statement 57 83 68.6
branch 45 72 62.5
condition 28 46 60.8
subroutine 10 10 100.0
pod 2 2 100.0
total 142 213 66.6


line stmt bran cond sub pod time code
1             package Date::Period::Human;
2              
3 4     4   319209 use strict;
  4         32  
  4         117  
4 4     4   21 use warnings;
  4         7  
  4         94  
5 4     4   21 use Carp;
  4         18  
  4         283  
6              
7 4     4   1849 use Date::Calc qw/Delta_DHMS Today_and_Now N_Delta_YMDHMS/;
  4         32314  
  4         401  
8              
9             our $VERSION='0.4.7';
10              
11 4     4   2513 use utf8; # umlauts in translations
  4         60  
  4         21  
12              
13             our %translation = (
14             de => {
15             time_months_ago => 'vor %d Monaten',
16             time_a_month_ago => 'vor einem Monat',
17             time_years_ago => 'vor %d Jahren',
18             time_a_year_ago => 'vor einem Jahr',
19             time_weeks_ago => 'vor %d Wochen',
20             time_a_week_ago => 'vor einer Woche',
21             time_num_days_ago => 'vor %d Tagen',
22             time_yesterday_at => 'Gestern um %02d:%02d',
23             time_hour_min_ago => 'vor %d Stunden %d Minuten',
24             time_minute_ago => 'vor %d Minute',
25             time_minutes_ago => 'vor %d Minuten',
26             time_less_than_minute_ago => 'vor weniger als einer Minute',
27             time_just_now => 'gerade eben',
28              
29             time_in_less_than_minute => 'in %d Sekunden',
30             time_in_minute => 'in %d Minute',
31             time_in_minutes => 'in %d Minuten',
32             time_in_hour_minutes => 'in %d Stunde %d Minuten',
33             time_in_hours_minutes => 'in %d Stunde %d Minuten',
34             time_tomorrow_at => 'Morgen um %02d:%02d',
35             time_after_tomorrow_at => 'Ãœbermorgen um %02d:%02d',
36             time_in_num_days => 'in %d Tagen',
37             time_in_week => 'in %d Woche',
38             time_in_weeks => 'in %d Wochen',
39             time_in_month => 'in %d Monat',
40             time_in_months => 'in %d Monaten',
41             time_in_year => 'in %d Jahr',
42             time_in_over_year => 'in über einem Jahr',
43             time_in_years => 'in %d Jahren',
44             time_in_over_years => 'in über %d Jahren',
45             },
46             nl => {
47             time_months_ago => '%d maanden geleden',
48             time_a_month_ago => 'een maand geleden',
49             time_years_ago => '%d jaar geleden',
50             time_a_year_ago => 'een jaar geleden',
51             time_a_week_ago => 'een week geleden',
52             time_weeks_ago => '%d weken geleden',
53             time_num_days_ago => '%d dagen geleden',
54             time_yesterday_at => 'gisteren om %02d:%02d',
55             time_hour_min_ago => '%d uur %d minuten geleden',
56             time_minute_ago => '%d minuut geleden',
57             time_minutes_ago => '%d minuten geleden',
58             time_less_than_minute_ago => 'minder dan een minuut geleden',
59             time_just_now => 'net precies',
60              
61             time_in_less_than_minute => 'in %d seconden',
62             time_in_minute => 'in %d minute',
63             time_in_minutes => 'in %d minuten',
64             time_in_hour_minutes => 'in %d uur %d minuten',
65             time_in_hours_minutes => 'in %d uur %d minuten',
66             time_tomorrow_at => 'morgen een %02d:%02d',
67             time_after_tomorrow_at => 'de dag nar morgen een %02d:%02d',
68             time_in_num_days => 'in %d fagen',
69             time_in_week => 'in %d week',
70             time_in_weeks => 'in %d weken',
71             time_in_month => 'in %d maand',
72             time_in_months => 'in %d maanden',
73             time_in_year => 'in %d jaar',
74             time_in_over_year => 'in mer dan een jaar',
75             time_in_years => 'in %d jaar',
76             time_in_over_years => 'in mer dan %d jaar',
77             },
78             en => {
79             time_months_ago => '%d months ago',
80             time_a_month_ago => 'a month ago',
81             time_years_ago => '%d years ago',
82             time_a_year_ago => 'a year ago',
83             time_a_week_ago => 'a week ago',
84             time_weeks_ago => '%d weeks ago',
85             time_num_days_ago => '%d days ago',
86             time_yesterday_at => 'yesterday at %02d:%02d',
87             time_hour_min_ago => '%d hour %d minutes ago',
88             time_minute_ago => '%d minute ago',
89             time_minutes_ago => '%d minutes ago',
90             time_less_than_minute_ago => 'less than a minute ago',
91             time_just_now => 'just now',
92              
93             time_in_less_than_minute => 'in %d seconds',
94             time_in_minute => 'in %d minute',
95             time_in_minutes => 'in %d minutes',
96             time_in_hour_minutes => 'in %d hour %d minutes',
97             time_in_hours_minutes => 'in %d hours %d minutes',
98             time_tomorrow_at => 'tomorrow at %02d:%02d',
99             time_after_tomorrow_at => 'the day after tomorrow at %02d:%02d',
100             time_in_num_days => 'in %d days',
101             time_in_week => 'in %d week',
102             time_in_weeks => 'in %d weeks',
103             time_in_month => 'in %d month',
104             time_in_months => 'in %d months',
105             time_in_year => 'in %d year',
106             time_in_over_year => 'in over a year',
107             time_in_years => 'in %d years',
108             time_in_over_years => 'in over %d years',
109             },
110             en_short => {
111             time_months_ago => '%dm ago',
112             time_a_month_ago => '1m ago',
113             time_years_ago => '%dy ago',
114             time_a_year_ago => '1y ago',
115             time_a_week_ago => '1w ago',
116             time_weeks_ago => '%dw ago',
117             time_num_days_ago => '%dd ago',
118             time_yesterday_at => '%02d:%02d yesterday',
119             time_hour_min_ago => '%dh %dm ago',
120             time_minute_ago => '%dm ago',
121             time_minutes_ago => '%dm ago',
122             time_less_than_minute_ago => '< 1m ago',
123             time_just_now => 'just now',
124              
125             time_in_less_than_minute => 'in %ds',
126             time_in_minute => 'in %dm',
127             time_in_minutes => 'in %dm',
128             time_in_hour_minutes => 'in %dh %dm',
129             time_in_hours_minutes => 'in %dh %dm',
130             time_tomorrow_at => 'tomorrow at %02d:%02d',
131             time_after_tomorrow_at => 'the day after tomorrow at %02d:%02d',
132             time_in_num_days => 'in %dd',
133             time_in_week => 'in %dw',
134             time_in_weeks => 'in %dw',
135             time_in_month => 'in %dm',
136             time_in_months => 'in %dm',
137             time_in_year => 'in %dy',
138             time_in_over_year => 'in > 1y',
139             time_in_years => 'in %dy',
140             time_in_over_years => 'in > %dy',
141             },
142             );
143              
144             sub new {
145 10     10 1 4078 my ($klass, $args) = @_;
146             my $self = {
147             today_and_now => $args->{today_and_now},
148 10   100     56 lang => $args->{lang} || 'nl',
149             };
150 10         54 return bless $self, $klass;
151             }
152              
153             sub _parse_mysql_date {
154 65     65   3700 my ($mysql_date) = @_;
155              
156 65 100 100     399 if ($mysql_date && $mysql_date =~ m/^(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})$/) {
157 59         351 return $1, $2, $3, $4, $5, $6;
158             }
159 6   100     19 $mysql_date ||= '';
160 6         69 croak "Not a MySQL date: [$mysql_date]";
161             }
162              
163             sub _get_date_parts {
164 107     107   191 my ($self, $date) = @_;
165              
166 107 100       330 if (ref($date)) {
167 48         128 return ($date->year, $date->month, $date->day, $date->hour, $date->minute, $date->second);
168             }
169              
170 59 100       296 if ($date =~ /^\d+$/o){
171 1         38 my @x = gmtime($date);
172             return (
173 1         7 $x[5]+1900,
174             $x[4]+1,
175             $x[3],
176             $x[2],
177             $x[1],
178             $x[0]
179             );
180             }
181              
182 58         125 return _parse_mysql_date($date);
183             }
184              
185             sub human_readable {
186 107     107 1 75449 my ($self, $date) = @_;
187              
188 107         248 my @date = $self->_get_date_parts($date);
189              
190 107 100       1607 my @now = ref($self->{today_and_now}) eq 'ARRAY' ? @{$self->{today_and_now}} : ();
  106         233  
191              
192 107 100       288 if (!@now) {
193 1         69 @now = Today_and_Now(0);
194             }
195 107         598 my ($Dy, $DM, $Dd,$Dh,$Dm,$Ds) = N_Delta_YMDHMS(@date,@now);
196              
197             ## the past
198 107 100 100     1136 if ($Dy == 1) {
    100 66        
    100 100        
    50 66        
    100 66        
    100 66        
    100 33        
    100 33        
    100 33        
    100 33        
    50 33        
    50 33        
    50 33        
    100          
    50          
    50          
    50          
    50          
199 3         7 return $self->_translate('time_a_year_ago');
200             }
201             elsif ($Dy > 1) {
202 1         4 return $self->_translate('time_years_ago', $Dy);
203             }
204             elsif ($DM == 1) {
205 4         11 return $self->_translate('time_a_month_ago');
206             }
207             elsif ($DM > 1) {
208 0         0 return $self->_translate('time_months_ago', $DM);
209             }
210             elsif ($Dd >= 7) {
211 6 100       27 if (int($Dd / 7) == 1) {
212 3         9 return $self->_translate('time_a_week_ago', 1);
213             }
214 3 50       12 if (int($Dd / 7) > 1) {
215 3         11 return $self->_translate('time_weeks_ago', int($Dd / 7));
216             }
217             }
218             elsif ($Dd > 1) {
219 7         34 return $self->_translate('time_num_days_ago', $Dd);
220             }
221             elsif ($Dd == 1) {
222 7         27 return $self->_translate('time_yesterday_at', $date[3], $date[4]);
223             }
224             elsif ($Dd == 0 && $Dh >= 1) {
225 21         60 return $self->_translate('time_hour_min_ago', $Dh, $Dm);
226             }
227             elsif ($Dd == 0 && $Dh == 0 && $Dm > 0) {
228 35 100       82 if ($Dm == 1) {
229 14         36 return $self->_translate('time_minute_ago', $Dm);
230             }
231 21         46 return $self->_translate('time_minutes_ago', $Dm);
232             }
233             elsif ($Dd == 0 && $Dh == 0 && $Dm == 0 && $Ds > 5) {
234 7         23 return $self->_translate('time_less_than_minute_ago');
235             }
236              
237             ## the future
238             elsif ($Dy == 0 && $DM < -12) { # ?? N_Delta_YMDHMS counts 1 year + 3 month as DM = 15 while Dy = 0
239 0 0       0 if (abs(int($DM / 12)) == 1) {
240 0         0 return $self->_translate('time_in_over_year');
241             }
242             }
243             elsif ($Dy < 0) {
244 0 0       0 if ($Dy == -1){
245 0 0       0 if ($DM < 0) {
246 0         0 return $self->_translate('time_in_over_year', abs(int($DM / 12)) );
247             }
248 0         0 return $self->_translate('time_in_year', $Dy * -1);
249             }else{
250 0 0       0 if ($DM < 0) {
251 0         0 return $self->_translate('time_in_over_years', $Dy * -1 );
252             }
253 0         0 return $self->_translate('time_in_years', $Dy * -1);
254             }
255             }
256             elsif ($DM < 0) {
257 0 0       0 if ($DM == -1) {
258 0         0 return $self->_translate('time_in_month', $DM * -1 );
259             }
260 0         0 return $self->_translate('time_in_months',$DM * -1);
261             }
262             elsif ($Dd <= -2) {
263 2 50       16 if (abs($Dd / 7) == 1) {
    50          
264 0         0 return $self->_translate('time_in_week', 1);
265             }
266             elsif ($Dd % 7 == 0) {
267 0         0 return $self->_translate('time_in_weeks', abs(int($Dd / 7)));
268             }
269 2         9 return $self->_translate('time_in_num_days', ($Dd * -1) );
270             }
271             elsif ($Dd == -1) {
272 0 0       0 if ($Dh < 0) {
273 0         0 return $self->_translate('time_after_tomorrow_at', $date[3], $date[4]);
274             }
275 0         0 return $self->_translate('time_tomorrow_at', $date[3], $date[4]);
276             }
277             elsif ($Dd == 0 && $Dh <= -1) {
278 0 0       0 if ($Dh == -1) {
279 0         0 return $self->_translate('time_in_hour_minutes', $Dh * -1, $Dm * -1);
280             }
281 0         0 return $self->_translate('time_in_hours_minutes', $Dh * -1, $Dm * -1);
282             }
283             elsif ($Dd == 0 && $Dh == 0 && $Dm < 0) {
284 0 0       0 if ($Dm == -1) {
285 0         0 return $self->_translate('time_in_minute', $Dm * -1);
286             }
287 0         0 return $self->_translate('time_in_minutes', $Dm * -1);
288             }
289             elsif ($Dd == 0 && $Dh == 0 && $Dm == 0 && $Ds < -5) {
290 0         0 return $self->_translate('time_in_less_than_minute', $Ds * -1);
291             }
292              
293              
294             else {
295 14         40 return $self->_translate('time_just_now');
296             }
297 0         0 return;
298             }
299              
300             sub _translate {
301 107     107   244 my ($self, $key, @values) = @_;
302              
303 107         1089 return sprintf($translation{$self->{lang}}{$key}, @values);
304             }
305              
306             1;
307              
308             =head1 NAME
309              
310             Date::Period::Human - Human readable date periods
311              
312             =head1 SYNOPSYS
313              
314             # Create the Date::Period::Human object
315             my $d = Date::Period::Human->new();
316              
317             # Get a relative human readable date string
318             my $s = $d->human_readable('2010-01-01 02:30:42');
319              
320             # Now $s contains the relative date in human readable form, e.g. "1 year
321             # ago", "3 weeks ago", etc.
322              
323             # Use English instead of the default Dutch
324             # my $d = Date::Period::Human->new({ lang => 'en' });
325              
326             =head1 DESCRIPTION
327              
328             Creates a string of relative time. This is useful when you're showing user
329             created content, where it's nicer to show how long ago the item was posted
330             (e.g. "3 weeks ago") instead of the date and time.
331              
332             This also solves the problem where you don't know the timezone of the user who
333             is viewing the item. This is solved because you show relative time instead of
334             absolute time in most cases.
335              
336             There is one case that isn't relative.
337              
338             =head1 CLASS METHODS
339              
340             This class contains one public class method.
341              
342             =head2 new [options]
343              
344             =over 4
345              
346             =item lang
347              
348             The language you want to use. Default 'nl', can be 'en' for English.
349              
350             =item today_and_now
351              
352             An arrayref containing [ $year, $month, $day, $hour, $min, $sec ].
353              
354             Will be used as the fixed point from which the relative time will be calculated.
355              
356             =back
357              
358             =head1 METHODS
359              
360             This class contains one public method.
361              
362             =head2 $self->human_readable($mysql_date|$datetime|$epoch)
363              
364             Parses the $mysql_date and returns a human readable time string.
365              
366             Or, $datetime (a DateTime object) and returns a human readable time string.
367              
368             Or, $epoch (identified by regex /^\d+$/) and passed through gmtime().
369              
370             =head1 HOMEPAGE
371              
372             http://github.com/pstuifzand/date-period-human
373              
374             =head1 LICENSE
375              
376             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
377              
378             =head1 AUTHOR
379              
380             Peter Stuifzand <peter@stuifzand.eu>
381              
382             =head1 COPYRIGHT
383              
384             Copyright 2010 Peter Stuifzand
385              
386             =cut
387