1 package DBIx::Class::InflateColumn::DateTime;
5 use base qw/DBIx::Class/;
6 use Carp::Clan qw/^DBIx::Class/;
11 DBIx::Class::InflateColumn::DateTime - Auto-create DateTime objects from date and datetime columns.
15 Load this component and then declare one or more
16 columns to be of the datetime, timestamp or date datatype.
19 use base 'DBIx::Class::Core';
21 __PACKAGE__->load_components(qw/InflateColumn::DateTime/);
22 __PACKAGE__->add_columns(
23 starts_when => { data_type => 'datetime' }
24 create_date => { data_type => 'date' }
27 Then you can treat the specified column as a L<DateTime> object.
29 print "This event starts the month of ".
30 $event->starts_when->month_name();
32 If you want to set a specific timezone and locale for that field, use:
34 __PACKAGE__->add_columns(
35 starts_when => { data_type => 'datetime', timezone => "America/Chicago", locale => "de_DE" }
38 If you want to inflate no matter what data_type your column is,
39 use inflate_datetime or inflate_date:
41 __PACKAGE__->add_columns(
42 starts_when => { data_type => 'varchar', inflate_datetime => 1 }
45 __PACKAGE__->add_columns(
46 starts_when => { data_type => 'varchar', inflate_date => 1 }
49 It's also possible to explicitly skip inflation:
51 __PACKAGE__->add_columns(
52 starts_when => { data_type => 'datetime', inflate_datetime => 0 }
55 NOTE: Don't rely on C<InflateColumn::DateTime> to parse date strings for you.
56 The column is set directly for any non-references and C<InflateColumn::DateTime>
57 is completely bypassed. Instead, use an input parser to create a DateTime
58 object. For instance, if your user input comes as a 'YYYY-MM-DD' string, you can
59 use C<DateTime::Format::ISO8601> thusly:
61 use DateTime::Format::ISO8601;
62 my $dt = DateTime::Format::ISO8601->parse_datetime('YYYY-MM-DD');
66 This module figures out the type of DateTime::Format::* class to
67 inflate/deflate with based on the type of DBIx::Class::Storage::DBI::*
68 that you are using. If you switch from one database to a different
69 one your code should continue to work without modification (though note
70 that this feature is new as of 0.07, so it may not be perfect yet - bug
71 reports to the list very much welcome).
73 If the data_type of a field is C<date>, C<datetime> or C<timestamp> (or
74 a derivative of these datatypes, e.g. C<timestamp with timezone>), this
75 module will automatically call the appropriate parse/format method for
76 deflation/inflation as defined in the storage class. For instance, for
77 a C<datetime> field the methods C<parse_datetime> and C<format_datetime>
78 would be called on deflation/inflation. If the storage class does not
79 provide a specialized inflator/deflator, C<[parse|format]_datetime> will
80 be used as a fallback. See L<DateTime::Format> for more information on
83 For more help with using components, see L<DBIx::Class::Manual::Component/USING>.
87 __PACKAGE__->load_components(qw/InflateColumn/);
89 =head2 register_column
91 Chains with the L<DBIx::Class::Row/register_column> method, and sets
92 up datetime columns appropriately. This would not normally be
93 directly called by end users.
95 In the case of an invalid date, L<DateTime> will throw an exception. To
96 bypass these exceptions and just have the inflation return undef, use
97 the C<datetime_undef_if_invalid> option in the column info:
101 data_type => "datetime",
102 default_value => '0000-00-00',
104 datetime_undef_if_invalid => 1
109 sub register_column {
110 my ($self, $column, $info, @rest) = @_;
111 $self->next::method($column, $info, @rest);
112 return unless defined($info->{data_type});
116 for (qw/date datetime timestamp/) {
117 my $key = "inflate_${_}";
119 next unless exists $info->{$key};
120 return unless $info->{$key};
127 $type = lc($info->{data_type});
128 if ($type eq "timestamp with time zone" || $type eq "timestamptz") {
130 $info->{_ic_dt_method} ||= "timestamp_with_timezone";
131 } elsif ($type eq "timestamp without time zone") {
133 $info->{_ic_dt_method} ||= "timestamp_without_timezone";
134 } elsif ($type eq "smalldatetime") {
136 $info->{_ic_dt_method} ||= "smalldatetime";
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 $info->{timezone} = $info->{extra}{timezone} unless defined $info->{timezone};
146 if ( defined $info->{extra}{locale} ) {
147 carp "Putting locale into extra => { locale => '...' } has been deprecated, ".
148 "please put it directly into the '$column' column definition.";
149 $info->{locale} = $info->{extra}{locale} unless defined $info->{locale};
152 my $undef_if_invalid = $info->{datetime_undef_if_invalid};
154 if ($type eq 'datetime' || $type eq 'date' || $type eq 'timestamp') {
155 # This shallow copy of %info avoids t/52_cycle.t treating
156 # the resulting deflator as a circular reference.
157 my %info = ( '_ic_dt_method' => $type , %{ $info } );
159 if (defined $info->{extra}{floating_tz_ok}) {
160 carp "Putting floating_tz_ok into extra => { floating_tz_ok => 1 } has been deprecated, ".
161 "please put it directly into the '$column' column definition.";
162 $info{floating_tz_ok} = $info->{extra}{floating_tz_ok};
165 $self->inflate_column(
169 my ($value, $obj) = @_;
172 try { $dt = $obj->_inflate_to_datetime( $value, \%info ) }
174 return undef if ($undef_if_invalid);
175 $self->throw_exception ("Error while inflating ${value} for ${column} on ${self}: $_");
178 return $obj->_post_inflate_datetime( $dt, \%info );
181 my ($value, $obj) = @_;
183 $value = $obj->_pre_deflate_datetime( $value, \%info );
184 $obj->_deflate_from_datetime( $value, \%info );
191 sub _flate_or_fallback
193 my( $self, $value, $info, $method_fmt ) = @_;
195 my $parser = $self->_datetime_parser;
196 my $preferred_method = sprintf($method_fmt, $info->{ _ic_dt_method });
197 my $method = $parser->can($preferred_method) ? $preferred_method : sprintf($method_fmt, 'datetime');
198 return $parser->$method($value);
201 sub _inflate_to_datetime {
202 my( $self, $value, $info ) = @_;
203 return $self->_flate_or_fallback( $value, $info, 'parse_%s' );
206 sub _deflate_from_datetime {
207 my( $self, $value, $info ) = @_;
208 return $self->_flate_or_fallback( $value, $info, 'format_%s' );
211 sub _datetime_parser {
212 shift->result_source->storage->datetime_parser (@_);
215 sub _post_inflate_datetime {
216 my( $self, $dt, $info ) = @_;
218 $dt->set_time_zone($info->{timezone}) if defined $info->{timezone};
219 $dt->set_locale($info->{locale}) if defined $info->{locale};
224 sub _pre_deflate_datetime {
225 my( $self, $dt, $info ) = @_;
227 if (defined $info->{timezone}) {
228 carp "You're using a floating timezone, please see the documentation of"
229 . " DBIx::Class::InflateColumn::DateTime for an explanation"
230 if ref( $dt->time_zone ) eq 'DateTime::TimeZone::Floating'
231 and not $info->{floating_tz_ok}
232 and not $ENV{DBIC_FLOATING_TZ_OK};
234 $dt->set_time_zone($info->{timezone});
237 $dt->set_locale($info->{locale}) if defined $info->{locale};
247 If you have a datetime column with an associated C<timezone>, and subsequently
248 create/update this column with a DateTime object in the L<DateTime::TimeZone::Floating>
249 timezone, you will get a warning (as there is a very good chance this will not have the
250 result you expect). For example:
252 __PACKAGE__->add_columns(
253 starts_when => { data_type => 'datetime', timezone => "America/Chicago" }
256 my $event = $schema->resultset('EventTZ')->create({
257 starts_at => DateTime->new(year=>2007, month=>12, day=>31, ),
260 The warning can be avoided in several ways:
264 =item Fix your broken code
266 When calling C<set_time_zone> on a Floating DateTime object, the timezone is simply
267 set to the requested value, and B<no time conversion takes place>. It is always a good idea
268 to be supply explicit times to the database:
270 my $event = $schema->resultset('EventTZ')->create({
271 starts_at => DateTime->new(year=>2007, month=>12, day=>31, time_zone => "America/Chicago" ),
274 =item Suppress the check on per-column basis
276 __PACKAGE__->add_columns(
277 starts_when => { data_type => 'datetime', timezone => "America/Chicago", floating_tz_ok => 1 }
280 =item Suppress the check globally
282 Set the environment variable DBIC_FLOATING_TZ_OK to some true value.
286 Putting extra attributes like timezone, locale or floating_tz_ok into extra => {} has been
287 B<DEPRECATED> because this gets you into trouble using L<DBIx::Class::Schema::Versioned>.
288 Instead put it directly into the columns definition like in the examples above. If you still
289 use the old way you'll see a warning - please fix your code then!
295 =item More information about the add_columns method, and column metadata,
296 can be found in the documentation for L<DBIx::Class::ResultSource>.
298 =item Further discussion of problems inherent to the Floating timezone:
299 L<Floating DateTimes|DateTime/Floating_DateTimes>
300 and L<< $dt->set_time_zone|DateTime/"Set" Methods >>
306 Matt S. Trout <mst@shadowcatsystems.co.uk>
310 Aran Deltac <bluefeet@cpan.org>
314 You may distribute this code under the same terms as Perl itself.