File Coverage

blib/lib/DateTime/Format/Pg.pm
Criterion Covered Total %
statement 139 161 86.3
branch 62 84 73.8
condition 21 38 55.2
subroutine 26 31 83.8
pod 13 13 100.0
total 261 327 79.8


line stmt bran cond sub pod time code
1             package DateTime::Format::Pg;
2              
3 19     19   930679 use strict;
  19         26  
  19         463  
4 19     19   57 use vars qw ($VERSION);
  19         21  
  19         768  
5              
6 19     19   60 use Carp;
  19         23  
  19         1013  
7 19     19   7823 use DateTime 0.13;
  19         814371  
  19         451  
8 19     19   84 use DateTime::Duration;
  19         20  
  19         355  
9 19     19   8934 use DateTime::Format::Builder 0.72;
  19         387555  
  19         118  
10 19     19   615 use DateTime::TimeZone 0.06;
  19         216  
  19         314  
11 19     19   63 use DateTime::TimeZone::UTC;
  19         20  
  19         335  
12 19     19   58 use DateTime::TimeZone::Floating;
  19         23  
  19         44125  
13              
14             $VERSION = '0.16012';
15             $VERSION = eval $VERSION;
16              
17             our @ISA = ('DateTime::Format::Builder');
18              
19             =head1 NAME
20              
21             DateTime::Format::Pg - Parse and format PostgreSQL dates and times
22              
23             =head1 SYNOPSIS
24              
25             use DateTime::Format::Pg;
26              
27             my $dt = DateTime::Format::Pg->parse_datetime( '2003-01-16 23:12:01' );
28              
29             # 2003-01-16 23:12:01
30             DateTime::Format::Pg->format_datetime($dt);
31              
32             =head1 DESCRIPTION
33              
34             This module understands the formats used by PostgreSQL for its DATE, TIME,
35             TIMESTAMP, and INTERVAL data types. It can be used to parse these formats in
36             order to create C or C objects, and it can take a
37             C or C object and produce a string representing
38             it in a format accepted by PostgreSQL.
39              
40             =head1 CONSTRUCTORS
41              
42             The following methods can be used to create C objects.
43              
44             =over 4
45              
46             =item * new( name => value, ... )
47              
48             Creates a new C instance. This is generally not
49             required for simple operations. If you wish to use a different parsing
50             style from the default then it is more comfortable to create an object.
51              
52             my $parser = DateTime::Format::Pg->new()
53             my $copy = $parser->new( 'european' => 1 );
54              
55             This method accepts the following options:
56              
57             =over 8
58              
59             =item * european
60              
61             If european is set to non-zero, dates are assumed to be in european
62             dd/mm/yyyy format. The default is to assume US mm/dd/yyyy format
63             (because this is the default for PostgreSQL).
64              
65             This option only has an effect if PostgreSQL is set to output dates in
66             the 'PostgreSQL' (DATE only) and 'SQL' (DATE and TIMESTAMP) styles.
67              
68             Note that you don't have to set this option if the PostgreSQL server has
69             been set to use the 'ISO' format, which is the default.
70              
71             =item * server_tz
72              
73             This option can be set to a C object or a string
74             that contains a time zone name.
75              
76             This value must be set to the same value as the PostgreSQL server's time
77             zone in order to parse TIMESTAMP WITH TIMEZONE values in the
78             'PostgreSQL', 'SQL', and 'German' formats correctly.
79              
80             Note that you don't have to set this option if the PostgreSQL server has
81             been set to use the 'ISO' format, which is the default.
82              
83             =back
84              
85             =cut
86              
87             sub _add_param
88             {
89 2     2   4 my ($to,%param) = @_;
90 2         6 foreach(keys %param)
91             {
92 2 50       4 if($_ eq 'european') {
    0          
93 2         11 $$to{'_european'} = $param{$_};
94             } elsif($_ eq 'server_tz') {
95 0         0 $$to{'_server_tz'} = $param{$_};
96             } else {
97 0         0 croak("Unknown option $_." );
98             }
99             }
100             }
101              
102             sub european {
103 14     14 1 18 my ($self,%param) = @_;
104 14 100       41 return $param{'european'} if exists $param{'european'};
105 2 50       8 return $self->{'_european'} if ref $self;
106             }
107              
108             sub server_tz {
109 0     0 1 0 my ($self,%param) = @_;
110 0 0       0 return $param{''} if (ref($param{'server_tz'})) =~ /TimeZone/;
111 0 0       0 return DateTime::TimeZone->new('name' => $param{''}) if exists $param{'server_tz'};
112 0   0     0 return ((ref $self) && $self->{'_server_tz'});
113             }
114              
115             sub new
116             {
117 2     2 1 24 my $class = shift;
118 2   33     10 my $self = bless {}, ref($class)||$class;
119 2 50       4 if (ref $class)
120             {
121 0         0 $self->{'_european'} = ( scalar $class->{'_european'} );
122             }
123 2         18 _add_param($self,@_);
124 2         3 return $self;
125             }
126              
127             =item * clone()
128              
129             This method is provided for those who prefer to explicitly clone via a
130             method called C.
131              
132             my $clone = $original->clone();
133              
134             If called as a class method it will die.
135              
136             =back
137              
138             =cut
139              
140             sub clone
141             {
142 0     0 1 0 my $self = shift;
143 0 0       0 croak('Calling object method as class method!') unless ref $self;
144 0         0 return $self->new();
145             }
146              
147             sub _create_infinity
148             {
149 6     6   1559 my $self = shift;
150 6         10 my %p = @_;
151              
152 6 100       10 if ($p{sign}) {
153 3         11 return DateTime::Infinite::Past->new;
154             } else {
155 3         24 return DateTime::Infinite::Future->new;
156             }
157             }
158              
159             # infinite datetimes
160             my $pg_infinity =
161             {
162             regex => qr/^(-)?infinity$/,
163             params => [ qw(sign) ],
164             constructor => \&_create_infinity,
165             };
166              
167             # Dates (without time zone)
168             #
169             # see EncodeDateOnly() in
170             # pgsql-server/src/backend/utils/adt/datetime.c
171             #
172             # 2003-04-18 (USE_ISO_DATES)
173             #
174             my $pg_dateonly_iso =
175             {
176             regex => qr/^(\d{4,})-(\d{2,})-(\d{2,})( BC)?$/,
177             params => [ qw( year month day era ) ],
178             postprocess => \&_fix_era,
179             };
180              
181             # 18/04/2003 (USE_SQL_DATES, EuroDates)
182             # 18-04-2003 (USE_POSTGRES_DATES, EuroDates)
183             # 04/18/2003 (USE_SQL_DATES, !EuroDates)
184             # 04-18-2003 (USE_POSTGRES_DATES, !EuroDates)
185             #
186             my $pg_dateonly_sql =
187             {
188             regex => qr/^(\d{2,})[\/-](\d{2,})[\/-](\d{4,})( BC)?$/,
189             params => [ qw( month day year era) ],
190             postprocess => [ \&_fix_era, \&_fix_eu ],
191             };
192              
193             # 18.04.2003 (USE_GERMAN_DATES)
194             #
195             my $pg_dateonly_german =
196             {
197             regex => qr/^(\d{2,})\.(\d{2,})\.(\d{4,})( BC)?$/,
198             params => [ qw( day month year era ) ],
199             postprocess => \&_fix_era
200             };
201              
202             # Times (with/without time zone)
203             #
204             # see EncodeTimeOnly() in
205             # pgsql-server/src/backend/utils/adt/datetime.c
206             #
207             # 17:20:24.373942+02
208             # (NB: always uses numerical tz)
209             #
210             my $pg_timeonly =
211             {
212             regex => qr/^T?(\d{2,}):(\d{2,}):(\d{2,})(\.\d+)? *([-\+][\d:]+)?$/,
213             params => [ qw( hour minute second nanosecond time_zone) ],
214             extra => { year => '1970' },
215             postprocess => [ \&_fix_timezone, \&_fix_nanosecond ],
216             };
217              
218             # Timestamps (with/without time zone)
219             #
220             # see EncodeDateTime() in
221             # pgsql-server/src/backend/utils/adt/datetime.c
222             #
223             # 2003-04-18 17:20:24.373942+02 (USE_ISO_DATES)
224             # (NB: always uses numerical tz)
225             #
226             my $pg_datetime_iso =
227             {
228             regex => qr/^(\d{4,})-(\d{2,})-(\d{2,})[ T](\d{2,}):(\d{2,}):(\d{2,})(\.\d+)? *([-\+][\d:]+)?( BC)?$/,
229             params => [ qw( year month day hour minute second nanosecond time_zone era) ],
230             postprocess => [ \&_fix_era, \&_fix_timezone, \&_fix_nanosecond ],
231             };
232              
233             # Fri 18 Apr 17:20:24.373942 2003 CEST (USE_POSTGRES_DATES, EuroDates)
234             #
235             my $pg_datetime_pg_eu =
236             {
237             regex => qr/^\S{3,} (\d{2,}) (\S{3,}) (\d{2,}):(\d{2,}):(\d{2,})(\.\d+)? (\d{4,}) *((?:[-\+][\d:]+)|(?:\S+))?( BC)?$/,
238             params => [ qw( day month hour minute second nanosecond year time_zone era ) ],
239             postprocess => [ \&_fix_era, \&_fix_timezone, \&_fix_nanosecond ],
240             };
241              
242             # Fri Apr 18 17:20:24.373942 2003 CEST (USE_POSTGRES_DATES, !EuroDates)
243             #
244             my $pg_datetime_pg_us =
245             {
246             regex => qr/^\S{3,} (\S{3,}) (\s{2,}) (\d{2,}):(\d{2,}):(\d{2,})(\.\d+)? (\d{4,}) *((?:[-\+][\d:]+)|(?:\S+))?( BC)?$/,
247             params => [ qw( month day hour minute second nanosecond year time_zone era ) ],
248             postprocess => [ \&_fix_era, \&_fix_month_names, \&_fix_timezone, \&_fix_nanosecond ],
249             };
250              
251             # 18/04/2003 17:20:24.373942 CEST (USE_SQL_DATES, EuroDates)
252             # 04/18/2003 17:20:24.373942 CEST (USE_SQL_DATES, !EuroDates)
253             #
254             my $pg_datetime_sql =
255             {
256             regex => qr/^(\d{2,})\/(\d{2,})\/(\d{4,}) (\d{2,}):(\d{2,}):(\d{2,})(\.\d+)? *((?:[-\+][\d:]+)|(?:\S+))?( BC)?$/,
257             params => [ qw( month day year hour minute second nanosecond time_zone era ) ],
258             postprocess => [ \&_fix_era, \&_fix_eu, \&_fix_timezone, \&_fix_nanosecond ],
259             };
260              
261             # 18.04.2003 17:20:24.373942 CEST (USE_GERMAN_DATES)
262             #
263             my $pg_datetime_german =
264             {
265             regex => qr/^(\d{2,})\.(\d{2,})\.(\d{4,}) (\d{2,}):(\d{2,}):(\d{2,})(\.\d+)? *((?:[-\+][\d:]+)|(?:\S+))?( BC)?$/,
266             params => [ qw( day month year hour minute second nanosecond time_zone era ) ],
267             postprocess => [ \&_fix_era, \&_fix_timezone, \&_fix_nanosecond ],
268             };
269              
270             # Helper functions
271             #
272             # Fix BC dates (1 BC => year 0, 2 BC => year -1)
273             #
274             sub _fix_era {
275 36     36   15811 my %args = @_;
276 36   100     152 my $era = (delete $args{'parsed'}->{'era'}) || '';
277 36 100       85 if ($era =~ m/BC/) {
278 7         29 $args{'parsed'}->{'year'} = 1-$args{'parsed'}->{'year'}
279             }
280 36         164 return 1;
281             }
282              
283             # Fix European dates (swap month and day)
284             #
285             sub _fix_eu {
286 14     14   69 my %args = @_;
287 14 100       15 if($args{'self'}->european(@{$args{'args'}}) ) {
  14         32  
288 7         8 my $save = $args{'parsed'}->{'month'};
289 7         10 $args{'parsed'}->{'month'} = $args{'parsed'}->{'day'};
290 7         8 $args{'parsed'}->{'day'} = $save;
291             }
292 14         22 return 1;
293             }
294              
295             # Fix month names (name => numeric)
296             #
297             my %months = (
298             'jan' => 1, 'feb' => 2, 'mar' => 3, 'apr' => 4,
299             'may' => 5, 'jun' => 6, 'jul' => 7, 'aug' => 8,
300             'sep' => 9, 'oct' =>10, 'nov' =>11, 'dec' =>12, );
301              
302             sub _fix_month_names {
303 0     0   0 my %args = @_;
304 0         0 $args{'parsed'}->{'month'} = $months{lc( $args{'parsed'}->{'month'} )};
305 0 0       0 return $args{'parsed'}->{'month'} ? 1 : undef;
306             }
307              
308             # Fix time zones
309             #
310             sub _fix_timezone {
311 19     19   1938 my %args = @_;
312 19 100       48 my %param = $args{'args'} ? (@{$args{'args'}}) : ();
  2         33  
313            
314 19 100       111 if($param{'_force_tz'}) {
    100          
    50          
315 2         6 $args{'parsed'}->{'time_zone'} = $param{'_force_tz'};
316             }
317              
318             elsif(!defined($args{'parsed'}->{'time_zone'})) {
319             # For very early and late dates, PostgreSQL always returns times in
320             # UTC and does not tell us that it did so.
321             #
322 4 100 66     45 if ( $args{'parsed'}->{'year'} < 1901
      66        
      33        
      66        
      66        
      66        
      66        
323             || ( $args{'parsed'}->{'year'} == 1901 && ($args{'parsed'}->{'month'} < 12 || $args{'parsed'}->{'day'} < 14) )
324             || $args{'parsed'}->{'year'} > 2038
325             || ( $args{'parsed'}->{'year'} == 2038 && ($args{'parsed'}->{'month'} > 01 || $args{'parsed'}->{'day'} > 18) )
326             ) {
327 2         11 $args{'parsed'}->{'time_zone'} = DateTime::TimeZone::UTC->new();
328             }
329              
330             # DT->new() does not like undef time_zone params, which are generated
331             # by the regexps
332             #
333             else {
334 2         3 delete $args{'parsed'}->{'time_zone'};
335             }
336             }
337              
338             # Numerical time zone
339             #
340            
341             elsif($args{'parsed'}->{'time_zone'} =~ m/^([-\+])(\d+)(?::(\d+))?(?::(\d+))?$/) {
342 13         13 my $tz;
343 13 100       44 if (length($2) == 2) {
344             # regular hour notation
345 9   100     79 my ($min, $sec) = ($3 || '00', $4 || '00');
      100        
346 9         61 $tz = sprintf "%s%02d:%02d:%02d", $1, $2, $min, $sec;
347             } else {
348 4         8 $tz = "$1$2";
349             }
350 13         25 $args{'parsed'}->{'time_zone'} = $tz;
351             }
352            
353             # Non-numerical time zone returned, which can be ambiguous :(
354             #
355             else
356             {
357             # XXX This barfs because 'self' may not necessarily be initialized
358             # Need to fix it
359 0 0       0 my $stz = $args{'self'}->_server_tz($args{'args'} ? @{$args{'args'}} : ());
  0         0  
360 0   0     0 $args{'parsed'}->{'time_zone'} = $stz || 'floating';
361             }
362              
363 19         68 return 1;
364             }
365              
366             # Fix fractional seconds
367             #
368             sub _fix_nanosecond {
369 19     19   102 my %args = @_;
370 19 100       42 if(defined $args{'parsed'}->{'nanosecond'}) {
371 7         38 $args{'parsed'}->{'nanosecond'} *= 1.0E9;
372             } else {
373 12         14 delete $args{'parsed'}->{'nanosecond'}
374             };
375 19         35 return 1;
376             }
377              
378             # Parser generation
379             #
380             DateTime::Format::Builder->create_class
381             (
382             parsers =>
383             {
384             parse_date => [ $pg_dateonly_iso, $pg_dateonly_sql,
385             $pg_dateonly_german, $pg_infinity ],
386             parse_timetz => [ $pg_timeonly, ],
387             parse_timestamptz => [ $pg_datetime_iso, $pg_datetime_pg_eu,
388             $pg_datetime_pg_us, $pg_datetime_sql,
389             $pg_datetime_german, $pg_infinity ],
390             parse_datetime => [ $pg_datetime_iso, $pg_datetime_pg_eu,
391             $pg_datetime_pg_us, $pg_datetime_sql,
392             $pg_datetime_german,
393             $pg_dateonly_iso, $pg_dateonly_german,
394             $pg_dateonly_sql, $pg_timeonly, $pg_infinity],
395             }
396             );
397              
398             =head1 METHODS
399              
400             This class provides the following methods. The parse_datetime, parse_duration,
401             format_datetime, and format_duration methods are general-purpose methods
402             provided for compatibility with other C modules.
403              
404             The other methods are specific to the corresponding PostgreSQL date/time data
405             types. The names of these methods are derived from the name of the PostgreSQL
406             data type. (Note: Prior to PostgreSQL 7.3, the TIMESTAMP type was equivalent
407             to the TIMESTAMP WITH TIME ZONE type. This data type corresponds to the
408             format/parse_timestamp_with_time_zone method but not to the
409             format/parse_timestamp method.)
410              
411             =head2 PARSING METHODS
412              
413             This class provides the following parsing methods.
414              
415             As a general rule, the parsing methods accept input in any format that the
416             PostgreSQL server can produce. However, if PostgreSQL's DateStyle is set to
417             'SQL' or 'PostgreSQL', dates can only be parsed correctly if the 'european'
418             option is set correctly (i.e. same as the PostgreSQL server). The same is true
419             for time zones and the 'australian_timezones' option in all modes but 'ISO'.
420              
421             The default DateStyle, 'ISO', will always produce unambiguous results
422             and is also parsed most efficiently by this parser class. I strongly
423             recommend using this setting unless you have a good reason not to.
424              
425             =over 4
426              
427             =item * parse_datetime($string,...)
428              
429             Given a string containing a date and/or time representation, this method
430             will return a new C object.
431              
432             If the input string does not contain a date, it is set to 1970-01-01.
433             If the input string does not contain a time, it is set to 00:00:00.
434             If the input string does not contain a time zone, it is set to the
435             floating time zone.
436              
437             If given an improperly formatted string, this method may die.
438              
439             =cut
440              
441             # sub parse_datetime {
442             # *** created automatically ***
443             # }
444              
445             =item * parse_timestamptz($string,...)
446              
447             =item * parse_timestamp_with_time_zone($string,...)
448              
449             Given a string containing a timestamp (date and time) representation,
450             this method will return a new C object. This method is
451             suitable for the TIMESTAMPTZ (or TIMESTAMP WITH TIME ZONE) type.
452              
453             If the input string does not contain a time zone, it is set to the
454             floating time zone.
455              
456             Please note that PostgreSQL does not actually store a time zone along
457             with the TIMESTAMP WITH TIME ZONE (or TIMESTAMPTZ) type but will just
458             return a time stamp converted for the server's local time zone.
459              
460             If given an improperly formatted string, this method may die.
461              
462             =cut
463              
464             # sub parse_timestamptz {
465             # *** created automatically ***
466             # }
467              
468             *parse_timestamp_with_time_zone = \&parse_timestamptz;
469              
470             =item * parse_timestamp($string,...)
471              
472             =item * parse_timestamp_without_time_zone($string,...)
473              
474             Similar to the functions above, but always returns a C object
475             with a floating time zone. This method is suitable for the TIMESTAMP (or
476             TIMESTAMP WITHOUT TIME ZONE) type.
477              
478             If the server does return a time zone, it is ignored.
479              
480             If given an improperly formatted string, this method may die.
481              
482             =cut
483              
484             sub parse_timestamp {
485 2     2 1 401 parse_timestamptz(@_,'_force_tz' => DateTime::TimeZone::Floating->new());
486             }
487              
488             *parse_timestamp_without_time_zone = \&parse_timestamp;
489              
490             =item * parse_timetz($string,...)
491              
492             =item * parse_time_with_time_zone($string,...)
493              
494             Given a string containing a time representation, this method will return
495             a new C object. The date is set to 1970-01-01. This method is
496             suitable for the TIMETZ (or TIME WITH TIME ZONE) type.
497              
498             If the input string does not contain a time zone, it is set to the
499             floating time zone.
500              
501             Please note that PostgreSQL stores a numerical offset with its TIME WITH
502             TIME ZONE (or TIMETZ) type. It does not store a time zone name (such as
503             'Europe/Rome').
504              
505             If given an improperly formatted string, this method may die.
506              
507             =cut
508              
509             # sub parse_timetz {
510             # *** created automatically ***
511             # }
512              
513             *parse_time_with_time_zone = \&parse_timetz;
514              
515             =item * parse_time($string,...)
516              
517             =item * parse_time_without_time_zone($string,...)
518              
519             Similar to the functions above, but always returns an C object
520             with a floating time zone. If the server returns a time zone, it is
521             ignored. This method is suitable for use with the TIME (or TIME WITHOUT
522             TIME ZONE) type.
523              
524             This ensures that the resulting C object will always have the
525             time zone expected by your application.
526              
527             If given an improperly formatted string, this method may die.
528              
529             =cut
530              
531             sub parse_time {
532 2     2 1 28 parse_timetz(@_,'_force_tz' => 'floating');
533             }
534              
535             *parse_time_without_time_zone = \&parse_time;
536              
537             =item * parse_date($string,...)
538              
539             Given a string containing a date representation, this method will return
540             a new C object. The time is set to 00:00:00 (floating time
541             zone). This method is suitable for the DATE type.
542              
543             If given an improperly formatted string, this method may die.
544              
545             =cut
546              
547             # sub parse_date {
548             # *** generated automatically ***
549             # }
550              
551             =item * parse_duration($string)
552              
553             =item * parse_interval($string)
554              
555             Given a string containing a duration (SQL type INTERVAL) representation,
556             this method will return a new C object.
557              
558             If given an improperly formatted string, this method may die.
559              
560             =cut
561              
562             sub parse_duration {
563 87     87 1 76946 my ($self, $string_to_parse) = @_;
564              
565             # NB: We can't just pass our values to new() because it treats all
566             # arguments as negative if we have a single negative component.
567             # PostgreSQL might return mixed signs, e.g. '1 mon -1day'.
568 87         223 my $du = DateTime::Duration->new;
569              
570 87         5350 my %units = ( map(($_, ["seconds", 1]), qw(s second seconds sec secs)),
571             map(($_, ["minutes", 1]), qw(m minute minutes min mins)),
572             map(($_, ["hours", 1]), qw(h hr hour hours)),
573             map(($_, ["days", 1]), qw(d day days)),
574             map(($_, ["weeks", 1]), qw(w week weeks)),
575             map(($_, ["months", 1]), qw(M mon mons month months)),
576             map(($_, ["years", 1]), qw(y yr yrs year years)),
577             map(($_, ["years", 10]), qw(decade decades dec decs)),
578             map(($_, ["years", 100]), qw(c cent century centuries)),
579             map(($_, ["years", 1000]), qw(millennium millennia millenniums mil mils)) );
580              
581 87         339 (my $string = $string_to_parse) =~ s/^@\s*//;
582 87         105 $string =~ s/\+(\d+)/$1/g;
583              
584 87         65 my $subtract = 0;
585 87 100       179 if ( $string =~ s/ago// ) {
586 8         7 $subtract = 1;
587             }
588              
589 87         57 my $sign = 0;
590 87         68 my %done;
591              
592             # $timespec =~ s/\b(\d+):(\d\d):((\d\d)|(\d\d.\d+))\b/$1h $2m $3s/g;
593 87         219 $string =~ s/\b(\d+):(\d\d):(\d\d)\b/$1h $2m $3s/g;
594 87         97 $string =~ s/\b(\d+):(\d\d)\b/$1h $2m/g;
595 87         97 $string =~ s/(-\d+h)\s+(\d+m)\s+(\d+s)\s*/$1 -$2 -$3 /;
596 87         85 $string =~ s/(-\d+h)\s+(\d+m)\s*/$1 -$2 /;
597              
598 87         479 while ($string =~ s/^\s*(-?\d+(?:[.,]\d+)?)\s*([a-zA-Z]+)(?:\s*(?:,|and)\s*)*//i) {
599 166         4435 my($amount, $unit) = ($1, $2);
600 166 100       329 $unit = lc($unit) unless length($unit) == 1;
601              
602 166         135 my ($base_unit, $num);
603 166 100       303 if ( defined( $units{$unit} ) ) {
604 165         103 ($base_unit, $num) = @{$units{$unit}};
  165         251  
605 165         227 my $key = $base_unit . "-" . $num;
606 165 100       446 Carp::croak "Unknown timespec: $string_to_parse" if defined($done{$key});
607 164         197 $done{$key} = 1;
608              
609 164         157 $amount =~ s/,/./;
610 164 100       168 if ( $subtract ) {
611 13         43 $du->subtract( $base_unit => $amount * $num );
612             } else {
613 151         390 $du->add( $base_unit => $amount * $num );
614             }
615             } else {
616 1         94 Carp::croak "Unknown timespec: $string_to_parse";
617             }
618             }
619              
620 85 100       4295 if ($string =~ /\S/) { # OK to have extra spaces, but nothing else
621 2         176 Carp::croak "Unknown timespec: $string_to_parse";
622             }
623              
624 83         778 return $du;
625             }
626              
627             *parse_interval = \&parse_duration;
628              
629             =back
630              
631             =head2 FORMATTING METHODS
632              
633             This class provides the following formatting methods.
634              
635             The output is always in the format mandated by the SQL standard (derived
636             from ISO 8601), which is parsed by PostgreSQL unambiguously in all
637             DateStyle modes.
638              
639             =over 4
640              
641             =item * format_datetime($datetime,...)
642              
643             Given a C object, this method returns a string appropriate as
644             input for all date and date/time types of PostgreSQL. It will contain
645             date and time.
646              
647             If the time zone of the C part is floating, the resulting
648             string will contain no time zone, which will result in the server's time
649             zone being used. Otherwise, the numerical offset of the time zone is
650             used.
651              
652             =cut
653              
654             *format_datetime = \&format_timestamptz;
655              
656             =item * format_time($datetime,...)
657              
658             =item * format_time_without_time_zone($datetime,...)
659              
660             Given a C object, this method returns a string appropriate as
661             input for the TIME type (also known as TIME WITHOUT TIME ZONE), which
662             will contain the local time of the C object and no time zone.
663              
664             =cut
665              
666             sub _format_fractional
667             {
668 11     11   518 my $ns = shift->nanosecond;
669 11 100       71 return $ns ? sprintf(".%09d", "$ns") : ''
670             }
671              
672             sub format_time
673             {
674 0     0 1 0 my ($self,$dt,%param) = @_;
675 0         0 return $dt->hms(':')._format_fractional($dt);
676             }
677              
678             *format_time_without_time_zone = \&format_time;
679              
680             =item * format_timetz($datetime)
681              
682             =item * format_time_with_time_zone($datetime)
683              
684             Given a C object, this method returns a string appropriate as
685             input for the TIME WITH TIME ZONE type (also known as TIMETZ), which
686             will contain the local part of the C object and a numerical
687             time zone.
688              
689             You should not use the TIME WITH TIME ZONE type to store dates with
690             floating time zones. If the time zone of the C part is
691             floating, the resulting string will contain no time zone, which will
692             result in the server's time zone being used.
693              
694             =cut
695              
696             sub _format_time_zone
697             {
698 11     11   11 my $dt = shift;
699 11 100       26 return '' if $dt->time_zone->is_floating;
700 10         60 return &DateTime::TimeZone::offset_as_string($dt->offset);
701             }
702              
703             sub format_timetz
704             {
705 0     0 1 0 my ($self,$dt) = @_;
706 0         0 return $dt->hms(':')._format_fractional($dt)._format_time_zone($dt);
707             }
708              
709             *format_time_with_time_zone = \&format_timetz;
710              
711             =item * format_date($datetime)
712              
713             Given a C object, this method returns a string appropriate as
714             input for the DATE type, which will contain the date part of the
715             C object.
716              
717             =cut
718              
719             sub format_date
720             {
721 5     5 1 1848 my ($self,$dt) = @_;
722 5 100       20 if($dt->is_infinite) {
    100          
723 2 100       20 return $dt->isa('DateTime::Infinite::Future') ? 'infinity' : '-infinity';
724             } elsif($dt->year()<=0) {
725 1         21 return sprintf('%04d-%02d-%02d BC',
726             1-$dt->year(),
727             $dt->month(),
728             $dt->day());
729             } else {
730 2         18 return $dt->ymd('-');
731             }
732             }
733              
734             =item * format_timestamp($datetime)
735              
736             =item * format_timestamp_without_time_zone($datetime)
737              
738             Given a C object, this method returns a string appropriate as
739             input for the TIMESTAMP type (also known as TIMESTAMP WITHOUT TIME
740             ZONE), which will contain the local time of the C object and
741             no time zone.
742              
743             =cut
744              
745             sub format_timestamp
746             {
747 2     2 1 8 my ($self,$dt,%param) = @_;
748 2 50       5 if($dt->is_infinite) {
    0          
749 2 100       16 return $dt->isa('DateTime::Infinite::Future') ? 'infinity' : '-infinity';
750             } elsif($dt->year()<=0) {
751 0         0 return sprintf('%04d-%02d-%02d %s BC',
752             1-$dt->year(),
753             $dt->month(),
754             $dt->day(),
755             $dt->hms(':')._format_fractional($dt));
756             } else {
757 0         0 return $dt->ymd('-').' '.$dt->hms(':')._format_fractional($dt);
758             }
759             }
760              
761             *format_timestamp_without_time_zone = \&format_timestamp;
762              
763             =item * format_timestamptz($datetime)
764              
765             =item * format_timestamp_with_time_zone($datetime)
766              
767             Given a C object, this method returns a string appropriate as
768             input for the TIMESTAMP WITH TIME ZONE type, which will contain the
769             local part of the C object and a numerical time zone.
770              
771             You should not use the TIMESTAMP WITH TIME ZONE type to store dates with
772             floating time zones. If the time zone of the C part is
773             floating, the resulting string will contain no time zone, which will
774             result in the server's time zone being used.
775              
776             =cut
777              
778             sub format_timestamptz
779             {
780 13     13 1 20486 my ($self,$dt,%param) = @_;
781 13 100       41 if($dt->is_infinite) {
    100          
782 2 100       19 return $dt->isa('DateTime::Infinite::Future') ? 'infinity' : '-infinity';
783             } elsif($dt->year()<=0) {
784 2         18 return sprintf('%04d-%02d-%02d',
785             1-$dt->year(),
786             $dt->month(),
787             $dt->day()).
788             ' '.
789             $dt->hms(':').
790             _format_fractional($dt).
791             _format_time_zone($dt).
792             ' BC';
793             } else {
794 9         74 return $dt->ymd('-').' '.$dt->hms(':').
795             _format_fractional($dt).
796             _format_time_zone($dt);
797             }
798             }
799              
800             *format_timestamp_with_time_zone = \&format_timestamptz;
801              
802             =item * format_duration($du)
803              
804             =item * format_interval($du)
805              
806             Given a C object, this method returns a string appropriate
807             as input for the INTERVAL type.
808              
809             =cut
810              
811             sub format_duration {
812 5 50 33 5 1 1389 shift if UNIVERSAL::isa($_[0], __PACKAGE__) || $_[0] eq __PACKAGE__;
813 5         6 my($du,%param) = @_;
814 5 50       18 croak 'DateTime::Duration object expected' unless UNIVERSAL::isa($du,'DateTime::Duration');
815              
816 5         39 my %deltas = $du->deltas();
817 5         48 my $output = '@';
818              
819 5 100       12 if($deltas{'nanoseconds'}) {
820             $deltas{'seconds'} =
821 2         22 sprintf('%f', $deltas{'seconds'} + $deltas{'nanoseconds'} /
822             DateTime::Duration::MAX_NANOSECONDS);
823             }
824              
825 5         10 foreach(qw(months days minutes seconds)) {
826 20 100       51 $output .= ' '.$deltas{$_}.' '.$_ if $deltas{$_};
827             }
828              
829 5 50       11 $output .= ' 0' if(length($output)<=2);
830 5         20 return $output;
831             }
832              
833             *format_interval = \&format_duration;
834              
835             =back
836              
837             =cut
838              
839              
840              
841             1;
842              
843             __END__