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 5     5   317645 use 5.010;
  5         38  
4 5     5   23 use Carp;
  5         8  
  5         289  
5 5     5   27 use strict;
  5         9  
  5         79  
6 5     5   19 use warnings;
  5         7  
  5         153  
7 5     5   26 use base qw(DateTime);
  5         6  
  5         4723  
8 5     5   2382123 use DateTime::TimeZone;
  5         11  
  5         103  
9 5     5   1612 use PGObject;
  5         18101  
  5         36  
10              
11             =head1 NAME
12              
13             PGObject::Type::DateTime - DateTime Wrappers for PGObject
14              
15             =head1 VERSION
16              
17             Version 2.0.2
18              
19             =cut
20              
21             our $VERSION = 2.000002;
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 1869 my $self = shift @_;
69 5 50       15 croak "Can't pass reference to register \n".
70             "Hint: use the class instead of the object" if ref $self;
71 5         12 my %args = @_;
72 5         10 my $registry = $args{registry};
73 5   100     21 $registry ||= 'default';
74 5         8 my $types = $args{types};
75 5 100 66     24 $types = ['date', 'time', 'timestamp', 'timestamptz']
76             unless defined $types and @$types;
77 5         9 for my $type (@$types){
78 14 50       407 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         56 require PGObject::Type::Registry;
84 14         44 PGObject::Type::Registry->register_type(
85             registry => $registry, dbtype => $type, apptype => $self
86             );
87             }
88             }
89 5         176 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 31     31   6464 my $class = shift;
104 31         94 my (%args) = @_;
105 31         148 my $self = $class->SUPER::_new(@_);
106 31         4751 bless $self, $class;
107 31 100 66     133 $self->{_pgobject_is_date} = (defined $args{year} && $args{year} > 1) ? 1 : 0;
108 31 50       66 $self->{_pgobject_is_time} = (defined $args{hour}) ? 1 : 0;
109 31 100       63 $self->{_pgobject_is_tz} = (defined $args{time_zone}) ? 1 : 0;
110 31         104 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 99 my $class = shift;
122 1         12 my $self = $class->SUPER::today(@_);
123 1         2 $self->{_pgobject_is_time} = 0;
124 1         2 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 235 my $class = shift;
136 1         8 my $self = $class->SUPER::last_day_of_month(@_);
137 1         11 $self->{_pgobject_is_time} = 0;
138 1         6 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         7 my $self = $class->SUPER::from_day_of_year(@_);
151 1         2 $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 310 my $class = shift;
165 11         23 my %args = @_;
166 11         59 my $self = $class->SUPER::truncate(@_);
167             $self->{_pgobject_is_time} = 0
168 11 100       150 if ! grep { $args{to} eq $_} qw/ hour minute second /;
  33         70  
169 11         36 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 114 my ($class, $value) = @_;
180 7         13 my ($year, $month, $day, $hour, $min, $sec, $nanosec, $tz);
181 7 100       17 $value = '' if not defined $value;
182 7 100       43 $value =~ /(\d{4})-(\d{2})-(\d{2})/
183             and ($year, $month, $day) = ($1, $2, $3);
184 7 100       39 $value =~ /(\d+):(\d+):([0-9.]+)([+-]\d{1,4})?/
185             and ($hour, $min, $sec, $tz) = ($1, $2, $3, $4);
186 7   66     23 $tz ||= $default_tz; # defaults to UTC
187 7 100       25 $tz .= '00' if $tz =~ /([+-]\d{2}$)/;
188 7 100       103 ($sec, $nanosec) = split /\./, $sec if $sec;
189 7 100       33 $nanosec *= 1000 if $nanosec;
190 7   100     77 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       21 $self->is_time(0) if ! defined $hour;
201 7 100       28 $self->is_tz(0) if $tz == $default_tz;
202 7         33 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 5274 my ($self) = @_;
213 8 100 100     19 return undef unless ($self->is_date or $self->is_time);
214 7         12 my $dbst = '';
215 7         20 my $offset = $self->offset;
216 7         44 $offset = $offset / 60;
217 7         12 my $offset_min = $offset%60;
218 7         11 $offset = $offset / 60;
219 7 100       16 my $sign = ($offset > 0)? '+' : '-';
220 7         25 $offset = $sign . sprintf('%02d', abs($offset));
221              
222 7 50       20 if ($offset_min){
223 0         0 $offset = "$offset$offset_min";
224             }
225              
226 7 100       13 $dbst .= $self->ymd if $self->is_date;
227 7 100 100     81 $dbst .= ' ' if $self->is_date and $self->is_time;
228 7 100       15 $dbst .= $self->hms . '.' . $self->microsecond if $self->is_time;
229 7 100 66     90 $dbst .= $offset if $self->time_zone ne $default_tz and $self->is_time;
230 7         59 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 31 my ($self, $val) = @_;
242 22 50       44 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 5617 my ($self, $val) = @_;
258 30 100       54 if (defined $val){
259 2         3 $self->{_pgobject_is_time} = $val;
260             }
261 30         100 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 20 my ($self, $val) = @_;
273 10 100       22 if (defined $val){
274 4         6 $self->{_pgobject_is_tz} = $val;
275             }
276 10         26 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 The LedgerSMB Core Team
328              
329             This program is released under the following license: BSD
330              
331              
332             =cut
333              
334             1; # End of PGObject::Type::DateTime