File Coverage

blib/lib/DBIx/Class/InflateColumn/DateTime.pm
Criterion Covered Total %
statement 55 77 71.4
branch 25 42 59.5
condition 6 20 30.0
subroutine 11 17 64.7
pod 1 1 100.0
total 98 157 62.4


line stmt bran cond sub pod time code
1             package DBIx::Class::InflateColumn::DateTime;
2              
3 266     266   222189 use strict;
  266         746  
  266         7523  
4 266     266   1573 use warnings;
  266         650  
  266         7493  
5 266     266   1810 use base qw/DBIx::Class/;
  266         618  
  266         24335  
6 266     266   1829 use DBIx::Class::Carp;
  266         610  
  266         2115  
7 266     266   1721 use DBIx::Class::_Util qw( dbic_internal_try dbic_internal_catch );
  266         638  
  266         13770  
8 266     266   1624 use namespace::clean;
  266         661  
  266         2284  
9              
10             =head1 NAME
11              
12             DBIx::Class::InflateColumn::DateTime - Auto-create DateTime objects from date and datetime columns.
13              
14             =head1 SYNOPSIS
15              
16             Load this component and then declare one or more
17             columns to be of the datetime, timestamp or date datatype.
18              
19             package Event;
20             use base 'DBIx::Class::Core';
21              
22             __PACKAGE__->load_components(qw/InflateColumn::DateTime/);
23             __PACKAGE__->add_columns(
24             starts_when => { data_type => 'datetime' }
25             create_date => { data_type => 'date' }
26             );
27              
28             Then you can treat the specified column as a L object.
29              
30             print "This event starts the month of ".
31             $event->starts_when->month_name();
32              
33             If you want to set a specific time zone and locale for that field, use:
34              
35             __PACKAGE__->add_columns(
36             starts_when => { data_type => 'datetime', time_zone => "America/Chicago", locale => "de_DE" }
37             );
38              
39             Note: DBIC before 0.082900 only accepted C, and silently discarded
40             any C arguments. For backwards compatibility, C will
41             continue being accepted as a synonym for C, and the value will
42             continue to be available in the
43             L<< C hash|DBIx::Class::ResultSource/column_info >>
44             under both names.
45              
46             If you want to inflate no matter what data_type your column is,
47             use inflate_datetime or inflate_date:
48              
49             __PACKAGE__->add_columns(
50             starts_when => { data_type => 'varchar', inflate_datetime => 1 }
51             );
52              
53             __PACKAGE__->add_columns(
54             starts_when => { data_type => 'varchar', inflate_date => 1 }
55             );
56              
57             It's also possible to explicitly skip inflation:
58              
59             __PACKAGE__->add_columns(
60             starts_when => { data_type => 'datetime', inflate_datetime => 0 }
61             );
62              
63             NOTE: Don't rely on C to parse date strings for you.
64             The column is set directly for any non-references and C
65             is completely bypassed. Instead, use an input parser to create a DateTime
66             object. For instance, if your user input comes as a 'YYYY-MM-DD' string, you can
67             use C thusly:
68              
69             use DateTime::Format::ISO8601;
70             my $dt = DateTime::Format::ISO8601->parse_datetime('YYYY-MM-DD');
71              
72             =head1 DESCRIPTION
73              
74             This module figures out the type of DateTime::Format::* class to
75             inflate/deflate with based on the type of DBIx::Class::Storage::DBI::*
76             that you are using. If you switch from one database to a different
77             one your code should continue to work without modification (though note
78             that this feature is new as of 0.07, so it may not be perfect yet - bug
79             reports to the list very much welcome).
80              
81             If the data_type of a field is C, C or C (or
82             a derivative of these datatypes, e.g. C), this
83             module will automatically call the appropriate parse/format method for
84             deflation/inflation as defined in the storage class. For instance, for
85             a C field the methods C and C
86             would be called on deflation/inflation. If the storage class does not
87             provide a specialized inflator/deflator, C<[parse|format]_datetime> will
88             be used as a fallback. See L
89             for more information on date formatting.
90              
91             For more help with using components, see L.
92              
93             =cut
94              
95             __PACKAGE__->load_components(qw/InflateColumn/);
96              
97             =head2 register_column
98              
99             Chains with the L method, and sets
100             up datetime columns appropriately. This would not normally be
101             directly called by end users.
102              
103             In the case of an invalid date, L will throw an exception. To
104             bypass these exceptions and just have the inflation return undef, use
105             the C option in the column info:
106              
107             "broken_date",
108             {
109             data_type => "datetime",
110             default_value => '0000-00-00',
111             is_nullable => 1,
112             datetime_undef_if_invalid => 1
113             }
114              
115             =cut
116              
117             sub register_column {
118 3908     3908 1 11334 my ($self, $column, $info, @rest) = @_;
119              
120 3908         16468 $self->next::method($column, $info, @rest);
121              
122 3908         371957 my $requested_type;
123 3908         9247 for (qw/datetime timestamp date/) {
124 10688         21078 my $key = "inflate_${_}";
125 10688 100       26184 if (exists $info->{$key}) {
126              
127             # this bailout is intentional
128 776 100       3208 return unless $info->{$key};
129              
130 518         1239 $requested_type = $_;
131 518         1214 last;
132             }
133             }
134              
135 3650 50 66     15377 return if (!$requested_type and !$info->{data_type});
136              
137 3650   50     12211 my $data_type = lc( $info->{data_type} || '' );
138              
139             # _ic_dt_method will follow whatever the registration requests
140             # thus = instead of ||=
141 3650 100 66     28476 if ($data_type eq 'timestamp with time zone' || $data_type eq 'timestamptz') {
    100          
    100          
    100          
    100          
142 2         7 $info->{_ic_dt_method} = 'timestamp_with_timezone';
143             }
144             elsif ($data_type eq 'timestamp without time zone') {
145 4         8 $info->{_ic_dt_method} = 'timestamp_without_timezone';
146             }
147             elsif ($data_type eq 'smalldatetime') {
148 2         5 $info->{_ic_dt_method} = 'smalldatetime';
149             }
150             elsif ($data_type =~ /^ (?: date | datetime | timestamp ) $/x) {
151 1304         4045 $info->{_ic_dt_method} = $data_type;
152             }
153             elsif ($requested_type) {
154 516         1541 $info->{_ic_dt_method} = $requested_type;
155             }
156             else {
157 1822         7009 return;
158             }
159              
160 1828 100       5876 if ($info->{extra}) {
161 4         8 for my $slot (qw/time_zone timezone locale floating_tz_ok/) {
162 16 100       44 if ( defined $info->{extra}{$slot} ) {
163 8         42 carp "Putting $slot into extra => { $slot => '...' } has been deprecated, ".
164             "please put it directly into the '$column' column definition.";
165 8 50       81 $info->{$slot} = $info->{extra}{$slot} unless defined $info->{$slot};
166             }
167             }
168             }
169              
170             # Store the time zone under both 'timezone' for backwards compatibility and
171             # 'time_zone' for DateTime ecosystem consistency
172 1828 100       6955 if ( defined $info->{timezone} ) {
    100          
173             $self->throw_exception("Conflicting 'timezone' and 'time_zone' values in '$column' column defintion.")
174 4 50 33     19 if defined $info->{time_zone} and $info->{time_zone} ne $info->{timezone};
175 4         12 $info->{time_zone} = $info->{timezone};
176             }
177             elsif ( defined $info->{time_zone} ) {
178 8         16 $info->{timezone} = $info->{time_zone};
179             }
180              
181             # shallow copy to avoid unfounded(?) Devel::Cycle complaints
182 1828         7658 my $infcopy = {%$info};
183              
184             $self->inflate_column(
185             $column =>
186             {
187             inflate => sub {
188 1     1   5 my ($value, $obj) = @_;
189              
190             # propagate for error reporting
191 1         5 $infcopy->{__dbic_colname} = $column;
192              
193 1         14 my $dt = $obj->_inflate_to_datetime( $value, $infcopy );
194              
195 0 0       0 return (defined $dt)
196             ? $obj->_post_inflate_datetime( $dt, $infcopy )
197             : undef
198             ;
199             },
200             deflate => sub {
201 0     0   0 my ($value, $obj) = @_;
202              
203 0         0 $value = $obj->_pre_deflate_datetime( $value, $infcopy );
204 0         0 $obj->_deflate_from_datetime( $value, $infcopy );
205             },
206             }
207 1828         35682 );
208             }
209              
210             sub _flate_or_fallback
211             {
212 1     1   5 my( $self, $value, $info, $method_fmt ) = @_;
213              
214 1         11 my $parser = $self->_datetime_parser;
215 0         0 my $preferred_method = sprintf($method_fmt, $info->{ _ic_dt_method });
216 0   0     0 my $method = $parser->can($preferred_method) || sprintf($method_fmt, 'datetime');
217              
218             dbic_internal_try {
219 0     0   0 $parser->$method($value);
220             }
221             dbic_internal_catch {
222             $self->throw_exception ("Error while inflating '$value' for $info->{__dbic_colname} on ${self}: $_")
223 0 0   0   0 unless $info->{datetime_undef_if_invalid};
224              
225 0         0 undef; # rv
226 0         0 };
227             }
228              
229             sub _inflate_to_datetime {
230 1     1   4 my( $self, $value, $info ) = @_;
231 1         7 return $self->_flate_or_fallback( $value, $info, 'parse_%s' );
232             }
233              
234             sub _deflate_from_datetime {
235 0     0   0 my( $self, $value, $info ) = @_;
236 0         0 return $self->_flate_or_fallback( $value, $info, 'format_%s' );
237             }
238              
239             sub _datetime_parser {
240 1     1   5 shift->result_source->schema->storage->datetime_parser (@_);
241             }
242              
243             sub _post_inflate_datetime {
244 0     0     my( $self, $dt, $info ) = @_;
245              
246 0 0         $dt->set_time_zone($info->{time_zone}) if defined $info->{time_zone};
247 0 0         $dt->set_locale($info->{locale}) if defined $info->{locale};
248              
249 0           return $dt;
250             }
251              
252             sub _pre_deflate_datetime {
253 0     0     my( $self, $dt, $info ) = @_;
254              
255 0 0         if (defined $info->{time_zone}) {
256             carp "You're using a floating time zone, please see the documentation of"
257             . " DBIx::Class::InflateColumn::DateTime for an explanation"
258             if ref( $dt->time_zone ) eq 'DateTime::TimeZone::Floating'
259             and not $info->{floating_tz_ok}
260 0 0 0       and not $ENV{DBIC_FLOATING_TZ_OK};
      0        
261              
262 0           $dt->set_time_zone($info->{time_zone});
263             }
264              
265 0 0         $dt->set_locale($info->{locale}) if defined $info->{locale};
266              
267 0           return $dt;
268             }
269              
270             1;
271             __END__