File Coverage

blib/lib/PGObject/Type/DateTime.pm
Criterion Covered Total %
statement 97 100 97.0
branch 45 50 90.0
condition 31 36 86.1
subroutine 18 18 100.0
pod 10 10 100.0
total 201 214 93.9


line stmt bran cond sub pod time code
1             package PGObject::Type::DateTime;
2              
3 6     6   169868 use 5.010;
  6         21  
4 6     6   33 use Carp;
  6         11  
  6         321  
5 6     6   33 use strict;
  6         16  
  6         111  
6 6     6   26 use warnings;
  6         10  
  6         173  
7 6     6   29 use base qw(DateTime);
  6         9  
  6         3774  
8 6     6   2321732 use DateTime::TimeZone;
  6         13  
  6         111  
9 6     6   1630 use PGObject;
  6         17590  
  6         37  
10              
11             =head1 NAME
12              
13             PGObject::Type::DateTime - DateTime Wrappers for PGObject
14              
15             =head1 VERSION
16              
17             Version 2.0.1
18              
19             =cut
20              
21             our $VERSION = 2.000001;
22             our $default_tz = DateTime::TimeZone->new(name => 'UTC');
23              
24              
25             =head1 SYNOPSIS
26              
27             PGObject::Type::DateTime->register();
28              
29             Now all Datetime, Timestamp, and TimestampTZ types are returned
30             returned as datetime objects. Date and time modules may require subclasses
31             to serialize properly to the database.
32              
33             =head1 ONGOING WORK IN 2.X
34              
35             During the 2.x series we expect to work on better NULL support. Right now this
36             is all delegated to clild classes, but there are likely to be cases where we
37             add this to our library directly.
38              
39             =head1 DESCRIPTION
40              
41             This module provides a basic wrapper around DateTime to allow PGObject-framework
42             types to automatically tie date/time related objects, but we handle date and
43             timestamp formats in our from_db routines.
44              
45             This specific module only supports the ISO YMD datestyle. The MDY or DMY
46             datestyles may be usable in future versions but datestyles other than ISO raise
47             ambiguity issues, sufficient that they cannot always even be used in PostgreSQL as input.
48              
49             This module also provides basic default handling. Times are assigned a date of
50             '0001-01-01' and dates are assigned a time of midnight. Whether this is set is
51             persisted, along with whether timezones are set, and these are returned to a
52             valid ISO YMD format on export, if a date component was initially set.
53              
54             This means you can use this for general math without worrying about many of the
55             other nicities. Parsing ISO YMD dates and standard times (24 hr format) is
56             supported via the from_db interface, which also provides a useful way of handing
57             dates in.
58              
59             =head1 SUBROUTINES/METHODS
60              
61             =head2 register
62              
63             By default registers 'date', 'time', 'timestamp', and 'timestamptz'
64              
65             =cut
66              
67             sub register {
68 5     5 1 1530 my $self = shift @_;
69 5 50       17 croak "Can't pass reference to register \n".
70             "Hint: use the class instead of the object" if ref $self;
71 5         13 my %args = @_;
72 5         8 my $registry = $args{registry};
73 5   100     24 $registry ||= 'default';
74 5         10 my $types = $args{types};
75 5 100 66     22 $types = ['date', 'time', 'timestamp', 'timestamptz']
76             unless defined $types and @$types;
77 5         11 for my $type (@$types){
78 14 50       410 if ($PGObject::VERSION =~ /^1\./) { # 1.x
79 0         0 my $ret =
80             PGObject->register_type(registry => $registry, pg_type => $type,
81             perl_class => $self);
82             } else { # higher than 1.x
83 14         55 require PGObject::Type::Registry;
84 14         44 PGObject::Type::Registry->register_type(
85             registry => $registry, dbtype => $type, apptype => $self
86             );
87             }
88             }
89 5         181 return 1;
90             }
91              
92             =head2 _new
93              
94             Constructor for the PGDate object. Fully compliant with DateTime
95             C<_new> constructor which it uses internally to instantiate objects.
96              
97             We need to hook this constructor instead of the regular C<new> one,
98             because this one is referred to directly on numerous occasions.
99              
100             =cut
101              
102             sub _new {
103 30     30   5782 my $class = shift;
104 30         91 my (%args) = @_;
105 30         92 my $self = $class->SUPER::_new(@_);
106 30         4597 bless $self, $class;
107 30 100 66     144 $self->{_pgobject_is_date} = (defined $args{year} && $args{year} > 1) ? 1 : 0;
108 30 50       61 $self->{_pgobject_is_time} = (defined $args{hour}) ? 1 : 0;
109 30 100       53 $self->{_pgobject_is_tz} = (defined $args{time_zone}) ? 1 : 0;
110 30         98 return $self;
111             }
112              
113             =head2 today
114              
115             Wraps C<DateTime::today>, clearing the internal flag which
116             causes C<is_time()> to return a non-false value.
117              
118             =cut
119              
120             sub today {
121 1     1 1 78 my $class = shift;
122 1         13 my $self = $class->SUPER::today(@_);
123 1         2 $self->{_pgobject_is_time} = 0;
124 1         4 return $self;
125             }
126              
127             =head2 last_day_of_month
128              
129             Wraps C<DateTime::last_day_of_month>, clearing the internal flag which
130             causes C<is_time()> to return a non-false value.
131              
132             =cut
133              
134             sub last_day_of_month {
135 1     1 1 218 my $class = shift;
136 1         9 my $self = $class->SUPER::last_day_of_month(@_);
137 1         4 $self->{_pgobject_is_time} = 0;
138 1         5 return $self;
139             }
140              
141             =head2 from_day_of_year
142              
143             Wraps C<DateTime::from_day_of_year>, clearing the internal flag which
144             causes C<is_time()> to return a non-false value.
145              
146             =cut
147              
148             sub from_day_of_year {
149 1     1 1 2 my $class = shift;
150 1         8 my $self = $class->SUPER::from_day_of_year(@_);
151 1         3 $self->{_pgobject_is_time} = 0;
152 1         5 return $self;
153             }
154              
155             =head2 truncate( to => ... )
156              
157             Wraps C<DateTime::from_day_of_year>, clearing the internal flag which
158             causes C<is_time()> to return a non-false value, if the C<to> argument
159             is not one of C<second>, C<minute> or C<hour>.
160              
161             =cut
162              
163             sub truncate {
164 11     11 1 371 my $class = shift;
165 11         24 my %args = @_;
166 11         36 my $self = $class->SUPER::truncate(@_);
167             $self->{_pgobject_is_time} = 0
168 11 100       140 if ! grep { $args{to} eq $_} qw/ hour minute second /;
  33         74  
169 11         38 return $self;
170             }
171              
172             =head2 from_db
173              
174             Parses a date from YYYY-MM-DD format and generates the new object based on it.
175              
176             =cut
177              
178             sub from_db {
179 7     7 1 81 my ($class, $value) = @_;
180 7         11 my ($year, $month, $day, $hour, $min, $sec, $nanosec, $tz);
181 7 100       18 $value = '' if not defined $value;
182 7 100       65 $value =~ /(\d{4})-(\d{2})-(\d{2})/
183             and ($year, $month, $day) = ($1, $2, $3);
184 7 100       38 $value =~ /(\d+):(\d+):([0-9.]+)([+-]\d{1,4})?/
185             and ($hour, $min, $sec, $tz) = ($1, $2, $3, $4);
186 7   66     24 $tz ||= $default_tz; # defaults to UTC
187 7 100       26 $tz .= '00' if $tz =~ /([+-]\d{2}$)/;
188 7 100       21 ($sec, $nanosec) = split /\./, $sec if $sec;
189 7 100       20 $nanosec *= 1000 if $nanosec;
190 7   100     75 my $self = "$class"->new(
      100        
      100        
      100        
      100        
      100        
      100        
      50        
191             year => $year || 1,
192             month => $month || 1,
193             day => $day || 1,
194             hour => $hour || 0,
195             minute => $min || 0,
196             second => $sec || 0,
197             nanosecond => $nanosec || 0,
198             time_zone => $tz || 0,
199             );
200 7 100       17 $self->is_time(0) if ! defined $hour;
201 7 100       26 $self->is_tz(0) if $tz == $default_tz;
202 7         37 return $self;
203             }
204              
205             =head2 to_db
206              
207             Returns the date in YYYY-MM-DD format.
208              
209             =cut
210              
211             sub to_db {
212 8     8 1 4526 my ($self) = @_;
213 8 100 100     19 return undef unless ($self->is_date or $self->is_time);
214 7         12 my $dbst = '';
215 7         21 my $offset = $self->offset;
216 7         44 $offset = $offset / 60;
217 7         13 my $offset_min = $offset%60;
218 7         9 $offset = $offset / 60;
219 7 100       17 my $sign = ($offset > 0)? '+' : '-';
220 7         26 $offset = $sign . sprintf('%02d', abs($offset));
221              
222 7 50       25 if ($offset_min){
223 0         0 $offset = "$offset$offset_min";
224             }
225              
226 7 100       12 $dbst .= $self->ymd if $self->is_date;
227 7 100 100     81 $dbst .= ' ' if $self->is_date and $self->is_time;
228 7 100       14 $dbst .= $self->hms . '.' . $self->microsecond if $self->is_time;
229 7 100 66     89 $dbst .= $offset if $self->time_zone ne $default_tz and $self->is_time;
230 7         66 return $dbst;
231             }
232              
233             =head2 is_date($to_set)
234              
235             If $to_set is set, sets this. In both cases, returns whether the object is now
236             a date.
237              
238             =cut
239              
240             sub is_date {
241 22     22 1 36 my ($self, $val) = @_;
242 22 50       40 if (defined $val){
243 0         0 $self->{_pgobject_is_date} = $val;
244             }
245 22         82 return $self->{_pgobject_is_date};
246             }
247              
248             =head2 is_time($to_set)
249              
250             If $to_set is set, sets this. In both cases, returns whether the object is now
251             a time.
252              
253             =cut
254              
255              
256             sub is_time {
257 30     30 1 5414 my ($self, $val) = @_;
258 30 100       56 if (defined $val){
259 2         3 $self->{_pgobject_is_time} = $val;
260             }
261 30         102 return $self->{_pgobject_is_time};
262             }
263              
264             =head2 is_tz($to_set)
265              
266             If $to_set is set, sets this. In both cases, returns whether the object is now
267             a date.
268              
269             =cut
270              
271             sub is_tz {
272 10     10 1 19 my ($self, $val) = @_;
273 10 100       19 if (defined $val){
274 4         7 $self->{_pgobject_is_tz} = $val;
275             }
276 10         27 return $self->{_pgobject_is_tz};
277             }
278              
279             =head1 AUTHOR
280              
281             Chris Travers, C<< <chris.travers at gmail.com> >>
282              
283             =head1 BUGS
284              
285             Please report any bugs or feature requests to C<bug-pgobject-type-datetime at rt.cpan.org>, or through
286             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=PGObject-Type-DateTime>. I will be notified, and then you'll
287             automatically be notified of progress on your bug as I make changes.
288              
289              
290              
291              
292             =head1 SUPPORT
293              
294             You can find documentation for this module with the perldoc command.
295              
296             perldoc PGObject::Type::DateTime
297              
298              
299             You can also look for information at:
300              
301             =over 4
302              
303             =item * RT: CPAN's request tracker (report bugs here)
304              
305             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=PGObject-Type-DateTime>
306              
307             =item * AnnoCPAN: Annotated CPAN documentation
308              
309             L<http://annocpan.org/dist/PGObject-Type-DateTime>
310              
311             =item * CPAN Ratings
312              
313             L<http://cpanratings.perl.org/d/PGObject-Type-DateTime>
314              
315             =item * Search CPAN
316              
317             L<http://search.cpan.org/dist/PGObject-Type-DateTime/>
318              
319             =back
320              
321              
322             =head1 ACKNOWLEDGEMENTS
323              
324              
325             =head1 LICENSE AND COPYRIGHT
326              
327             Copyright 2013-2017 Chris Travers.
328              
329             This program is released under the following license: BSD
330              
331              
332             =cut
333              
334             1; # End of PGObject::Type::DateTime