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 timezone and locale for that field, use:
36 __PACKAGE__->add_columns(
37 starts_when => { data_type => 'datetime', timezone => "America/Chicago", locale => "de_DE" }
40 If you want to inflate no matter what data_type your column is,
41 use inflate_datetime or inflate_date:
43 __PACKAGE__->add_columns(
44 starts_when => { data_type => 'varchar', inflate_datetime => 1 }
47 __PACKAGE__->add_columns(
48 starts_when => { data_type => 'varchar', inflate_date => 1 }
51 It's also possible to explicitly skip inflation:
53 __PACKAGE__->add_columns(
54 starts_when => { data_type => 'datetime', inflate_datetime => 0 }
57 NOTE: Don't rely on C<InflateColumn::DateTime> to parse date strings for you.
58 The column is set directly for any non-references and C<InflateColumn::DateTime>
59 is completely bypassed. Instead, use an input parser to create a DateTime
60 object. For instance, if your user input comes as a 'YYYY-MM-DD' string, you can
61 use C<DateTime::Format::ISO8601> thusly:
63 use DateTime::Format::ISO8601;
64 my $dt = DateTime::Format::ISO8601->parse_datetime('YYYY-MM-DD');
68 This module figures out the type of DateTime::Format::* class to
69 inflate/deflate with based on the type of DBIx::Class::Storage::DBI::*
70 that you are using. If you switch from one database to a different
71 one your code should continue to work without modification (though note
72 that this feature is new as of 0.07, so it may not be perfect yet - bug
73 reports to the list very much welcome).
75 If the data_type of a field is C<date>, C<datetime> or C<timestamp> (or
76 a derivative of these datatypes, e.g. C<timestamp with timezone>), this
77 module will automatically call the appropriate parse/format method for
78 deflation/inflation as defined in the storage class. For instance, for
79 a C<datetime> field the methods C<parse_datetime> and C<format_datetime>
80 would be called on deflation/inflation. If the storage class does not
81 provide a specialized inflator/deflator, C<[parse|format]_datetime> will
82 be used as a fallback. See L<DateTime/Formatters And Stringification>
83 for more information on date formatting.
85 For more help with using components, see L<DBIx::Class::Manual::Component/USING>.
89 __PACKAGE__->load_components(qw/InflateColumn/);
91 =head2 register_column
93 Chains with the L<DBIx::Class::Row/register_column> method, and sets
94 up datetime columns appropriately. This would not normally be
95 directly called by end users.
97 In the case of an invalid date, L<DateTime> will throw an exception. To
98 bypass these exceptions and just have the inflation return undef, use
99 the C<datetime_undef_if_invalid> option in the column info:
103 data_type => "datetime",
104 default_value => '0000-00-00',
106 datetime_undef_if_invalid => 1
111 sub register_column {
112 my ($self, $column, $info, @rest) = @_;
114 $self->next::method($column, $info, @rest);
117 for (qw/datetime timestamp date/) {
118 my $key = "inflate_${_}";
119 if (exists $info->{$key}) {
121 # this bailout is intentional
122 return unless $info->{$key};
124 $requested_type = $_;
129 return if (!$requested_type and !$info->{data_type});
131 my $data_type = lc( $info->{data_type} || '' );
133 # _ic_dt_method will follow whatever the registration requests
134 # thus = instead of ||=
135 if ($data_type eq 'timestamp with time zone' || $data_type eq 'timestamptz') {
136 $info->{_ic_dt_method} = 'timestamp_with_timezone';
138 elsif ($data_type eq 'timestamp without time zone') {
139 $info->{_ic_dt_method} = 'timestamp_without_timezone';
141 elsif ($data_type eq 'smalldatetime') {
142 $info->{_ic_dt_method} = 'smalldatetime';
144 elsif ($data_type =~ /^ (?: date | datetime | timestamp ) $/x) {
145 $info->{_ic_dt_method} = $data_type;
147 elsif ($requested_type) {
148 $info->{_ic_dt_method} = $requested_type;
154 if ($info->{extra}) {
155 for my $slot (qw/timezone locale floating_tz_ok/) {
156 if ( defined $info->{extra}{$slot} ) {
157 carp "Putting $slot into extra => { $slot => '...' } has been deprecated, ".
158 "please put it directly into the '$column' column definition.";
159 $info->{$slot} = $info->{extra}{$slot} unless defined $info->{$slot};
164 # shallow copy to avoid unfounded(?) Devel::Cycle complaints
165 my $infcopy = {%$info};
167 $self->inflate_column(
171 my ($value, $obj) = @_;
173 # propagate for error reporting
174 $infcopy->{__dbic_colname} = $column;
176 my $dt = $obj->_inflate_to_datetime( $value, $infcopy );
179 ? $obj->_post_inflate_datetime( $dt, $infcopy )
184 my ($value, $obj) = @_;
186 $value = $obj->_pre_deflate_datetime( $value, $infcopy );
187 $obj->_deflate_from_datetime( $value, $infcopy );
193 sub _flate_or_fallback
195 my( $self, $value, $info, $method_fmt ) = @_;
197 my $parser = $self->_datetime_parser;
198 my $preferred_method = sprintf($method_fmt, $info->{ _ic_dt_method });
199 my $method = $parser->can($preferred_method) || sprintf($method_fmt, 'datetime');
201 return dbic_internal_try {
202 $parser->$method($value);
205 $self->throw_exception ("Error while inflating '$value' for $info->{__dbic_colname} on ${self}: $_")
206 unless $info->{datetime_undef_if_invalid};
211 sub _inflate_to_datetime {
212 my( $self, $value, $info ) = @_;
213 return $self->_flate_or_fallback( $value, $info, 'parse_%s' );
216 sub _deflate_from_datetime {
217 my( $self, $value, $info ) = @_;
218 return $self->_flate_or_fallback( $value, $info, 'format_%s' );
221 sub _datetime_parser {
222 shift->result_source->storage->datetime_parser (@_);
225 sub _post_inflate_datetime {
226 my( $self, $dt, $info ) = @_;
228 $dt->set_time_zone($info->{timezone}) if defined $info->{timezone};
229 $dt->set_locale($info->{locale}) if defined $info->{locale};
234 sub _pre_deflate_datetime {
235 my( $self, $dt, $info ) = @_;
237 if (defined $info->{timezone}) {
238 carp "You're using a floating timezone, please see the documentation of"
239 . " DBIx::Class::InflateColumn::DateTime for an explanation"
240 if ref( $dt->time_zone ) eq 'DateTime::TimeZone::Floating'
241 and not $info->{floating_tz_ok}
242 and not $ENV{DBIC_FLOATING_TZ_OK};
244 $dt->set_time_zone($info->{timezone});
247 $dt->set_locale($info->{locale}) if defined $info->{locale};
257 If you have a datetime column with an associated C<timezone>, and subsequently
258 create/update this column with a DateTime object in the L<DateTime::TimeZone::Floating>
259 timezone, you will get a warning (as there is a very good chance this will not have the
260 result you expect). For example:
262 __PACKAGE__->add_columns(
263 starts_when => { data_type => 'datetime', timezone => "America/Chicago" }
266 my $event = $schema->resultset('EventTZ')->create({
267 starts_at => DateTime->new(year=>2007, month=>12, day=>31, ),
270 The warning can be avoided in several ways:
274 =item Fix your broken code
276 When calling C<set_time_zone> on a Floating DateTime object, the timezone is simply
277 set to the requested value, and B<no time conversion takes place>. It is always a good idea
278 to be supply explicit times to the database:
280 my $event = $schema->resultset('EventTZ')->create({
281 starts_at => DateTime->new(year=>2007, month=>12, day=>31, time_zone => "America/Chicago" ),
284 =item Suppress the check on per-column basis
286 __PACKAGE__->add_columns(
287 starts_when => { data_type => 'datetime', timezone => "America/Chicago", floating_tz_ok => 1 }
290 =item Suppress the check globally
292 Set the environment variable DBIC_FLOATING_TZ_OK to some true value.
296 Putting extra attributes like timezone, locale or floating_tz_ok into extra => {} has been
297 B<DEPRECATED> because this gets you into trouble using L<DBIx::Class::Schema::Versioned>.
298 Instead put it directly into the columns definition like in the examples above. If you still
299 use the old way you'll see a warning - please fix your code then!
305 =item More information about the add_columns method, and column metadata,
306 can be found in the documentation for L<DBIx::Class::ResultSource>.
308 =item Further discussion of problems inherent to the Floating timezone:
309 L<Floating DateTimes|DateTime/Floating DateTimes>
310 and L<< $dt->set_time_zone|DateTime/"Set" Methods >>
314 =head1 FURTHER QUESTIONS?
316 Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
318 =head1 COPYRIGHT AND LICENSE
320 This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
321 by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
322 redistribute it and/or modify it under the same terms as the
323 L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.