1 package DBIx::Class::InflateColumn::DateTime;
5 use base qw/DBIx::Class/;
6 use Carp::Clan qw/^DBIx::Class/;
10 DBIx::Class::InflateColumn::DateTime - Auto-create DateTime objects from date and datetime columns.
14 Load this component and then declare one or more
15 columns to be of the datetime, timestamp or date datatype.
18 use base 'DBIx::Class::Core';
20 __PACKAGE__->load_components(qw/InflateColumn::DateTime/);
21 __PACKAGE__->add_columns(
22 starts_when => { data_type => 'datetime' }
23 create_date => { data_type => 'date' }
26 Then you can treat the specified column as a L<DateTime> object.
28 print "This event starts the month of ".
29 $event->starts_when->month_name();
31 If you want to set a specific timezone and locale for that field, use:
33 __PACKAGE__->add_columns(
34 starts_when => { data_type => 'datetime', timezone => "America/Chicago", locale => "de_DE" }
37 If you want to inflate no matter what data_type your column is,
38 use inflate_datetime or inflate_date:
40 __PACKAGE__->add_columns(
41 starts_when => { data_type => 'varchar', inflate_datetime => 1 }
44 __PACKAGE__->add_columns(
45 starts_when => { data_type => 'varchar', inflate_date => 1 }
48 It's also possible to explicitly skip inflation:
50 __PACKAGE__->add_columns(
51 starts_when => { data_type => 'datetime', inflate_datetime => 0 }
54 NOTE: Don't rely on C<InflateColumn::DateTime> to parse date strings for you.
55 The column is set directly for any non-references and C<InflateColumn::DateTime>
56 is completely bypassed. Instead, use an input parser to create a DateTime
57 object. For instance, if your user input comes as a 'YYYY-MM-DD' string, you can
58 use C<DateTime::Format::ISO8601> thusly:
60 use DateTime::Format::ISO8601;
61 my $dt = DateTime::Format::ISO8601->parse_datetime('YYYY-MM-DD');
65 This module figures out the type of DateTime::Format::* class to
66 inflate/deflate with based on the type of DBIx::Class::Storage::DBI::*
67 that you are using. If you switch from one database to a different
68 one your code should continue to work without modification (though note
69 that this feature is new as of 0.07, so it may not be perfect yet - bug
70 reports to the list very much welcome).
72 If the data_type of a field is C<date>, C<datetime> or C<timestamp> (or
73 a derivative of these datatypes, e.g. C<timestamp with timezone>), this
74 module will automatically call the appropriate parse/format method for
75 deflation/inflation as defined in the storage class. For instance, for
76 a C<datetime> field the methods C<parse_datetime> and C<format_datetime>
77 would be called on deflation/inflation. If the storage class does not
78 provide a specialized inflator/deflator, C<[parse|format]_datetime> will
79 be used as a fallback. See L<DateTime::Format> for more information on
82 For more help with using components, see L<DBIx::Class::Manual::Component/USING>.
86 __PACKAGE__->load_components(qw/InflateColumn/);
88 =head2 register_column
90 Chains with the L<DBIx::Class::Row/register_column> method, and sets
91 up datetime columns appropriately. This would not normally be
92 directly called by end users.
94 In the case of an invalid date, L<DateTime> will throw an exception. To
95 bypass these exceptions and just have the inflation return undef, use
96 the C<datetime_undef_if_invalid> option in the column info:
100 data_type => "datetime",
101 default_value => '0000-00-00',
103 datetime_undef_if_invalid => 1
108 sub register_column {
109 my ($self, $column, $info, @rest) = @_;
110 $self->next::method($column, $info, @rest);
111 return unless defined($info->{data_type});
115 for (qw/date datetime timestamp/) {
116 my $key = "inflate_${_}";
118 next unless exists $info->{$key};
119 return unless $info->{$key};
126 $type = lc($info->{data_type});
127 if ($type eq "timestamp with time zone" || $type eq "timestamptz") {
129 $info->{_ic_dt_method} ||= "timestamp_with_timezone";
130 } elsif ($type eq "timestamp without time zone") {
132 $info->{_ic_dt_method} ||= "timestamp_without_timezone";
133 } elsif ($type eq "smalldatetime") {
135 $info->{_ic_dt_method} ||= "datetime";
140 if ( defined $info->{extra}{timezone} ) {
141 carp "Putting timezone into extra => { timezone => '...' } has been deprecated, ".
142 "please put it directly into the '$column' column definition.";
143 $timezone = $info->{extra}{timezone};
147 if ( defined $info->{extra}{locale} ) {
148 carp "Putting locale into extra => { locale => '...' } has been deprecated, ".
149 "please put it directly into the '$column' column definition.";
150 $locale = $info->{extra}{locale};
153 $locale = $info->{locale} if defined $info->{locale};
154 $timezone = $info->{timezone} if defined $info->{timezone};
156 my $undef_if_invalid = $info->{datetime_undef_if_invalid};
158 if ($type eq 'datetime' || $type eq 'date' || $type eq 'timestamp') {
159 # This shallow copy of %info avoids t/52_cycle.t treating
160 # the resulting deflator as a circular reference.
161 my %info = ( '_ic_dt_method' => $type , %{ $info } );
163 if (defined $info->{extra}{floating_tz_ok}) {
164 carp "Putting floating_tz_ok into extra => { floating_tz_ok => 1 } has been deprecated, ".
165 "please put it directly into the '$column' column definition.";
166 $info{floating_tz_ok} = $info->{extra}{floating_tz_ok};
169 $self->inflate_column(
173 my ($value, $obj) = @_;
175 my $dt = eval { $obj->_inflate_to_datetime( $value, \%info ) };
177 return undef if ($undef_if_invalid);
178 $self->throw_exception ("Error while inflating ${value} for ${column} on ${self}: $err");
181 return $obj->_post_inflate_datetime( $dt, \%info );
184 my ($value, $obj) = @_;
186 $value = $obj->_pre_deflate_datetime( $value, \%info );
187 $obj->_deflate_from_datetime( $value, \%info );
194 sub _flate_or_fallback
196 my( $self, $value, $info, $method_fmt ) = @_;
198 my $parser = $self->_datetime_parser;
199 my $preferred_method = sprintf($method_fmt, $info->{ _ic_dt_method });
200 my $method = $parser->can($preferred_method) ? $preferred_method : sprintf($method_fmt, 'datetime');
201 return $parser->$method($value);
204 sub _inflate_to_datetime {
205 my( $self, $value, $info ) = @_;
206 return $self->_flate_or_fallback( $value, $info, 'parse_%s' );
209 sub _deflate_from_datetime {
210 my( $self, $value, $info ) = @_;
211 return $self->_flate_or_fallback( $value, $info, 'format_%s' );
214 sub _datetime_parser {
215 shift->result_source->storage->datetime_parser (@_);
218 sub _post_inflate_datetime {
219 my( $self, $dt, $info ) = @_;
222 if (exists $info->{timezone}) {
223 $timezone = $info->{timezone};
225 elsif (exists $info->{extra} and exists $info->{extra}{timezone}) {
226 $timezone = $info->{extra}{timezone};
230 if (exists $info->{locale}) {
231 $locale = $info->{locale};
233 elsif (exists $info->{extra} and exists $info->{extra}{locale}) {
234 $locale = $info->{extra}{locale};
237 $dt->set_time_zone($timezone) if $timezone;
238 $dt->set_locale($locale) if $locale;
243 sub _pre_deflate_datetime {
244 my( $self, $dt, $info ) = @_;
247 if (exists $info->{timezone}) {
248 $timezone = $info->{timezone};
250 elsif (exists $info->{extra} and exists $info->{extra}{timezone}) {
251 $timezone = $info->{extra}{timezone};
255 if (exists $info->{locale}) {
256 $locale = $info->{locale};
258 elsif (exists $info->{extra} and exists $info->{extra}{locale}) {
259 $locale = $info->{extra}{locale};
263 carp "You're using a floating timezone, please see the documentation of"
264 . " DBIx::Class::InflateColumn::DateTime for an explanation"
265 if ref( $dt->time_zone ) eq 'DateTime::TimeZone::Floating'
266 and not $info->{floating_tz_ok}
267 and not $ENV{DBIC_FLOATING_TZ_OK};
269 $dt->set_time_zone($timezone);
272 $dt->set_locale($locale) if $locale;
282 If you have a datetime column with an associated C<timezone>, and subsequently
283 create/update this column with a DateTime object in the L<DateTime::TimeZone::Floating>
284 timezone, you will get a warning (as there is a very good chance this will not have the
285 result you expect). For example:
287 __PACKAGE__->add_columns(
288 starts_when => { data_type => 'datetime', timezone => "America/Chicago" }
291 my $event = $schema->resultset('EventTZ')->create({
292 starts_at => DateTime->new(year=>2007, month=>12, day=>31, ),
295 The warning can be avoided in several ways:
299 =item Fix your broken code
301 When calling C<set_time_zone> on a Floating DateTime object, the timezone is simply
302 set to the requested value, and B<no time conversion takes place>. It is always a good idea
303 to be supply explicit times to the database:
305 my $event = $schema->resultset('EventTZ')->create({
306 starts_at => DateTime->new(year=>2007, month=>12, day=>31, time_zone => "America/Chicago" ),
309 =item Suppress the check on per-column basis
311 __PACKAGE__->add_columns(
312 starts_when => { data_type => 'datetime', timezone => "America/Chicago", floating_tz_ok => 1 }
315 =item Suppress the check globally
317 Set the environment variable DBIC_FLOATING_TZ_OK to some true value.
321 Putting extra attributes like timezone, locale or floating_tz_ok into extra => {} has been
322 B<DEPRECATED> because this gets you into trouble using L<DBIx::Class::Schema::Versioned>.
323 Instead put it directly into the columns definition like in the examples above. If you still
324 use the old way you'll see a warning - please fix your code then!
330 =item More information about the add_columns method, and column metadata,
331 can be found in the documentation for L<DBIx::Class::ResultSource>.
333 =item Further discussion of problems inherent to the Floating timezone:
334 L<Floating DateTimes|DateTime/Floating_DateTimes>
335 and L<< $dt->set_time_zone|DateTime/"Set" Methods >>
341 Matt S. Trout <mst@shadowcatsystems.co.uk>
345 Aran Deltac <bluefeet@cpan.org>
349 You may distribute this code under the same terms as Perl itself.