LP#1635737 Due date calculation honors variable durations user/berick/lp1635737-due-date-interval-smarts
authorBill Erickson <berickxx@gmail.com>
Wed, 22 Mar 2017 19:02:58 +0000 (15:02 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 22 Mar 2017 19:03:08 +0000 (15:03 -0400)
Due date calculations now take time changes, variable length months, and
variable length (leap) years into account.

For example, a duration of "1 month" results in a due date with the same
day the following month, instead of simply adding 365/12=~30 days to
today.

Note, in cases where a matching day in the following month (or year)
does not exist (e.g. Jan 31 + "1 month"), days are added to the result
to accommodate the extra days.  Jan 31 + "1 month" yields March 3rd in
a non-leap year.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/Circ/Circulate.pm

index a8c6c9f..b30dc15 100644 (file)
@@ -5,6 +5,7 @@ use base qw/OpenILS::Application/;
 use OpenSRF::Utils::Cache;
 use OpenSRF::Utils::Logger qw/$logger/;
 use OpenILS::Utils::ModsParser;
+use OpenSRF::Utils qw/:datetime/;
 use OpenSRF::EX qw(:try);
 use OpenILS::Event;
 use Data::Dumper;
@@ -2384,6 +2385,35 @@ sub verify_migrated_user_password {
         $e, $user_id, md5_hex($salt . $md5_pass), $pw_type);
 }
 
+# Adds an interval amount to a date, taking variable length durations
+# into account, for example days across time changes boundaries, 
+# number of days in a month, and number of days in a (leap) year.
+# If no date is provided, it defaults to NOW().
+sub add_interval {
+    my $class = shift;
+    my $interval = shift;
+    my $date = shift || DateTime->now(time_zone => 'local');
+
+    # Pass the interval value through a cycle of interval_to_seconds()
+    # and seconds_to_interval() to normalize the interval string.
+    my $seconds = OpenSRF::Utils->interval_to_seconds($interval);
+    $interval = OpenSRF::Utils->seconds_to_interval($seconds);
+
+    # Then chop the interval up into components DateTime::add() understands.
+    # ::add() expects all lower case, plural duration components.
+
+    my %parts;
+    for my $component (split(/, /, $interval)) {
+        my ($number, $part) = split(/ /, $component);
+        $part = lc($part);
+        $part .= 's' unless ($part =~ /s$/); # force plural
+        $parts{$part} = $number;
+    }
+    $date->add(\%parts);
+
+    return $date;
+}
+
 
 1;
 
index 74e425b..be24986 100644 (file)
@@ -2083,8 +2083,7 @@ sub create_due_date {
     my $due_date = DateTime->now(time_zone => 'local');
     $due_date = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($start_time)) if $start_time;
 
-    # add the circ duration
-    $due_date->add(seconds => OpenSRF::Utils->interval_to_seconds($duration));
+    $due_date = $U->add_interval($duration, $due_date);
 
     if($date_ceiling) {
         my $cdate = DateTime::Format::ISO8601->new->parse_datetime(cleanse_ISO8601($date_ceiling));