[ic] time tag with adjust in February bug?

Mike Heins mike at perusion.com
Mon Apr 1 12:32:12 UTC 2013


Quoting Gert van der Spoel (gert at 3edge.com):
> > -----Original Message-----
> > From: interchange-users-bounces at icdevgroup.org [mailto:interchange-users-
> > bounces at icdevgroup.org] On Behalf Of Mike Heins
> > Sent: maandag 1 april 2013 15:00
> > To: interchange-users at icdevgroup.org
> > Subject: Re: [ic] time tag with adjust in February bug?
> > 
> > Quoting Peter (peter at pajamian.dhs.org):
> > > On 04/01/2013 04:36 PM, Mike Heins wrote:
> > > >There is no "month". 1m is one minute, the "onth" makes no difference.
> > > >And you can't do addition and subtraction by combining terms...
> > >
> > > Actually there is, added in May of 2009.  Check the adjust_time()
> > > function in Utils.pm
> > >
> > > >This is April Fool's on European time, no?
> > >
> > > Nope, even though it was April 1st in New Zealand at the time.
> > >
> > 
> > I am afraid I was not aware of this transition in code, I had thought
> > that all of this type of code used Vend::Config::time_to_seconds().
> > One is, of course, at the mercy of the mktime() function, and it is
> > not at all clear what happens when you alter multiple members. March
> > and February are particularly picky in that area, as Feb. 30 is not
> > something that can be represented.
> > 
> > The only way to do this properly would be to run mktime after
> > each atom of adjustment, generating a new @times array, and then
> > apply the next. Even then, you may not get what you think you
> > are going to get.
> 
> That was my initial thought but nope ... 31-5 minus 1 month makes 1-5 ... 
> It would need adjusting at this point already ... 

But then you could do 

	-1 month +1 day

and have it work as it should. I ran this code on my new function (patch
attached):

use POSIX;
my $time = 1364728390;
print "Time starts as $time, which is " . scalar localtime($time) . "\n";
for(
	'-1 day',
	'-1 month',
	'-1 month 1 day',
	'-1 month +1 day',
	'-2 month 1 day',
) {
	my $newtime = adjust_time($_, $time);
	print "Adjust '$_', get ";
	print scalar localtime($newtime);
	print "\n";
	$newtime = adjust_time($_, $time, 1);
	print "Adjust '$_', get ";
	print scalar localtime($newtime);
	print " (including dst)\n";
}

It produces:

Time starts as 1364728390, which is Sun Mar 31 07:13:10 2013
Adjust '-1 day', get Sat Mar 30 07:13:10 2013
Adjust '-1 day', get Sat Mar 30 07:13:10 2013 (including dst)
Adjust '-1 month', get Sun Mar  3 06:13:10 2013
Adjust '-1 month', get Sun Mar  3 07:13:10 2013 (including dst)
Adjust '-1 month 1 day', get Sat Mar  2 06:13:10 2013
Adjust '-1 month 1 day', get Sat Mar  2 07:13:10 2013 (including dst)
Adjust '-1 month +1 day', get Mon Mar  4 06:13:10 2013
Adjust '-1 month +1 day', get Mon Mar  4 07:13:10 2013 (including dst)
Adjust '-2 month 1 day', get Wed Jan 30 06:13:10 2013
Adjust '-2 month 1 day', get Wed Jan 30 07:13:10 2013 (including dst)

This shows up when you google "mktime February". People are calling
it a bug, but it isn't, really. 

-- 
Mike Heins
Perusion -- Expert Interchange Consulting    http://www.perusion.com/
phone +1.765.253.4194  <mike at perusion.com>

Some people have twenty years of experience, some people have
one year of experience twenty times over. -- Anonymous
-------------- next part --------------
--- /old/Util.pm	2013-04-01 08:05:36.480922726 -0400
+++ /new/Util.pm	2013-04-01 08:31:00.000000000 -0400
@@ -2465,10 +2465,16 @@
     # mktime will make the appropriate adjustment for us (either add one hour or subtract one hour
     # or leave the time the same).
 
+    my $interim;
     my @times = localtime($time);
     my $sign = 1;
 
-    foreach my $amount ($adjust =~ /([+-]?\s*[\d\.]+\s*[a-z]*)/ig) {
+    while( $adjust =~ /([+-]?\s*[\d\.]+\s*[a-z]*)/g ) {
+    	my $amount = $1;
+    	if($interim) {
+		@times = localtime($interim);
+		undef $interim;
+	}
 	my $unit = 'seconds';
 	$amount =~ s/\s+//g;
 
@@ -2493,38 +2499,40 @@
 	    ::logError("adjust_time(): bad unit: $unit");
 	    return $time;
 	}
-    }
 
-    if ($compensate_dst) { $times[8] = -1 }
+	if ($compensate_dst) { $times[8] = -1 }
 
-    # mktime can only handle integers, so we need to convert real numbers:
-    my @multip = (0, 60, 60, 24, 0, 12);
-    my $monfrac = 0;
-    foreach my $i (reverse 0..5) {
-	if ($times[$i] =~ /\./) {
-	    if ($multip[$i]) {
-		$times[$i-1] += ($times[$i] - int $times[$i]) * $multip[$i];
-	    }
+	# mktime can only handle integers, so we need to convert real numbers:
+	my @multip = (0, 60, 60, 24, 0, 12);
+	my $monfrac = 0;
+	foreach my $i (reverse 0..5) {
+	    if ($times[$i] =~ /\./) {
+		if ($multip[$i]) {
+		    $times[$i-1] += ($times[$i] - int $times[$i]) * $multip[$i];
+		}
+
+		elsif ($i == 4) {
+		    # Fractions of a month need some really extra special handling.
+		    $monfrac = $times[$i] - int $times[$i];
+		}
 
-	    elsif ($i == 4) {
-		# Fractions of a month need some really extra special handling.
-		$monfrac = $times[$i] - int $times[$i];
+		$times[$i] = int $times[$i]
 	    }
+	}
 
-	    $times[$i] = int $times[$i]
+	$time = POSIX::mktime(@times);
+
+	# This is how we handle a fraction of a month:
+	if ($monfrac) {
+	    $times[4] += $monfrac > 0 ? 1 : -1;
+	    my $timediff = POSIX::mktime(@times);
+	    $timediff = int(abs($timediff - $time) * $monfrac);
+	    $time += $timediff;
 	}
-    }
 
-    $time = POSIX::mktime(@times);
+	$interim = $time;
 
-    # This is how we handle a fraction of a month:
-    if ($monfrac) {
-	$times[4] += $monfrac > 0 ? 1 : -1;
-	my $timediff = POSIX::mktime(@times);
-	$timediff = int(abs($timediff - $time) * $monfrac);
-	$time += $timediff;
     }
-
     return $time;
 }
 


More information about the interchange-users mailing list