1 package DBIx::Class::InflateColumn::DateTime;
5 use base qw/DBIx::Class/;
7 use DBIx::Class::_Util qw( dbic_internal_try dbic_internal_catch );
12 DBIx::Class::InflateColumn::DateTime - Auto-create DateTime objects from date and datetime columns.
16 Load this component and then declare one or more
17 columns to be of the datetime, timestamp or date datatype.
20 use base 'DBIx::Class::Core';
22 __PACKAGE__->load_components(qw/InflateColumn::DateTime/);
23 __PACKAGE__->add_columns(
24 starts_when => { data_type => 'datetime' }
25 create_date => { data_type => 'date' }
28 Then you can treat the specified column as a L<DateTime> object.
30 print "This event starts the month of ".
31 $event->starts_when->month_name();
33 If you want to set a specific time zone and locale for that field, use:
35 __PACKAGE__->add_columns(
36 starts_when => { data_type => 'datetime', time_zone => "America/Chicago", locale => "de_DE" }
39 Note: DBIC before 0.082900 only accepted C<timezone>, and silently discarded
40 any C<time_zone> arguments. For backwards compatibility, C<timezone> will
41 continue being accepted as a synonym for C<time_zone>, and the value will
42 continue to be available in the
43 L<< C<column_info> hash|DBIx::Class::ResultSource/column_info >>
46 If you want to inflate no matter what data_type your column is,
47 use inflate_datetime or inflate_date:
49 __PACKAGE__->add_columns(
50 starts_when => { data_type => 'varchar', inflate_datetime => 1 }
53 __PACKAGE__->add_columns(
54 starts_when => { data_type => 'varchar', inflate_date => 1 }
57 It's also possible to explicitly skip inflation:
59 __PACKAGE__->add_columns(
60 starts_when => { data_type => 'datetime', inflate_datetime => 0 }
63 NOTE: Don't rely on C<InflateColumn::DateTime> to parse date strings for you.
64 The column is set directly for any non-references and C<InflateColumn::DateTime>
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<DateTime::Format::ISO8601> thusly:
69 use DateTime::Format::ISO8601;
70 my $dt = DateTime::Format::ISO8601->parse_datetime('YYYY-MM-DD');
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).
81 If the data_type of a field is C<date>, C<datetime> or C<timestamp> (or
82 a derivative of these datatypes, e.g. C<timestamp with time zone>), 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<datetime> field the methods C<parse_datetime> and C<format_datetime>
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<DateTime/Formatters And Stringification>
89 for more information on date formatting.
91 For more help with using components, see L<DBIx::Class::Manual::Component/USING>.
95 __PACKAGE__->load_components(qw/InflateColumn/);
97 =head2 register_column
99 Chains with the L<DBIx::Class::Row/register_column> method, and sets
100 up datetime columns appropriately. This would not normally be
101 directly called by end users.
103 In the case of an invalid date, L<DateTime> will throw an exception. To
104 bypass these exceptions and just have the inflation return undef, use
105 the C<datetime_undef_if_invalid> option in the column info:
109 data_type => "datetime",
110 default_value => '0000-00-00',
112 datetime_undef_if_invalid => 1
117 sub register_column {
118 my ($self, $column, $info, @rest) = @_;
120 $self->next::method($column, $info, @rest);
123 for (qw/datetime timestamp date/) {
124 my $key = "inflate_${_}";
125 if (exists $info->{$key}) {
127 # this bailout is intentional
128 return unless $info->{$key};
130 $requested_type = $_;
135 return if (!$requested_type and !$info->{data_type});
137 my $data_type = lc( $info->{data_type} || '' );
139 # _ic_dt_method will follow whatever the registration requests
140 # thus = instead of ||=
141 if ($data_type eq 'timestamp with time zone' || $data_type eq 'timestamptz') {
142 $info->{_ic_dt_method} = 'timestamp_with_timezone';
144 elsif ($data_type eq 'timestamp without time zone') {
145 $info->{_ic_dt_method} = 'timestamp_without_timezone';
147 elsif ($data_type eq 'smalldatetime') {
148 $info->{_ic_dt_method} = 'smalldatetime';
150 elsif ($data_type =~ /^ (?: date | datetime | timestamp ) $/x) {
151 $info->{_ic_dt_method} = $data_type;
153 elsif ($requested_type) {
154 $info->{_ic_dt_method} = $requested_type;
160 if ($info->{extra}) {
161 for my $slot (qw/time_zone timezone locale floating_tz_ok/) {
162 if ( defined $info->{extra}{$slot} ) {
163 carp "Putting $slot into extra => { $slot => '...' } has been deprecated, ".
164 "please put it directly into the '$column' column definition.";
165 $info->{$slot} = $info->{extra}{$slot} unless defined $info->{$slot};
170 # Store the time zone under both 'timezone' for backwards compatibility and
171 # 'time_zone' for DateTime ecosystem consistency
172 if ( defined $info->{timezone} ) {
173 $self->throw_exception("Conflicting 'timezone' and 'time_zone' values in '$column' column defintion.")
174 if defined $info->{time_zone} and $info->{time_zone} ne $info->{timezone};
175 $info->{time_zone} = $info->{timezone};
177 elsif ( defined $info->{time_zone} ) {
178 $info->{timezone} = $info->{time_zone};
181 # shallow copy to avoid unfounded(?) Devel::Cycle complaints
182 my $infcopy = {%$info};
184 $self->inflate_column(
188 my ($value, $obj) = @_;
190 # propagate for error reporting
191 $infcopy->{__dbic_colname} = $column;
193 my $dt = $obj->_inflate_to_datetime( $value, $infcopy );
196 ? $obj->_post_inflate_datetime( $dt, $infcopy )
201 my ($value, $obj) = @_;
203 $value = $obj->_pre_deflate_datetime( $value, $infcopy );
204 $obj->_deflate_from_datetime( $value, $infcopy );
210 sub _flate_or_fallback
212 my( $self, $value, $info, $method_fmt ) = @_;
214 my $parser = $self->_datetime_parser;
215 my $preferred_method = sprintf($method_fmt, $info->{ _ic_dt_method });
216 my $method = $parser->can($preferred_method) || sprintf($method_fmt, 'datetime');
219 $parser->$method($value);
221 dbic_internal_catch {
222 $self->throw_exception ("Error while inflating '$value' for $info->{__dbic_colname} on ${self}: $_")
223 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>.