1 package DBIx::Class::InflateColumn::DateTime;
5 use base qw/DBIx::Class/;
7 use DBIx::Class::_Util 'dbic_internal_try';
13 DBIx::Class::InflateColumn::DateTime - Auto-create DateTime objects from date and datetime columns.
17 Load this component and then declare one or more
18 columns to be of the datetime, timestamp or date datatype.
21 use base 'DBIx::Class::Core';
23 __PACKAGE__->load_components(qw/InflateColumn::DateTime/);
24 __PACKAGE__->add_columns(
25 starts_when => { data_type => 'datetime' }
26 create_date => { data_type => 'date' }
29 Then you can treat the specified column as a L<DateTime> object.
31 print "This event starts the month of ".
32 $event->starts_when->month_name();
34 If you want to set a specific time zone and locale for that field, use:
36 __PACKAGE__->add_columns(
37 starts_when => { data_type => 'datetime', time_zone => "America/Chicago", locale => "de_DE" }
40 Note: DBIC before 0.082900 only accepted C<timezone>, and silently discarded
41 any C<time_zone> arguments. For backwards compatibility, C<timezone> will
42 continue being accepted as a synonym for C<time_zone>, and the value will
43 continue to be available in the
44 L<< C<column_info> hash|DBIx::Class::ResultSource/column_info >>
47 If you want to inflate no matter what data_type your column is,
48 use inflate_datetime or inflate_date:
50 __PACKAGE__->add_columns(
51 starts_when => { data_type => 'varchar', inflate_datetime => 1 }
54 __PACKAGE__->add_columns(
55 starts_when => { data_type => 'varchar', inflate_date => 1 }
58 It's also possible to explicitly skip inflation:
60 __PACKAGE__->add_columns(
61 starts_when => { data_type => 'datetime', inflate_datetime => 0 }
64 NOTE: Don't rely on C<InflateColumn::DateTime> to parse date strings for you.
65 The column is set directly for any non-references and C<InflateColumn::DateTime>
66 is completely bypassed. Instead, use an input parser to create a DateTime
67 object. For instance, if your user input comes as a 'YYYY-MM-DD' string, you can
68 use C<DateTime::Format::ISO8601> thusly:
70 use DateTime::Format::ISO8601;
71 my $dt = DateTime::Format::ISO8601->parse_datetime('YYYY-MM-DD');
75 This module figures out the type of DateTime::Format::* class to
76 inflate/deflate with based on the type of DBIx::Class::Storage::DBI::*
77 that you are using. If you switch from one database to a different
78 one your code should continue to work without modification (though note
79 that this feature is new as of 0.07, so it may not be perfect yet - bug
80 reports to the list very much welcome).
82 If the data_type of a field is C<date>, C<datetime> or C<timestamp> (or
83 a derivative of these datatypes, e.g. C<timestamp with time zone>), this
84 module will automatically call the appropriate parse/format method for
85 deflation/inflation as defined in the storage class. For instance, for
86 a C<datetime> field the methods C<parse_datetime> and C<format_datetime>
87 would be called on deflation/inflation. If the storage class does not
88 provide a specialized inflator/deflator, C<[parse|format]_datetime> will
89 be used as a fallback. See L<DateTime/Formatters And Stringification>
90 for more information on date formatting.
92 For more help with using components, see L<DBIx::Class::Manual::Component/USING>.
96 __PACKAGE__->load_components(qw/InflateColumn/);
98 =head2 register_column
100 Chains with the L<DBIx::Class::Row/register_column> method, and sets
101 up datetime columns appropriately. This would not normally be
102 directly called by end users.
104 In the case of an invalid date, L<DateTime> will throw an exception. To
105 bypass these exceptions and just have the inflation return undef, use
106 the C<datetime_undef_if_invalid> option in the column info:
110 data_type => "datetime",
111 default_value => '0000-00-00',
113 datetime_undef_if_invalid => 1
118 sub register_column {
119 my ($self, $column, $info, @rest) = @_;
121 $self->next::method($column, $info, @rest);
124 for (qw/datetime timestamp date/) {
125 my $key = "inflate_${_}";
126 if (exists $info->{$key}) {
128 # this bailout is intentional
129 return unless $info->{$key};
131 $requested_type = $_;
136 return if (!$requested_type and !$info->{data_type});
138 my $data_type = lc( $info->{data_type} || '' );
140 # _ic_dt_method will follow whatever the registration requests
141 # thus = instead of ||=
142 if ($data_type eq 'timestamp with time zone' || $data_type eq 'timestamptz') {
143 $info->{_ic_dt_method} = 'timestamp_with_timezone';
145 elsif ($data_type eq 'timestamp without time zone') {
146 $info->{_ic_dt_method} = 'timestamp_without_timezone';
148 elsif ($data_type eq 'smalldatetime') {
149 $info->{_ic_dt_method} = 'smalldatetime';
151 elsif ($data_type =~ /^ (?: date | datetime | timestamp ) $/x) {
152 $info->{_ic_dt_method} = $data_type;
154 elsif ($requested_type) {
155 $info->{_ic_dt_method} = $requested_type;
161 if ($info->{extra}) {
162 for my $slot (qw/time_zone timezone locale floating_tz_ok/) {
163 if ( defined $info->{extra}{$slot} ) {
164 carp "Putting $slot into extra => { $slot => '...' } has been deprecated, ".
165 "please put it directly into the '$column' column definition.";
166 $info->{$slot} = $info->{extra}{$slot} unless defined $info->{$slot};
171 # Store the time zone under both 'timezone' for backwards compatibility and
172 # 'time_zone' for DateTime ecosystem consistency
173 if ( defined $info->{timezone} ) {
174 $self->throw_exception("Conflicting 'timezone' and 'time_zone' values in '$column' column defintion.")
175 if defined $info->{time_zone} and $info->{time_zone} ne $info->{timezone};
176 $info->{time_zone} = $info->{timezone};
178 elsif ( defined $info->{time_zone} ) {
179 $info->{timezone} = $info->{time_zone};
182 # shallow copy to avoid unfounded(?) Devel::Cycle complaints
183 my $infcopy = {%$info};
185 $self->inflate_column(
189 my ($value, $obj) = @_;
191 # propagate for error reporting
192 $infcopy->{__dbic_colname} = $column;
194 my $dt = $obj->_inflate_to_datetime( $value, $infcopy );
197 ? $obj->_post_inflate_datetime( $dt, $infcopy )
202 my ($value, $obj) = @_;
204 $value = $obj->_pre_deflate_datetime( $value, $infcopy );
205 $obj->_deflate_from_datetime( $value, $infcopy );
211 sub _flate_or_fallback
213 my( $self, $value, $info, $method_fmt ) = @_;
215 my $parser = $self->_datetime_parser;
216 my $preferred_method = sprintf($method_fmt, $info->{ _ic_dt_method });
217 my $method = $parser->can($preferred_method) || sprintf($method_fmt, 'datetime');
219 return dbic_internal_try {
220 $parser->$method($value);
223 $self->throw_exception ("Error while inflating '$value' for $info->{__dbic_colname} on ${self}: $_")
224 unless $info->{datetime_undef_if_invalid};
229 sub _inflate_to_datetime {
230 my( $self, $value, $info ) = @_;
231 return $self->_flate_or_fallback( $value, $info, 'parse_%s' );
234 sub _deflate_from_datetime {
235 my( $self, $value, $info ) = @_;
236 return $self->_flate_or_fallback( $value, $info, 'format_%s' );
239 sub _datetime_parser {
240 shift->result_source->schema->storage->datetime_parser (@_);
243 sub _post_inflate_datetime {
244 my( $self, $dt, $info ) = @_;
246 $dt->set_time_zone($info->{time_zone}) if defined $info->{time_zone};
247 $dt->set_locale($info->{locale}) if defined $info->{locale};
252 sub _pre_deflate_datetime {
253 my( $self, $dt, $info ) = @_;
255 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 and not $ENV{DBIC_FLOATING_TZ_OK};
262 $dt->set_time_zone($info->{time_zone});
265 $dt->set_locale($info->{locale}) if defined $info->{locale};
275 If you have a datetime column with an associated C<time_zone>, and subsequently
276 create/update this column with a DateTime object in the L<DateTime::TimeZone::Floating>
277 time zone, you will get a warning (as there is a very good chance this will not have the
278 result you expect). For example:
280 __PACKAGE__->add_columns(
281 starts_when => { data_type => 'datetime', time_zone => "America/Chicago" }
284 my $event = $schema->resultset('EventTZ')->create({
285 starts_at => DateTime->new(year=>2007, month=>12, day=>31, ),
288 The warning can be avoided in several ways:
292 =item Fix your broken code
294 When calling C<set_time_zone> on a Floating DateTime object, the time zone is simply
295 set to the requested value, and B<no time conversion takes place>. It is always a good idea
296 to be supply explicit times to the database:
298 my $event = $schema->resultset('EventTZ')->create({
299 starts_at => DateTime->new(year=>2007, month=>12, day=>31, time_zone => "America/Chicago" ),
302 =item Suppress the check on per-column basis
304 __PACKAGE__->add_columns(
305 starts_when => { data_type => 'datetime', time_zone => "America/Chicago", floating_tz_ok => 1 }
308 =item Suppress the check globally
310 Set the environment variable DBIC_FLOATING_TZ_OK to some true value.
314 Putting extra attributes like time_zone, locale or floating_tz_ok into extra => {} has been
315 B<DEPRECATED> because this gets you into trouble using L<DBIx::Class::Schema::Versioned>.
316 Instead put it directly into the columns definition like in the examples above. If you still
317 use the old way you'll see a warning - please fix your code then!
323 =item More information about the add_columns method, and column metadata,
324 can be found in the documentation for L<DBIx::Class::ResultSource>.
326 =item Further discussion of problems inherent to the Floating time zone:
327 L<Floating DateTimes|DateTime/Floating DateTimes>
328 and L<< $dt->set_time_zone|DateTime/"Set" Methods >>
332 =head1 FURTHER QUESTIONS?
334 Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
336 =head1 COPYRIGHT AND LICENSE
338 This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
339 by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
340 redistribute it and/or modify it under the same terms as the
341 L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.