File Coverage

blib/lib/DateTime/Format/Pg.pm
Criterion Covered Total %
statement 144 167 86.2
branch 63 86 73.2
condition 21 38 55.2
subroutine 26 31 83.8
pod 13 13 100.0
total 267 335 79.7


line stmt bran cond sub pod time code
1             package DateTime::Format::Pg;
2              
3 22     22   4296628 use strict;
  22         54  
  22         652  
4 22     22   113 use vars qw ($VERSION);
  22         47  
  22         997  
5              
6 22     22   117 use Carp;
  22         45  
  22         1281  
7 22     22   9260 use DateTime 0.13;
  22         5715903  
  22         833  
8 22     22   163 use DateTime::Duration;
  22         52  
  22         544  
9 22     22   11972 use DateTime::Format::Builder 0.72;
  22         1476834  
  22         196  
10 22     22   913 use DateTime::TimeZone 0.06;
  22         315  
  22         442  
11 22     22   252 use DateTime::TimeZone::UTC;
  22         51  
  22         558  
12 22     22   109 use DateTime::TimeZone::Floating;
  22         49  
  22         58056  
13              
14             $VERSION = '0.16013';
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<DateTime> or C<DateTime::Duration> objects, and it can take a
37             C<DateTime> or C<DateTime::Duration> 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<DateTime::Format::Pg> objects.
43              
44             =over 4
45              
46             =item * new( name => value, ... )
47              
48             Creates a new C<DateTime::Format::Pg> 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<DateTime::TimeZone> 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   8 my ($to,%param) = @_;
90 2         7 foreach(keys %param)
91             {
92 2 50       15 if($_ eq 'european') {
    0          
93 2         14 $$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 35 my ($self,%param) = @_;
104 14 100       55 return $param{'european'} if exists $param{'european'};
105 2 50       9 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 142 my $class = shift;
118 2   33     12 my $self = bless {}, ref($class)||$class;
119 2 50       7 if (ref $class)
120             {
121 0         0 $self->{'_european'} = ( scalar $class->{'_european'} );
122             }
123 2         19 _add_param($self,@_);
124 2         6 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<clone()>.
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   2937 my $self = shift;
150 6         16 my %p = @_;
151              
152 6 100       15 if ($p{sign}) {
153 3         16 return DateTime::Infinite::Past->new;
154             } else {
155 3         37 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 37     37   31743 my %args = @_;
276 37   100     218 my $era = (delete $args{'parsed'}->{'era'}) || '';
277 37 100       124 if ($era =~ m/BC/) {
278 7         38 $args{'parsed'}->{'year'} = 1-$args{'parsed'}->{'year'}
279             }
280 37         200 return 1;
281             }
282              
283             # Fix European dates (swap month and day)
284             #
285             sub _fix_eu {
286 14     14   113 my %args = @_;
287 14 100       26 if($args{'self'}->european(@{$args{'args'}}) ) {
  14         49  
288 7         17 my $save = $args{'parsed'}->{'month'};
289 7         12 $args{'parsed'}->{'month'} = $args{'parsed'}->{'day'};
290 7         14 $args{'parsed'}->{'day'} = $save;
291             }
292 14         34 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 20     20   4708 my %args = @_;
312 20 100       88 my %param = $args{'args'} ? (@{$args{'args'}}) : ();
  2         55  
313            
314 20 100       180 if($param{'_force_tz'}) {
    100          
    50          
315 2         7 $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     59 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         17 $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         5 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 14         31 my $tz;
343 14 100       98 if (length($2) == 2) {
344             # regular hour notation
345 10   100     134 my ($min, $sec) = ($3 || '00', $4 || '00');
      100        
346 10         86 $tz = sprintf "%s%02d:%02d:%02d", $1, $2, $min, $sec;
347             } else {
348 4         13 $tz = "$1$2";
349             }
350 14         40 $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 20         112 return 1;
364             }
365              
366             # Fix fractional seconds
367             #
368             sub _fix_nanosecond {
369 20     20   187 my %args = @_;
370 20 100       79 if(defined $args{'parsed'}->{'nanosecond'}) {
371 8         64 $args{'parsed'}->{'nanosecond'} = sprintf '%.0f', $args{'parsed'}->{'nanosecond'} * 1.0E9;
372             } else {
373 12         26 delete $args{'parsed'}->{'nanosecond'}
374             };
375 20         57 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<DateTime::Format> 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<DateTime> 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<DateTime> 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<DateTime> 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 732 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<DateTime> 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<DateTime> 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<DateTime> 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 242 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<DateTime> 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<DateTime::Duration> object.
557              
558             If given an improperly formatted string, this method may die.
559              
560             =cut
561              
562             sub parse_duration {
563 88     88 1 162378 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 88         385 my $du = DateTime::Duration->new;
569              
570 88         12041 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 88         457 (my $string = $string_to_parse) =~ s/^@\s*//;
582 88         191 $string =~ s/\+(\d+)/$1/g;
583              
584             # Method used later on duration object
585 88         138 my $arith_method = "add";
586 88 100       265 if ( $string =~ s/ago// ) {
587 8         15 $arith_method = "subtract";
588             }
589              
590 88         131 my $sign = 0;
591 88         140 my %done;
592              
593 88         299 $string =~ s/\b(\d+):(\d\d):(\d\d)(\.\d+)?\b/$1h $2m $3$4s/g;
594 88         194 $string =~ s/\b(\d+):(\d\d)\b/$1h $2m/g;
595 88         186 $string =~ s/(-\d+h)\s+(\d+m)\s+(\d+s)\s*/$1 -$2 -$3 /;
596 88         159 $string =~ s/(-\d+h)\s+(\d+m)\s*/$1 -$2 /;
597              
598 88         529 while ($string =~ s/^\s*(-?\d+(?:[.,]\d+)?)\s*([a-zA-Z]+)(?:\s*(?:,|and)\s*)*//i) {
599 170         10470 my($amount, $unit) = ($1, $2);
600 170 100       457 if (length($unit) != 1) {
601 97         167 $unit = lc($unit);
602             }
603              
604 170         293 my $udata = $units{$unit};
605 170 100       374 if (! $udata) {
606 1         94 Carp::croak("Unknown timespec: $string_to_parse");
607             }
608 169         326 my ($base_unit, $num) = @$udata;
609 169         352 my $key = $base_unit . "-" . $num;
610 169 100       379 if (exists $done{$key}) {
611 1         198 Carp::croak("Unknown timespec: $string_to_parse");
612             }
613 168         320 $done{$key} = 1;
614              
615 168         241 my @extra_args;
616              
617 168         290 $amount =~ s/,/./;
618 168 100       351 if ($amount =~ s/\.(\d+)$//) {
619 1         3 my $fractional = $1;
620             # We only handle fractional seconds right now. If you
621             # need support for silly formats (from my perspective ;-P)
622             # like '1.5 weeks', please provide me with a comprehensive
623             # test for all possible combinations of fractional times.
624 1 50       4 if ($base_unit ne "seconds") {
625 0         0 Carp::croak("Fractional input detected: currently only fractional seconds are supported")
626             }
627              
628             # From the spec, Pg can take up to 6 digits for fractional part
629             # (duh, as 1 sec = 1_000_000 nano sec). If we're missing 0's,
630             # we should pad them
631 1         3 $fractional .= '0'x (6 - length($fractional));
632 1         3 push @extra_args, ("nanoseconds" => $fractional);
633             }
634              
635 168         714 $du->$arith_method($base_unit => $amount * $num, @extra_args);
636             }
637              
638 86 100       10016 if ($string =~ /\S/) { # OK to have extra spaces, but nothing else
639 2         207 Carp::croak "Unknown timespec: $string_to_parse";
640             }
641              
642 84         928 return $du;
643             }
644              
645             *parse_interval = \&parse_duration;
646              
647             =back
648              
649             =head2 FORMATTING METHODS
650              
651             This class provides the following formatting methods.
652              
653             The output is always in the format mandated by the SQL standard (derived
654             from ISO 8601), which is parsed by PostgreSQL unambiguously in all
655             DateStyle modes.
656              
657             =over 4
658              
659             =item * format_datetime($datetime,...)
660              
661             Given a C<DateTime> object, this method returns a string appropriate as
662             input for all date and date/time types of PostgreSQL. It will contain
663             date and time.
664              
665             If the time zone of the C<DateTime> part is floating, the resulting
666             string will contain no time zone, which will result in the server's time
667             zone being used. Otherwise, the numerical offset of the time zone is
668             used.
669              
670             =cut
671              
672             *format_datetime = \&format_timestamptz;
673              
674             =item * format_time($datetime,...)
675              
676             =item * format_time_without_time_zone($datetime,...)
677              
678             Given a C<DateTime> object, this method returns a string appropriate as
679             input for the TIME type (also known as TIME WITHOUT TIME ZONE), which
680             will contain the local time of the C<DateTime> object and no time zone.
681              
682             =cut
683              
684             sub _format_fractional
685             {
686 11     11   265 my $ns = shift->nanosecond;
687 11 100       90 return $ns ? sprintf(".%09d", "$ns") : ''
688             }
689              
690             sub format_time
691             {
692 0     0 1 0 my ($self,$dt,%param) = @_;
693 0         0 return $dt->hms(':')._format_fractional($dt);
694             }
695              
696             *format_time_without_time_zone = \&format_time;
697              
698             =item * format_timetz($datetime)
699              
700             =item * format_time_with_time_zone($datetime)
701              
702             Given a C<DateTime> object, this method returns a string appropriate as
703             input for the TIME WITH TIME ZONE type (also known as TIMETZ), which
704             will contain the local part of the C<DateTime> object and a numerical
705             time zone.
706              
707             You should not use the TIME WITH TIME ZONE type to store dates with
708             floating time zones. If the time zone of the C<DateTime> part is
709             floating, the resulting string will contain no time zone, which will
710             result in the server's time zone being used.
711              
712             =cut
713              
714             sub _format_time_zone
715             {
716 11     11   19 my $dt = shift;
717 11 100       39 return '' if $dt->time_zone->is_floating;
718 10         90 return &DateTime::TimeZone::offset_as_string($dt->offset);
719             }
720              
721             sub format_timetz
722             {
723 0     0 1 0 my ($self,$dt) = @_;
724 0         0 return $dt->hms(':')._format_fractional($dt)._format_time_zone($dt);
725             }
726              
727             *format_time_with_time_zone = \&format_timetz;
728              
729             =item * format_date($datetime)
730              
731             Given a C<DateTime> object, this method returns a string appropriate as
732             input for the DATE type, which will contain the date part of the
733             C<DateTime> object.
734              
735             =cut
736              
737             sub format_date
738             {
739 5     5 1 2817 my ($self,$dt) = @_;
740 5 100       24 if($dt->is_infinite) {
    100          
741 2 100       27 return $dt->isa('DateTime::Infinite::Future') ? 'infinity' : '-infinity';
742             } elsif($dt->year()<=0) {
743 1         19 return sprintf('%04d-%02d-%02d BC',
744             1-$dt->year(),
745             $dt->month(),
746             $dt->day());
747             } else {
748 2         25 return $dt->ymd('-');
749             }
750             }
751              
752             =item * format_timestamp($datetime)
753              
754             =item * format_timestamp_without_time_zone($datetime)
755              
756             Given a C<DateTime> object, this method returns a string appropriate as
757             input for the TIMESTAMP type (also known as TIMESTAMP WITHOUT TIME
758             ZONE), which will contain the local time of the C<DateTime> object and
759             no time zone.
760              
761             =cut
762              
763             sub format_timestamp
764             {
765 2     2 1 14 my ($self,$dt,%param) = @_;
766 2 50       7 if($dt->is_infinite) {
    0          
767 2 100       19 return $dt->isa('DateTime::Infinite::Future') ? 'infinity' : '-infinity';
768             } elsif($dt->year()<=0) {
769 0         0 return sprintf('%04d-%02d-%02d %s BC',
770             1-$dt->year(),
771             $dt->month(),
772             $dt->day(),
773             $dt->hms(':')._format_fractional($dt));
774             } else {
775 0         0 return $dt->ymd('-').' '.$dt->hms(':')._format_fractional($dt);
776             }
777             }
778              
779             *format_timestamp_without_time_zone = \&format_timestamp;
780              
781             =item * format_timestamptz($datetime)
782              
783             =item * format_timestamp_with_time_zone($datetime)
784              
785             Given a C<DateTime> object, this method returns a string appropriate as
786             input for the TIMESTAMP WITH TIME ZONE type, which will contain the
787             local part of the C<DateTime> object and a numerical time zone.
788              
789             You should not use the TIMESTAMP WITH TIME ZONE type to store dates with
790             floating time zones. If the time zone of the C<DateTime> part is
791             floating, the resulting string will contain no time zone, which will
792             result in the server's time zone being used.
793              
794             =cut
795              
796             sub format_timestamptz
797             {
798 13     13 1 32356 my ($self,$dt,%param) = @_;
799 13 100       56 if($dt->is_infinite) {
    100          
800 2 100       25 return $dt->isa('DateTime::Infinite::Future') ? 'infinity' : '-infinity';
801             } elsif($dt->year()<=0) {
802 2         25 return sprintf('%04d-%02d-%02d',
803             1-$dt->year(),
804             $dt->month(),
805             $dt->day()).
806             ' '.
807             $dt->hms(':').
808             _format_fractional($dt).
809             _format_time_zone($dt).
810             ' BC';
811             } else {
812 9         112 return $dt->ymd('-').' '.$dt->hms(':').
813             _format_fractional($dt).
814             _format_time_zone($dt);
815             }
816             }
817              
818             *format_timestamp_with_time_zone = \&format_timestamptz;
819              
820             =item * format_duration($du)
821              
822             =item * format_interval($du)
823              
824             Given a C<DateTime::Duration> object, this method returns a string appropriate
825             as input for the INTERVAL type.
826              
827             =cut
828              
829             sub format_duration {
830 5 50 33 5 1 2688 shift if UNIVERSAL::isa($_[0], __PACKAGE__) || $_[0] eq __PACKAGE__;
831 5         15 my($du,%param) = @_;
832 5 50       27 croak 'DateTime::Duration object expected' unless UNIVERSAL::isa($du,'DateTime::Duration');
833              
834 5         35 my %deltas = $du->deltas();
835 5         75 my $output = '@';
836              
837 5 100       21 if($deltas{'nanoseconds'}) {
838             $deltas{'seconds'} =
839 2         25 sprintf('%f', $deltas{'seconds'} + $deltas{'nanoseconds'} /
840             DateTime::Duration::MAX_NANOSECONDS);
841             }
842              
843 5         12 foreach(qw(months days minutes seconds)) {
844 20 100       63 $output .= ' '.$deltas{$_}.' '.$_ if $deltas{$_};
845             }
846              
847 5 50       16 $output .= ' 0' if(length($output)<=2);
848 5         22 return $output;
849             }
850              
851             *format_interval = \&format_duration;
852              
853             =back
854              
855             =cut
856              
857              
858              
859             1;
860              
861             __END__
862              
863             =head1 LIMITATIONS
864              
865             Some output formats of PostgreSQL have limitations that can only be passed on
866             by this class.
867              
868             As a general rules, none of these limitations apply to the 'ISO' output
869             format. It is strongly recommended to use this format (and to use
870             PostgreSQL's to_char function when another output format that's not
871             supposed to be handled by a parser of this class is desired). 'ISO' is
872             the default but you are advised to explicitly set it at the beginning of
873             the session by issuing a SET DATESTYLE TO 'ISO'; command in case the
874             server administrator changes that setting.
875              
876             When formatting DateTime objects, this class always uses a format that's
877             handled unambiguously by PostgreSQL.
878              
879             =head2 TIME ZONES
880              
881             If DateStyle is set to 'PostgreSQL', 'SQL', or 'German', PostgreSQL does
882             not send numerical time zones for the TIMESTAMPTZ (or TIMESTAMP WITH
883             TIME ZONE) type. Unfortunately, the time zone names used instead can be
884             ambiguous: For example, 'EST' can mean -0500, +1000, or +1100.
885              
886             You must set the 'server_tz' variable to a time zone that is identical to that
887             of the PostgreSQL server. If the server is set to a different time zone (or the
888             underlying operating system interprets the time zone differently), the parser
889             will return wrong times.
890              
891             You can avoid such problems by setting the server's time zone to UTC
892             using the SET TIME ZONE 'UTC' command and setting 'server_tz' parameter
893             to 'UTC' (or by using the ISO output format, of course).
894              
895             =head2 EUROPEAN DATES
896              
897             For the SQL (for DATE and TIMSTAMP[TZ]) and the PostgreSQL (for DATE)
898             output format, the server can send dates in both European-style
899             'dd/mm/yyyy' and in US-style 'mm/dd/yyyy' format. In order to parse
900             these dates correctly, you have to pass the 'european' option to the
901             constructor or to the C<parse_xxx> routines.
902              
903             This problem does not occur when using the ISO or German output format
904             (and for PostgreSQL with TIMESTAMP[TZ] as month names are used then).
905              
906             =head2 INTERVAL ELEMENTS
907              
908             C<DateTime::Duration> stores months, days, minutes and seconds
909             separately. PostgreSQL only stores months and seconds and disregards the
910             irregular length of days due to DST switching and the irregular length
911             of minutes due to leap seconds. Therefore, it is not possible to store
912             C<DateTime::Duration> objects as SQL INTERVALs without the loss of some
913             information.
914              
915             =head2 NEGATIVE INTERVALS
916              
917             In the SQL and German output formats, the server does not send an
918             indication of the sign with intervals. This means that '1 month ago' and
919             '1 month' are both returned as '1 mon'.
920              
921             This problem can only be avoided by using the 'ISO' or 'PostgreSQL'
922             output format.
923              
924             =head1 SUPPORT
925              
926             Support for this module is provided via the datetime@perl.org email
927             list. See http://lists.perl.org/ for more details.
928              
929             =head1 AUTHOR
930              
931             Daisuke Maki E<lt>daisuke@endeworks.jpE<gt>
932              
933             =head1 AUTHOR EMERITUS
934              
935             Claus A. Faerber <perl@faerber.muc.de>
936              
937             =head1 COPYRIGHT
938              
939             Copyright (c) 2003 Claus A. Faerber. Copyright (c) 2005-2007 Daisuke Maki
940              
941             This program is free software; you can redistribute it and/or modify it under
942             the same terms as Perl itself.
943              
944             The full text of the license can be found in the LICENSE file included with
945             this module.
946              
947             =head1 SEE ALSO
948              
949             datetime@perl.org mailing list
950              
951             http://datetime.perl.org/
952              
953             =cut