From 3bf25ee812f8aecb018460ef943feeb003a73483 Mon Sep 17 00:00:00 2001 From: Daisuke Maki Date: Fri, 19 Aug 2016 09:25:42 +0900 Subject: [PATCH] Fix parse_duration handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * handle fractional seconds (but only seconds) * if you want support for fractional [some other unit], please send in PRs with new tests Signed-off-by: Petr Písař --- README.md | 2 +- lib/DateTime/Format/Pg.pm | 58 +++++++++++++++++++++++++++++++---------------- t/gh12.t | 20 ++++++++++++++++ 3 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 t/gh12.t diff --git a/README.md b/README.md index 7d1167f..6c61c27 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ handled unambiguously by PostgreSQL. If DateStyle is set to 'PostgreSQL', 'SQL', or 'German', PostgreSQL does not send numerical time zones for the TIMESTAMPTZ (or TIMESTAMP WITH -TIME ZONE) type. Unfortunatly, the time zone names used instead can be +TIME ZONE) type. Unfortunately, the time zone names used instead can be ambiguous: For example, 'EST' can mean -0500, +1000, or +1100. You must set the 'server\_tz' variable to a time zone that is identical to that diff --git a/lib/DateTime/Format/Pg.pm b/lib/DateTime/Format/Pg.pm index b262e2a..0258cd7 100644 --- a/lib/DateTime/Format/Pg.pm +++ b/lib/DateTime/Format/Pg.pm @@ -581,40 +581,58 @@ sub parse_duration { (my $string = $string_to_parse) =~ s/^@\s*//; $string =~ s/\+(\d+)/$1/g; - my $subtract = 0; + # Method used later on duration object + my $arith_method = "add"; if ( $string =~ s/ago// ) { - $subtract = 1; + $arith_method = "subtract"; } my $sign = 0; my %done; -# $timespec =~ s/\b(\d+):(\d\d):((\d\d)|(\d\d.\d+))\b/$1h $2m $3s/g; - $string =~ s/\b(\d+):(\d\d):(\d\d)\b/$1h $2m $3s/g; + $string =~ s/\b(\d+):(\d\d):(\d\d)(\.\d+)?\b/$1h $2m $3$4s/g; $string =~ s/\b(\d+):(\d\d)\b/$1h $2m/g; $string =~ s/(-\d+h)\s+(\d+m)\s+(\d+s)\s*/$1 -$2 -$3 /; $string =~ s/(-\d+h)\s+(\d+m)\s*/$1 -$2 /; while ($string =~ s/^\s*(-?\d+(?:[.,]\d+)?)\s*([a-zA-Z]+)(?:\s*(?:,|and)\s*)*//i) { my($amount, $unit) = ($1, $2); - $unit = lc($unit) unless length($unit) == 1; - - my ($base_unit, $num); - if ( defined( $units{$unit} ) ) { - ($base_unit, $num) = @{$units{$unit}}; - my $key = $base_unit . "-" . $num; - Carp::croak "Unknown timespec: $string_to_parse" if defined($done{$key}); - $done{$key} = 1; - - $amount =~ s/,/./; - if ( $subtract ) { - $du->subtract( $base_unit => $amount * $num ); - } else { - $du->add( $base_unit => $amount * $num ); + if (length($unit) != 1) { + $unit = lc($unit); + } + + my $udata = $units{$unit}; + if (! $udata) { + Carp::croak("Unknown timespec: $string_to_parse"); + } + my ($base_unit, $num) = @$udata; + my $key = $base_unit . "-" . $num; + if (exists $done{$key}) { + Carp::croak("Unknown timespec: $string_to_parse"); + } + $done{$key} = 1; + + my @extra_args; + + $amount =~ s/,/./; + if ($amount =~ s/\.(\d+)$//) { + my $fractional = $1; + # We only handle fractional seconds right now. If you + # need support for silly formats (from my perspective ;-P) + # like '1.5 weeks', please provide me with a comprehensive + # test for all possible combinations of fractional times. + if ($base_unit ne "seconds") { + Carp::croak("Fractional input detected: currently only fractional seconds are supported") } - } else { - Carp::croak "Unknown timespec: $string_to_parse"; + + # From the spec, Pg can take up to 6 digits for fractional part + # (duh, as 1 sec = 1_000_000 nano sec). If we're missing 0's, + # we should pad them + $fractional .= '0'x (6 - length($fractional)); + push @extra_args, ("nanoseconds" => $fractional); } + + $du->$arith_method($base_unit => $amount * $num, @extra_args); } if ($string =~ /\S/) { # OK to have extra spaces, but nothing else diff --git a/t/gh12.t b/t/gh12.t new file mode 100644 index 0000000..c4a2e83 --- /dev/null +++ b/t/gh12.t @@ -0,0 +1,20 @@ +use strict; +use Test::More; + +use_ok('DateTime::Format::Pg'); + +# https://www.postgresql.org/docs/9.5/static/datatype-datetime.html#DATATYPE-INTERVAL-INPUT +my $offset = '1095 days 13:37:28.36922'; +my $duration; +eval { + $duration = DateTime::Format::Pg->parse_duration($offset); +}; +my $e = $@; +if (! ok !$e, "should succeed parsing '$offset' without errors") { + diag $e; +} + +is $duration->seconds, 28, "seconds should be '28'"; +is $duration->nanoseconds, 369220, "seconds should be '369220'"; + +done_testing; \ No newline at end of file -- 2.7.4