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>.
44 If you want to inflate no matter what data_type your column is,
45 use inflate_datetime or inflate_date:
47 __PACKAGE__->add_columns(
48 starts_when => { data_type => 'varchar', inflate_datetime => 1 }
51 __PACKAGE__->add_columns(
52 starts_when => { data_type => 'varchar', inflate_date => 1 }
55 It's also possible to explicitly skip inflation:
57 __PACKAGE__->add_columns(
58 starts_when => { data_type => 'datetime', inflate_datetime => 0 }
61 NOTE: Don't rely on C<InflateColumn::DateTime> to parse date strings for you.
62 The column is set directly for any non-references and C<InflateColumn::DateTime>
63 is completely bypassed. Instead, use an input parser to create a DateTime
64 object. For instance, if your user input comes as a 'YYYY-MM-DD' string, you can
65 use C<DateTime::Format::ISO8601> thusly:
67 use DateTime::Format::ISO8601;
68 my $dt = DateTime::Format::ISO8601->parse_datetime('YYYY-MM-DD');
72 This module figures out the type of DateTime::Format::* class to
73 inflate/deflate with based on the type of DBIx::Class::Storage::DBI::*
74 that you are using. If you switch from one database to a different
75 one your code should continue to work without modification (though note
76 that this feature is new as of 0.07, so it may not be perfect yet - bug
77 reports to the list very much welcome).
79 If the data_type of a field is C<date>, C<datetime> or C<timestamp> (or
80 a derivative of these datatypes, e.g. C<timestamp with time zone>), this
81 module will automatically call the appropriate parse/format method for
82 deflation/inflation as defined in the storage class. For instance, for
83 a C<datetime> field the methods C<parse_datetime> and C<format_datetime>
84 would be called on deflation/inflation. If the storage class does not
85 provide a specialized inflator/deflator, C<[parse|format]_datetime> will
86 be used as a fallback. See L<DateTime/Formatters And Stringification>
87 for more information on date formatting.
89 For more help with using components, see L<DBIx::Class::Manual::Component/USING>.
93 __PACKAGE__->load_components(qw/InflateColumn/);
95 =head2 register_column
97 Chains with the L<DBIx::Class::Row/register_column> method, and sets
98 up datetime columns appropriately. This would not normally be
99 directly called by end users.
101 In the case of an invalid date, L<DateTime> will throw an exception. To
102 bypass these exceptions and just have the inflation return undef, use
103 the C<datetime_undef_if_invalid> option in the column info:
107 data_type => "datetime",
108 default_value => '0000-00-00',
110 datetime_undef_if_invalid => 1
115 sub register_column {
116 my ($self, $column, $info, @rest) = @_;
118 $self->next::method($column, $info, @rest);
121 for (qw/datetime timestamp date/) {
122 my $key = "inflate_${_}";
123 if (exists $info->{$key}) {
125 # this bailout is intentional
126 return unless $info->{$key};
128 $requested_type = $_;
133 return if (!$requested_type and !$info->{data_type});
135 my $data_type = lc( $info->{data_type} || '' );
137 # _ic_dt_method will follow whatever the registration requests
138 # thus = instead of ||=
139 if ($data_type eq 'timestamp with time zone' || $data_type eq 'timestamptz') {
140 $info->{_ic_dt_method} = 'timestamp_with_timezone';
142 elsif ($data_type eq 'timestamp without time zone') {
143 $info->{_ic_dt_method} = 'timestamp_without_timezone';
145 elsif ($data_type eq 'smalldatetime') {
146 $info->{_ic_dt_method} = 'smalldatetime';
148 elsif ($data_type =~ /^ (?: date | datetime | timestamp ) $/x) {
149 $info->{_ic_dt_method} = $data_type;
151 elsif ($requested_type) {
152 $info->{_ic_dt_method} = $requested_type;
158 if ($info->{extra}) {
159 for my $slot (qw/time_zone timezone locale floating_tz_ok/) {
160 if ( defined $info->{extra}{$slot} ) {
161 carp "Putting $slot into extra => { $slot => '...' } has been deprecated, ".
162 "please put it directly into the '$column' column definition.";
163 $info->{$slot} = $info->{extra}{$slot} unless defined $info->{$slot};
168 if ( defined $info->{timezone} ) {
169 $self->throw_exception("Cannot specify both 'timezone' and 'time_zone' in '$column' column defintion.")
170 if defined $info->{time_zone};
171 $info->{time_zone} = delete $info->{timezone};
174 # shallow copy to avoid unfounded(?) Devel::Cycle complaints
175 my $infcopy = {%$info};
177 $self->inflate_column(
181 my ($value, $obj) = @_;
183 # propagate for error reporting
184 $infcopy->{__dbic_colname} = $column;
186 my $dt = $obj->_inflate_to_datetime( $value, $infcopy );
189 ? $obj->_post_inflate_datetime( $dt, $infcopy )
194 my ($value, $obj) = @_;
196 $value = $obj->_pre_deflate_datetime( $value, $infcopy );
197 $obj->_deflate_from_datetime( $value, $infcopy );
203 sub _flate_or_fallback
205 my( $self, $value, $info, $method_fmt ) = @_;
207 my $parser = $self->_datetime_parser;
208 my $preferred_method = sprintf($method_fmt, $info->{ _ic_dt_method });
209 my $method = $parser->can($preferred_method) || sprintf($method_fmt, 'datetime');
211 return dbic_internal_try {
212 $parser->$method($value);
215 $self->throw_exception ("Error while inflating '$value' for $info->{__dbic_colname} on ${self}: $_")
216 unless $info->{datetime_undef_if_invalid};
221 sub _inflate_to_datetime {
222 my( $self, $value, $info ) = @_;
223 return $self->_flate_or_fallback( $value, $info, 'parse_%s' );
226 sub _deflate_from_datetime {
227 my( $self, $value, $info ) = @_;
228 return $self->_flate_or_fallback( $value, $info, 'format_%s' );
231 sub _datetime_parser {
232 shift->result_source->schema->storage->datetime_parser (@_);
235 sub _post_inflate_datetime {
236 my( $self, $dt, $info ) = @_;
238 $dt->set_time_zone($info->{time_zone}) if defined $info->{time_zone};
239 $dt->set_locale($info->{locale}) if defined $info->{locale};
244 sub _pre_deflate_datetime {
245 my( $self, $dt, $info ) = @_;
247 if (defined $info->{time_zone}) {
248 carp "You're using a floating time zone, please see the documentation of"
249 . " DBIx::Class::InflateColumn::DateTime for an explanation"
250 if ref( $dt->time_zone ) eq 'DateTime::TimeZone::Floating'
251 and not $info->{floating_tz_ok}
252 and not $ENV{DBIC_FLOATING_TZ_OK};
254 $dt->set_time_zone($info->{time_zone});
257 $dt->set_locale($info->{locale}) if defined $info->{locale};
267 If you have a datetime column with an associated C<time_zone>, and subsequently
268 create/update this column with a DateTime object in the L<DateTime::TimeZone::Floating>
269 time zone, you will get a warning (as there is a very good chance this will not have the
270 result you expect). For example:
272 __PACKAGE__->add_columns(
273 starts_when => { data_type => 'datetime', time_zone => "America/Chicago" }
276 my $event = $schema->resultset('EventTZ')->create({
277 starts_at => DateTime->new(year=>2007, month=>12, day=>31, ),
280 The warning can be avoided in several ways:
284 =item Fix your broken code
286 When calling C<set_time_zone> on a Floating DateTime object, the time zone is simply
287 set to the requested value, and B<no time conversion takes place>. It is always a good idea
288 to be supply explicit times to the database:
290 my $event = $schema->resultset('EventTZ')->create({
291 starts_at => DateTime->new(year=>2007, month=>12, day=>31, time_zone => "America/Chicago" ),
294 =item Suppress the check on per-column basis
296 __PACKAGE__->add_columns(
297 starts_when => { data_type => 'datetime', time_zone => "America/Chicago", floating_tz_ok => 1 }
300 =item Suppress the check globally
302 Set the environment variable DBIC_FLOATING_TZ_OK to some true value.
306 Putting extra attributes like time_zone, locale or floating_tz_ok into extra => {} has been
307 B<DEPRECATED> because this gets you into trouble using L<DBIx::Class::Schema::Versioned>.
308 Instead put it directly into the columns definition like in the examples above. If you still
309 use the old way you'll see a warning - please fix your code then!
315 =item More information about the add_columns method, and column metadata,
316 can be found in the documentation for L<DBIx::Class::ResultSource>.
318 =item Further discussion of problems inherent to the Floating time zone:
319 L<Floating DateTimes|DateTime/Floating DateTimes>
320 and L<< $dt->set_time_zone|DateTime/"Set" Methods >>
324 =head1 FURTHER QUESTIONS?
326 Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
328 =head1 COPYRIGHT AND LICENSE
330 This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
331 by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
332 redistribute it and/or modify it under the same terms as the
333 L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.