File Coverage

blib/lib/Date/Period/Human.pm
Criterion Covered Total %
statement 59 85 69.4
branch 47 74 63.5
condition 28 46 60.8
subroutine 10 10 100.0
pod 2 2 100.0
total 146 217 67.2


line stmt bran cond sub pod time code
1             package Date::Period::Human;
2              
3 4     4   325027 use strict;
  4         33  
  4         120  
4 4     4   20 use warnings;
  4         9  
  4         96  
5 4     4   20 use Carp;
  4         18  
  4         289  
6              
7 4     4   1919 use Date::Calc qw/Delta_DHMS Today_and_Now N_Delta_YMDHMS/;
  4         33250  
  4         403  
8              
9             our $VERSION='0.4.8';
10              
11 4     4   2656 use utf8; # umlauts in translations
  4         60  
  4         22  
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 4380 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         57 return bless $self, $klass;
151             }
152              
153             sub _parse_mysql_date {
154 69     69   3733 my ($mysql_date) = @_;
155              
156 69 100 100     433 if ($mysql_date && $mysql_date =~ m/^(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):(\d{2})$/) {
157 63         365 return $1, $2, $3, $4, $5, $6;
158             }
159 6   100     21 $mysql_date ||= '';
160 6         78 croak "Not a MySQL date: [$mysql_date]";
161             }
162              
163             sub _get_date_parts {
164 111     111   192 my ($self, $date) = @_;
165              
166 111 100       309 if (ref($date)) {
167 48         128 return ($date->year, $date->month, $date->day, $date->hour, $date->minute, $date->second);
168             }
169              
170 63 100       325 if ($date =~ /^\d+$/o){
171 1         22 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 62         133 return _parse_mysql_date($date);
183             }
184              
185             sub human_readable {
186 111     111 1 77797 my ($self, $date) = @_;
187              
188 111         273 my @date = $self->_get_date_parts($date);
189              
190 111 100       1279 my @now = ref($self->{today_and_now}) eq 'ARRAY' ? @{$self->{today_and_now}} : ();
  110         263  
191              
192 111 100       279 if (!@now) {
193 1         58 @now = Today_and_Now(0);
194             }
195 111         658 my ($Dy, $DM, $Dd,$Dh,$Dm,$Ds) = N_Delta_YMDHMS(@date,@now);
196              
197             ## the past
198 111 100 100     1129 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         10 return $self->_translate('time_a_year_ago');
200             }
201             elsif ($Dy > 1) {
202 1         5 return $self->_translate('time_years_ago', $Dy);
203             }
204             elsif ($DM == 1) {
205 4         12 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 10 100       39 if (int($Dd / 7) == 1) {
212 7         21 return $self->_translate('time_a_week_ago', 1);
213             }
214 3 50       11 if (int($Dd / 7) > 1) {
215 3         21 return $self->_translate('time_weeks_ago', int($Dd / 7));
216             }
217             }
218             elsif ($Dd > 1) {
219 7         23 return $self->_translate('time_num_days_ago', $Dd);
220             }
221             elsif ($Dd == 1) {
222 7         20 return $self->_translate('time_yesterday_at', $date[3], $date[4]);
223             }
224             elsif ($Dd == 0 && $Dh >= 1) {
225 21         69 return $self->_translate('time_hour_min_ago', $Dh, $Dm);
226             }
227             elsif ($Dd == 0 && $Dh == 0 && $Dm > 0) {
228 35 100       77 if ($Dm == 1) {
229 14         35 return $self->_translate('time_minute_ago', $Dm);
230             }
231 21         45 return $self->_translate('time_minutes_ago', $Dm);
232             }
233             elsif ($Dd == 0 && $Dh == 0 && $Dm == 0 && $Ds > 5) {
234 7         27 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       14 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         35 return $self->_translate('time_just_now');
296             }
297 0         0 return;
298             }
299              
300             sub _translate {
301 111     111   246 my ($self, $key, @values) = @_;
302 111         332 my $phrase = $translation{$self->{lang}}{$key};
303 111 100       875 return sprintf $phrase, @values if $phrase =~ /%/;
304 35         180 return $phrase;
305             }
306              
307             1;
308              
309             =head1 NAME
310              
311             Date::Period::Human - Human readable date periods
312              
313             =head1 SYNOPSYS
314              
315             # Create the Date::Period::Human object
316             my $d = Date::Period::Human->new();
317              
318             # Get a relative human readable date string
319             my $s = $d->human_readable('2010-01-01 02:30:42');
320              
321             # Now $s contains the relative date in human readable form, e.g. "1 year
322             # ago", "3 weeks ago", etc.
323              
324             # Use English instead of the default Dutch
325             # my $d = Date::Period::Human->new({ lang => 'en' });
326              
327             =head1 DESCRIPTION
328              
329             Creates a string of relative time. This is useful when you're showing user
330             created content, where it's nicer to show how long ago the item was posted
331             (e.g. "3 weeks ago") instead of the date and time.
332              
333             This also solves the problem where you don't know the timezone of the user who
334             is viewing the item. This is solved because you show relative time instead of
335             absolute time in most cases.
336              
337             There is one case that isn't relative.
338              
339             =head1 CLASS METHODS
340              
341             This class contains one public class method.
342              
343             =head2 new [options]
344              
345             =over 4
346              
347             =item lang
348              
349             The language you want to use. Default 'nl', can be 'en' for English.
350              
351             =item today_and_now
352              
353             An arrayref containing [ $year, $month, $day, $hour, $min, $sec ].
354              
355             Will be used as the fixed point from which the relative time will be calculated.
356              
357             =back
358              
359             =head1 METHODS
360              
361             This class contains one public method.
362              
363             =head2 $self->human_readable($mysql_date|$datetime|$epoch)
364              
365             Parses the $mysql_date and returns a human readable time string.
366              
367             Or, $datetime (a DateTime object) and returns a human readable time string.
368              
369             Or, $epoch (identified by regex /^\d+$/) and passed through gmtime().
370              
371             =head1 HOMEPAGE
372              
373             http://github.com/pstuifzand/date-period-human
374              
375             =head1 LICENSE
376              
377             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
378              
379             =head1 AUTHOR
380              
381             Peter Stuifzand <peter@stuifzand.eu>
382              
383             =head1 COPYRIGHT
384              
385             Copyright 2010 Peter Stuifzand
386              
387             =cut
388