Skip to content

Commit

Permalink
Fix month and year adjustment wrapping issue.
Browse files Browse the repository at this point in the history
Month and year adjustments introduced in 5.7 had a bug where adjusting the month
to one where the target date doesn't exist caused the day to roll over into the
following month (ex: May 31st - 1 month became May 1st instead of April 30th)
and similarily with leapday year adjustments (Feb 29th, 2016 + 1 year), this was
fixed to adjust to the last day of the correct month instead of rolling over
into the next month.
  • Loading branch information
pajamian committed Jun 5, 2017
1 parent b498ab7 commit fb8cd26
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 15 deletions.
13 changes: 11 additions & 2 deletions UPGRADE
Expand Up @@ -8,8 +8,17 @@ Briefly summarized, here's what you can expect when upgrading from the
following versions:

5.10.x -- A minor bug was fixed in an edge-case usage of the [area] tag which
could result in incompatibility if your code relies on the buggy
behaviour.
could result in incompatibility if your code relies on the buggy
behaviour.

-- Month and year adjustments introduced in 5.7 had a bug where
adjusting the month to one where the target date doesn't exist caused
the day to roll over into the following month (ex: May 31st - 1 month
became May 1st instead of April 30th) and similarily with leapday
year adjustments (Feb 29th, 2016 + 1 year), this was fixed to adjust
to the last day of the correct month instead of rolling over into the
next month. If your code relies on the old behavior please update
it.

5.6.x -- Perl 5.8.8 or newer is now generally required to run Interchange.
See "Known Issues" below.
Expand Down
49 changes: 36 additions & 13 deletions lib/Vend/Util.pm
Expand Up @@ -2490,6 +2490,27 @@ sub timecard_read {
# optional.
#
sub adjust_time {
# We need special adjustments to take into account end of month or leap year
# issues in adjusting the month or year. This sub will adjust the time
# passed in $time as well as kick back a unixtime of the adjusted time.
my $perform_adjust = sub {
my ($time, $adjust) = @_;
# Do an adjustment based on year and month first to check for issues
# with leap year and end of month variances. We set isdst to -1 to
# avoid variances due to DST time change.
my @timecheck = @$time;
$timecheck[5] += $adjust->[5];
$timecheck[4] += $adjust->[4];
$timecheck[8] = -1;
my @adjusted = localtime(POSIX::mktime(@timecheck));
# If the day is off we need to add an additional adjustment for it.
$adjust->[3] -= $adjusted[3] if $adjusted[3] < $timecheck[3];
$time->[$_] += $adjust->[$_] for (0..5);
my $unixtime = POSIX::mktime(@$time);
@$time = localtime($unixtime);
return $unixtime;
};

my ($adjust, $time, $compensate_dst) = @_;
$time ||= time;

Expand All @@ -2511,6 +2532,7 @@ sub adjust_time {
# or leave the time the same).

my @times = localtime($time);
my @adjust = (0)x6;
my $sign = 1;

foreach my $amount ($adjust =~ /([+-]?\s*[\d\.]+\s*[a-z]*)/ig) {
Expand All @@ -2527,12 +2549,12 @@ sub adjust_time {
$amount *= 7;
}

if ($unit =~ /^s/) { $times[0] += $amount }
elsif ($unit =~ /^mo/) { $times[4] += $amount } # has to come before min
elsif ($unit =~ /^m/) { $times[1] += $amount }
elsif ($unit =~ /^h/) { $times[2] += $amount }
elsif ($unit =~ /^d/) { $times[3] += $amount }
elsif ($unit =~ /^y/) { $times[5] += $amount }
if ($unit =~ /^s/) { $adjust[0] += $amount }
elsif ($unit =~ /^mo/) { $adjust[4] += $amount } # has to come before min
elsif ($unit =~ /^m/) { $adjust[1] += $amount }
elsif ($unit =~ /^h/) { $adjust[2] += $amount }
elsif ($unit =~ /^d/) { $adjust[3] += $amount }
elsif ($unit =~ /^y/) { $adjust[5] += $amount }

else {
::logError("adjust_time(): bad unit: $unit");
Expand All @@ -2546,26 +2568,27 @@ sub adjust_time {
my @multip = (0, 60, 60, 24, 0, 12);
my $monfrac = 0;
foreach my $i (reverse 0..5) {
if ($times[$i] =~ /\./) {
if ($adjust[$i] =~ /\./) {
if ($multip[$i]) {
$times[$i-1] += ($times[$i] - int $times[$i]) * $multip[$i];
$adjust[$i-1] += ($adjust[$i] - int $adjust[$i]) * $multip[$i];
}

elsif ($i == 4) {
# Fractions of a month need some really extra special handling.
$monfrac = $times[$i] - int $times[$i];
$monfrac = $adjust[$i] - int $adjust[$i];
}

$times[$i] = int $times[$i]
$adjust[$i] = int $adjust[$i];
}
}

$time = POSIX::mktime(@times);
$time = $perform_adjust->(\@times, \@adjust);

# This is how we handle a fraction of a month:
if ($monfrac) {
$times[4] += $monfrac > 0 ? 1 : -1;
my $timediff = POSIX::mktime(@times);
@adjust = (0)x6;
$adjust[4] = $monfrac > 0 ? 1 : -1;
my $timediff = $perform_adjust->(\@times, \@adjust);
$timediff = int(abs($timediff - $time) * $monfrac);
$time += $timediff;
}
Expand Down

0 comments on commit fb8cd26

Please sign in to comment.