File Coverage

blib/lib/Jifty/DBI/Filter/DateTime.pm
Criterion Covered Total %
statement 67 70 95.7
branch 17 24 70.8
condition 3 3 100.0
subroutine 14 14 100.0
pod 3 3 100.0
total 104 114 91.2


line stmt bran cond sub pod time code
1             package Jifty::DBI::Filter::DateTime;
2              
3 3     3   18 use warnings;
  3         6  
  3         180  
4 3     3   16 use strict;
  3         7  
  3         113  
5              
6 3     3   16 use base qw|Jifty::DBI::Filter Class::Data::Inheritable|;
  3         6  
  3         351  
7 3     3   27 use DateTime ();
  3         6  
  3         57  
8 3     3   3511 use DateTime::Format::ISO8601 ();
  3         192723  
  3         278  
9 3     3   40 use DateTime::Format::Strptime ();
  3         8  
  3         50  
10 3     3   18 use Carp ();
  3         4  
  3         91  
11              
12 3     3   17 use constant _time_zone => 'UTC';
  3         6  
  3         252  
13 3     3   16 use constant _strptime => '%Y-%m-%d %H:%M:%S';
  3         6  
  3         226  
14 3     3   16 use constant _parser => DateTime::Format::ISO8601->new();
  3         8  
  3         19  
15 3     3   347 use constant date_only => 0;
  3         7  
  3         1712  
16              
17             =head1 NAME
18              
19             Jifty::DBI::Filter::DateTime - DateTime object wrapper around date columns
20              
21             =head1 DESCRIPTION
22              
23             This filter allow you to work with DateTime objects instead of
24             plain text dates. If the column type is "date", then the hour,
25             minute, and second information is discarded when encoding.
26              
27             Both input and output will always be coerced into UTC (or, in the case of
28             Dates, the Floating timezone) for consistency.
29              
30             =head2 formatter
31              
32             This is an instance of the DateTime::Format object used for inflating the
33             string in the database to a DateTime object. By default it is a
34             L object that uses the C<_strptime> method as its
35             pattern.
36              
37             You can use the _formatter classdata storage as a cache so you don't need
38             to re-instantiate your format object every C.
39              
40             =cut
41              
42             __PACKAGE__->mk_classdata("_formatter");
43             sub formatter {
44 12     12 1 30 my $self = shift;
45 12 100 100     94 if ( not $self->_formatter
46             or $self->_formatter->pattern ne $self->_strptime )
47             {
48 9         313 $self->_formatter(DateTime::Format::Strptime->new(pattern => $self->_strptime));
49             }
50 12         5470 return $self->_formatter;
51             }
52              
53             =head2 encode
54              
55             If value is DateTime object then converts it into ISO format
56             C. Does nothing if value is not defined.
57              
58             Sets the value to undef if the value is a string and doesn't match an ISO date (at least).
59              
60              
61             =cut
62              
63             sub encode {
64 15     15 1 29 my $self = shift;
65              
66 15         94 my $value_ref = $self->value_ref;
67              
68 15 100       260 return if !defined $$value_ref;
69              
70 12 100       88 if ( ! UNIVERSAL::isa( $$value_ref, 'DateTime' )) {
71 3 50       36 if ($$value_ref !~ /^\d{4}[ -]?\d{2}[ -]?\d{2}/) {
72 0         0 $$value_ref = undef;
73             }
74 3         32 return undef;
75             }
76              
77 9 50       236 return unless $$value_ref;
78 9 50       549 if (my $tz = $self->_time_zone) {
79 9         38 $$value_ref = $$value_ref->clone;
80 9         158 $$value_ref->set_time_zone($tz);
81             }
82 9         1264 $$value_ref = $$value_ref->DateTime::strftime($self->_strptime);
83 9         846 return 1;
84             }
85              
86             =head2 decode
87              
88             If value is defined then converts it into DateTime object otherwise do
89             nothing.
90              
91             =cut
92              
93             sub decode {
94 16     16 1 121 my $self = shift;
95              
96 16         93 my $value_ref = $self->value_ref;
97 16 100       265 return unless defined $$value_ref;
98              
99             # XXX: Looks like we should use special modules for parsing DT because
100             # different MySQL versions can return DT in different formats(none strict ISO)
101             # Pg has also special format that depends on "european" and
102             # server time_zone, by default ISO
103             # other DBs may have own formats(Interbase for example can be forced to use special format)
104             # but we need Jifty::DBI::Handle here to get DB type
105              
106 12         113 my $str = join('T', split ' ', $$value_ref, 2);
107              
108             # The ISO8601 parser accepts 2012-11-04T12:34:56+00
109             # and 2012-11-04T12:34:56.789+00:00
110             # but not 2012-11-04T12:34:56.789+00
111             # Postgres returns sub-second times as the last one; append ":00" to
112             # change it into the acceptable second option.
113 12 50       64 $str .= ":00" if $str =~ /\d\.\d+[+-]\d\d$/;
114              
115 12         24 my $dt;
116 12         23 eval { $dt = $self->_parser->parse_datetime($str) };
  12         163  
117              
118 12 50       13688 if ($@) { # if datetime can't decode this, scream loudly with a useful error message
119 0         0 Carp::cluck("Unable to decode $str: $@");
120 0         0 return;
121             }
122              
123 12 50       76 return if !$dt;
124              
125 12         1053 my $tz = $self->_time_zone;
126 12 50       83 $dt->set_time_zone($tz) if $tz;
127              
128 12 100       2876 if ($self->date_only) {
129 3         21 $dt->set_hour(0);
130 3         1412 $dt->set_minute(0);
131 3         925 $dt->set_second(0);
132             }
133              
134 12         2152 $dt->set_formatter($self->formatter);
135 12         654 $$value_ref = $dt;
136             }
137              
138             =head1 SEE ALSO
139              
140             L, L
141              
142             =cut
143              
144             1;