File Coverage

blib/lib/Catmandu/Fix/datetime_diff.pm
Criterion Covered Total %
statement 73 88 82.9
branch 4 8 50.0
condition 2 3 66.6
subroutine 16 16 100.0
pod 0 1 0.0
total 95 116 81.9


line stmt bran cond sub pod time code
1             package Catmandu::Fix::datetime_diff;
2 2     2   171664 use Catmandu::Sane;
  2         3  
  2         10  
3 2     2   324 use Moo;
  2         4  
  2         9  
4 2     2   432 use Catmandu::Util qw(:is :check :array);
  2         3  
  2         804  
5 2     2   556 use DateTime::Format::Strptime;
  2         293399  
  2         14  
6 2     2   135 use DateTime::TimeZone;
  2         3  
  2         40  
7 2     2   21 use DateTime::Locale;
  2         3  
  2         28  
8 2     2   7 use DateTime;
  2         1  
  2         30  
9 2     2   803 use Catmandu::Fix::Has;
  2         1305  
  2         11  
10              
11             our $VERSION = "0.0131";
12              
13             with 'Catmandu::Fix::Base';
14              
15             has path => (
16             fix_arg => 1
17             );
18             has start => (
19             fix_arg => 1
20             );
21             has end => (
22             fix_arg => 1
23             );
24             has start_time_zone => (
25             fix_opt => 1,
26             is => 'ro',
27             required => 1,
28             isa => sub {
29             check_string($_[0]);
30             },
31             default => sub {
32             "UTC"
33             }
34             );
35             has end_time_zone => (
36             fix_opt => 1,
37             is => 'ro',
38             required => 1,
39             isa => sub {
40             check_string($_[0]);
41             },
42             default => sub {
43             "UTC"
44             }
45             );
46              
47             has validate => (
48             fix_opt => 1,
49             is => 'ro',
50             required => 0,
51             lazy => 1,
52             default => sub { 1; }
53             );
54              
55             has delete => (
56             fix_opt => 1,
57             is => 'ro'
58             );
59             has start_pattern => (
60             fix_opt => 1,
61             is => 'ro',
62             required => 1,
63             isa => sub {
64             check_string($_[0]);
65             },
66             default => sub {
67             "%FT%T.%NZ"
68             }
69             );
70             has end_pattern => (
71             fix_opt => 1,
72             is => 'ro',
73             required => 1,
74             isa => sub {
75             check_string($_[0]);
76             },
77             default => sub {
78             "%FT%T.%NZ"
79             }
80             );
81             has start_locale => (
82             is => 'ro',
83             required => 1,
84             isa => sub {
85             check_string($_[0]);
86             },
87             default => sub {
88             "en_US"
89             }
90             );
91             has _start_locale => (
92             is => 'ro',
93             required => 0,
94             lazy => 1,
95             builder => '_build_start_locale'
96             );
97             has end_locale => (
98             is => 'ro',
99             required => 1,
100             isa => sub {
101             check_string($_[0]);
102             },
103             default => sub {
104             "en_US"
105             }
106             );
107             has _end_locale => (
108             is => 'ro',
109             required => 0,
110             lazy => 1,
111             builder => '_build_end_locale'
112             );
113              
114             has _start_time_zone => (
115             is => 'ro',
116             required => 0,
117             lazy => 1,
118             builder => '_build_start_time_zone'
119             );
120             has _end_time_zone => (
121             is => 'ro',
122             required => 0,
123             lazy => 1,
124             builder => '_build_end_time_zone'
125             );
126              
127             has _start_datetime_parser => (
128             is => 'ro',
129             lazy => 1,
130             default => sub {
131             my $self = $_[0];
132             DateTime::Format::Strptime->new(
133             pattern => $self->start_pattern,
134             locale => $self->_start_locale,
135             time_zone => $self->_start_time_zone,
136             on_error => 'undef'
137             );
138             }
139             );
140             has _end_datetime_parser => (
141             is => 'ro',
142             lazy => 1,
143             default => sub {
144             my $self = $_[0];
145             DateTime::Format::Strptime->new(
146             pattern => $self->end_pattern,
147             locale => $self->_end_locale,
148             time_zone => $self->_end_time_zone,
149             on_error => 'undef'
150             );
151             }
152             );
153              
154             sub _get_locale {
155 8     8   10 state $l = {};
156 8         8 my $name = $_[0];
157 8   66     44 $l->{$name} ||= DateTime::Locale->load($name);
158             }
159             sub _get_time_zone {
160 8     8   9 state $t = {};
161 8         7 my $name = $_[0];
162 8         30 $t->{$name} = DateTime::TimeZone->new( name => $name );
163             }
164             sub _build_start_locale {
165 4     4   321 _get_locale($_[0]->start_locale);
166             }
167             sub _build_end_locale {
168 4     4   303 _get_locale($_[0]->end_locale);
169             }
170             sub _build_start_time_zone {
171 4     4   313 _get_time_zone( $_[0]->start_time_zone );
172             }
173             sub _build_end_time_zone {
174 4     4   302 _get_time_zone( $_[0]->end_time_zone );
175             }
176              
177             sub emit {
178 4     4 0 2148 my($self,$fixer) = @_;
179              
180 4         5 my @perl;
181              
182 4         36 my $path = $fixer->split_path($self->path());
183 4         72 my $start = $fixer->split_path($self->start());
184 4         34 my $start_key = pop @$start;
185 4         14 my $end = $fixer->split_path($self->end());
186 4         30 my $end_key = pop @$end;
187              
188 4         48 my $start_time_zone = $fixer->capture($self->_start_time_zone());
189 4         483 my $start_locale = $fixer->capture($self->_start_locale());
190 4         259 my $end_time_zone = $fixer->capture($self->_end_time_zone());
191 4         9822 my $end_locale = $fixer->capture($self->_end_locale());
192              
193 4         260 my $start_parser = $fixer->capture($self->_start_datetime_parser());
194 4         3836 my $end_parser = $fixer->capture($self->_end_datetime_parser());
195              
196             #cf. http://www.nntp.perl.org/group/perl.datetime/2012/05/msg7838.html
197 4 50       2932 push @perl, "local \$Params::Validate::NO_VALIDATION = ".($self->validate() ? 0 : 1).";";
198              
199             push @perl, $fixer->emit_walk_path( $fixer->var, $start, sub {
200              
201 4     4   58 my $p_start_var = shift;
202              
203             $fixer->emit_get_key( $p_start_var, $start_key, sub {
204              
205 4         73 my $start_var = shift;
206              
207             $fixer->emit_walk_path( $fixer->var, $end, sub {
208              
209 4         43 my $p_end_var = shift;
210              
211             $fixer->emit_get_key( $p_end_var, $end_key, sub {
212              
213 4         41 my $end_var = shift;
214              
215 4         11 my $dt_start = $fixer->generate_var();
216 4         132 my $dt_end = $fixer->generate_var();
217              
218 4         112 my @p;
219              
220 4         11 push @p, $fixer->emit_declare_vars($dt_start);
221 4         30 push @p, $fixer->emit_declare_vars($dt_end);
222              
223             #start: no parsing needed (fast)
224 4 50       30 if($self->start_pattern() =~ /\s*%s\s*/o){
225 0         0 push @p, "if( is_string(${start_var}) ) {";
226 0         0 push @p, " ${start_var} =~ s\/^\\s+|\\s+\$\/\/go;";
227 0         0 push @p, " $dt_start = DateTime->from_epoch(epoch => ${start_var},time_zone => ${start_time_zone},locale => ${start_locale});";
228 0         0 push @p, "}"
229             }
230             #start: parsing needed (slow)
231             else{
232 4         19 push @p, " $dt_start = ".${start_parser}."->parse_datetime(${start_var}) if is_string(${start_var});";
233             }
234             #end: no parsing needed (fast)
235 4 50       11 if($self->end_pattern() =~ /\s*%s\s*/o){
236 0         0 push @p, "if( is_string(${end_var}) ) {";
237 0         0 push @p, " ${end_var} =~ s\/^\\s+|\\s+\$\/\/go;";
238 0         0 push @p, " $dt_end = DateTime->from_epoch(epoch => ${end_var},time_zone => ${end_time_zone},locale => ${end_locale});";
239 0         0 push @p, "}"
240             }
241             #end: parsing needed (slow)
242             else{
243 4         15 push @p, " $dt_end = ".${end_parser}."->parse_datetime(${end_var}) if is_string(${end_var});";
244             }
245              
246 4         11 push @p, " if($dt_start && $dt_end){";
247              
248             push @p, $fixer->emit_create_path( $fixer->var, $path, sub {
249              
250 4         254 my $var = shift;
251 4         16 " ${var} = $dt_end->subtract_datetime_absolute( $dt_start )->seconds();";
252              
253 4         57 });
254              
255 4         26 push @p, " }";
256              
257 4 50       14 if($self->delete){
258 0         0 my $dest_path = [@$path];
259 0         0 my $dest_key = pop @$dest_path;
260 0         0 push @p, " else { ";
261             push @p, $fixer->emit_walk_path( $fixer->var, $dest_path, sub {
262 0         0 my $p_dest = shift;
263 0         0 $fixer->emit_delete_key( $p_dest, $dest_key );
264 0         0 });
265 0         0 push @p, " }";
266             }
267              
268 4         16 join('', @p);
269              
270              
271 4         30 });
272              
273 4         58 });
274              
275 4         23 });
276              
277 4         57 });
278              
279              
280 4         92 join('',@perl);
281             }
282              
283             1;
284             __END__
285              
286             =head1 NAME
287              
288             Catmandu::Fix::datetime_diff - Catmandu Fix to compute difference in seconds between two datetimes
289              
290             =head1 SYNOPSIS
291              
292             datetime_diff('diff','startTime','endTime',
293             'start_pattern' => '%Y-%m-%d',
294             'end_pattern' => '%Y-%m-%d',
295             'start_time_zone' => 'UTC',
296             'end_time_zone' => 'Europe/Brussels',
297             'delete' => 1,
298             validate => 0,
299             start_locale => 'en_US',
300             end_locale => 'nl_NL'
301             )
302              
303             =head1 OPTIONS
304              
305             =over 4
306              
307             =item start_pattern
308              
309             Pattern of the start date string to parse. See L<DateTime::Format::Strptime>
310             for documentation of the format. The default is C<%FT%T.%NZ> (UTC datetime string).
311              
312             =item end_pattern
313              
314             Pattern of the end date string. See L<DateTime::Format::Strptime>
315             for documentation of the format. The default is C<%FT%T.%NZ> (UTC datetime string).
316              
317             =item start_time_zone
318              
319             Time zone of the start date string. In case the start date string does not
320             contain any time zone information, the parser will use this time_zone to
321             interpret the date. When not set correctly, the resulting date string will be
322             wrong. The default value is C<UTC>. For a complete list of time zone codes see
323             L<http://en.wikipedia.org/wiki/List_of_tz_database_time_zones>.
324              
325             Most parsers assume 'local', but this can lead to different results on
326             different systems. 'local' simply means the same time zone as the one
327             configured on your system.
328              
329             =item end_time_zone
330              
331             Time zone of the end date string. In case the end date string does not
332             contain any time zone information, the parser will use this time_zone to
333             interpret the date. When not set correctly, the resulting date string will be
334             wrong. The default value is C<UTC>. For a complete list of time zone codes see
335             L<http://en.wikipedia.org/wiki/List_of_tz_database_time_zones>.
336              
337             Most parsers assume 'local', but this can lead to different results on
338             different systems. 'local' simply means the same time zone as the one
339             configured on your system.
340              
341             =item start_locale
342              
343             Language code for the start date string. This is only important when your date
344             string contains names of week days or months. For a complete list of locale
345             codes see L<DateTime::Locale::Catalog>. The default value is C<en_US>.
346              
347             =item end_locale
348              
349             Language code for the end date string. This is only important when your date
350             string contains names of week days or months. For a complete list of locale
351             codes see L<DateTime::Locale::Catalog>. The default value is C<en_US>.
352              
353             =item delete
354              
355             Delete the key when either start or end date string cannot be parsed. When used, the
356             option C<default> is ignored. Disabled (C<0>) by default.
357              
358             =item validate
359              
360             Validate start and end date string when parsing. Disabled (C<0>) by default to
361             increase speed.
362              
363             =back
364              
365             =head1 AUTHOR
366              
367             Nicolas Franck, C<< <nicolas.franck at ugent.be> >>
368              
369             =head1 SEE ALSO
370              
371             L<Catmandu::Fix>
372              
373             =cut