1 package DBIx::Class::InflateColumn::DateTime;
5 use base qw/DBIx::Class/;
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 timezone and locale for that field, use:
35 __PACKAGE__->add_columns(
36 starts_when => { data_type => 'datetime', timezone => "America/Chicago", locale => "de_DE" }
39 If you want to inflate no matter what data_type your column is,
40 use inflate_datetime or inflate_date:
42 __PACKAGE__->add_columns(
43 starts_when => { data_type => 'varchar', inflate_datetime => 1 }
46 __PACKAGE__->add_columns(
47 starts_when => { data_type => 'varchar', inflate_date => 1 }
50 It's also possible to explicitly skip inflation:
52 __PACKAGE__->add_columns(
53 starts_when => { data_type => 'datetime', inflate_datetime => 0 }
56 NOTE: Don't rely on C<InflateColumn::DateTime> to parse date strings for you.
57 The column is set directly for any non-references and C<InflateColumn::DateTime>
58 is completely bypassed. Instead, use an input parser to create a DateTime
59 object. For instance, if your user input comes as a 'YYYY-MM-DD' string, you can
60 use C<DateTime::Format::ISO8601> thusly:
62 use DateTime::Format::ISO8601;
63 my $dt = DateTime::Format::ISO8601->parse_datetime('YYYY-MM-DD');
67 This module figures out the type of DateTime::Format::* class to
68 inflate/deflate with based on the type of DBIx::Class::Storage::DBI::*
69 that you are using. If you switch from one database to a different
70 one your code should continue to work without modification (though note
71 that this feature is new as of 0.07, so it may not be perfect yet - bug
72 reports to the list very much welcome).
74 If the data_type of a field is C<date>, C<datetime> or C<timestamp> (or
75 a derivative of these datatypes, e.g. C<timestamp with timezone>), this
76 module will automatically call the appropriate parse/format method for
77 deflation/inflation as defined in the storage class. For instance, for
78 a C<datetime> field the methods C<parse_datetime> and C<format_datetime>
79 would be called on deflation/inflation. If the storage class does not
80 provide a specialized inflator/deflator, C<[parse|format]_datetime> will
81 be used as a fallback. See L<DateTime::Format> for more information on
84 For more help with using components, see L<DBIx::Class::Manual::Component/USING>.
88 __PACKAGE__->load_components(qw/InflateColumn/);
90 =head2 register_column
92 Chains with the L<DBIx::Class::Row/register_column> method, and sets
93 up datetime columns appropriately. This would not normally be
94 directly called by end users.
96 In the case of an invalid date, L<DateTime> will throw an exception. To
97 bypass these exceptions and just have the inflation return undef, use
98 the C<datetime_undef_if_invalid> option in the column info:
102 data_type => "datetime",
103 default_value => '0000-00-00',
105 datetime_undef_if_invalid => 1
110 sub register_column {
111 my ($self, $column, $info, @rest) = @_;
113 $self->next::method($column, $info, @rest);
116 for (qw/datetime timestamp date/) {
117 my $key = "inflate_${_}";
118 if (exists $info->{$key}) {
120 # this bailout is intentional
121 return unless $info->{$key};
123 $requested_type = $_;
128 return if (!$requested_type and !$info->{data_type});
130 my $data_type = lc( $info->{data_type} || '' );
132 # _ic_dt_method will follow whatever the registration requests
133 # thus = instead of ||=
134 if ($data_type eq 'timestamp with time zone' || $data_type eq 'timestamptz') {
135 $info->{_ic_dt_method} = 'timestamp_with_timezone';
137 elsif ($data_type eq 'timestamp without time zone') {
138 $info->{_ic_dt_method} = 'timestamp_without_timezone';
140 elsif ($data_type eq 'smalldatetime') {
141 $info->{_ic_dt_method} = 'smalldatetime';
143 elsif ($data_type =~ /^ (?: date | datetime | timestamp ) $/x) {
144 $info->{_ic_dt_method} = $data_type;
146 elsif ($requested_type) {
147 $info->{_ic_dt_method} = $requested_type;
153 if ($info->{extra}) {
154 for my $slot (qw/timezone locale floating_tz_ok/) {
155 if ( defined $info->{extra}{$slot} ) {
156 carp "Putting $slot into extra => { $slot => '...' } has been deprecated, ".
157 "please put it directly into the '$column' column definition.";
158 $info->{$slot} = $info->{extra}{$slot} unless defined $info->{$slot};
163 # shallow copy to avoid unfounded(?) Devel::Cycle complaints
164 my $infcopy = {%$info};
166 $self->inflate_column(
170 my ($value, $obj) = @_;
172 # propagate for error reporting
173 $infcopy->{__dbic_colname} = $column;
175 my $dt = $obj->_inflate_to_datetime( $value, $infcopy );
178 ? $obj->_post_inflate_datetime( $dt, $infcopy )
183 my ($value, $obj) = @_;
185 $value = $obj->_pre_deflate_datetime( $value, $infcopy );
186 $obj->_deflate_from_datetime( $value, $infcopy );
192 sub _flate_or_fallback
194 my( $self, $value, $info, $method_fmt ) = @_;
196 my $parser = $self->_datetime_parser;
197 my $preferred_method = sprintf($method_fmt, $info->{ _ic_dt_method });
198 my $method = $parser->can($preferred_method) || sprintf($method_fmt, 'datetime');
201 $parser->$method($value);
204 $self->throw_exception ("Error while inflating '$value' for $info->{__dbic_colname} on ${self}: $_")
205 unless $info->{datetime_undef_if_invalid};
210 sub _inflate_to_datetime {
211 my( $self, $value, $info ) = @_;
212 return $self->_flate_or_fallback( $value, $info, 'parse_%s' );
215 sub _deflate_from_datetime {
216 my( $self, $value, $info ) = @_;
217 return $self->_flate_or_fallback( $value, $info, 'format_%s' );
220 sub _datetime_parser {
221 shift->result_source->storage->datetime_parser (@_);
224 sub _post_inflate_datetime {
225 my( $self, $dt, $info ) = @_;
227 $dt->set_time_zone($info->{timezone}) if defined $info->{timezone};
228 $dt->set_locale($info->{locale}) if defined $info->{locale};
233 sub _pre_deflate_datetime {
234 my( $self, $dt, $info ) = @_;
236 if (defined $info->{timezone}) {
237 carp "You're using a floating timezone, please see the documentation of"
238 . " DBIx::Class::InflateColumn::DateTime for an explanation"
239 if ref( $dt->time_zone ) eq 'DateTime::TimeZone::Floating'
240 and not $info->{floating_tz_ok}
241 and not $ENV{DBIC_FLOATING_TZ_OK};
243 $dt->set_time_zone($info->{timezone});
246 $dt->set_locale($info->{locale}) if defined $info->{locale};
256 If you have a datetime column with an associated C<timezone>, and subsequently
257 create/update this column with a DateTime object in the L<DateTime::TimeZone::Floating>
258 timezone, you will get a warning (as there is a very good chance this will not have the
259 result you expect). For example:
261 __PACKAGE__->add_columns(
262 starts_when => { data_type => 'datetime', timezone => "America/Chicago" }
265 my $event = $schema->resultset('EventTZ')->create({
266 starts_at => DateTime->new(year=>2007, month=>12, day=>31, ),
269 The warning can be avoided in several ways:
273 =item Fix your broken code
275 When calling C<set_time_zone> on a Floating DateTime object, the timezone is simply
276 set to the requested value, and B<no time conversion takes place>. It is always a good idea
277 to be supply explicit times to the database:
279 my $event = $schema->resultset('EventTZ')->create({
280 starts_at => DateTime->new(year=>2007, month=>12, day=>31, time_zone => "America/Chicago" ),
283 =item Suppress the check on per-column basis
285 __PACKAGE__->add_columns(
286 starts_when => { data_type => 'datetime', timezone => "America/Chicago", floating_tz_ok => 1 }
289 =item Suppress the check globally
291 Set the environment variable DBIC_FLOATING_TZ_OK to some true value.
295 Putting extra attributes like timezone, locale or floating_tz_ok into extra => {} has been
296 B<DEPRECATED> because this gets you into trouble using L<DBIx::Class::Schema::Versioned>.
297 Instead put it directly into the columns definition like in the examples above. If you still
298 use the old way you'll see a warning - please fix your code then!
304 =item More information about the add_columns method, and column metadata,
305 can be found in the documentation for L<DBIx::Class::ResultSource>.
307 =item Further discussion of problems inherent to the Floating timezone:
308 L<Floating DateTimes|DateTime/Floating DateTimes>
309 and L<< $dt->set_time_zone|DateTime/"Set" Methods >>
315 Matt S. Trout <mst@shadowcatsystems.co.uk>
319 Aran Deltac <bluefeet@cpan.org>
323 You may distribute this code under the same terms as Perl itself.