From netch@lucky.net Wed Nov 10 11:13:11 1999
Return-Path: <netch@lucky.net>
Received: from burka.carrier.kiev.ua (burka.carrier.kiev.ua [193.193.193.107])
	by hub.freebsd.org (Postfix) with ESMTP id 33BC214E16
	for <FreeBSD-gnats-submit@freebsd.org>; Wed, 10 Nov 1999 11:12:47 -0800 (PST)
	(envelope-from netch@lucky.net)
Received: from netch@localhost
	by burka.carrier.kiev.ua  id VEX88453;
	Wed, 10 Nov 1999 21:12:44 +0200 (EET)
	(envelope-from netch)
Message-Id: <199911101912.VEX88453@burka.carrier.kiev.ua>
Date: Wed, 10 Nov 1999 21:12:44 +0200 (EET)
From: netch@lucky.net (Valentin Nechayev)
Reply-To: netch@lucky.net
To: FreeBSD-gnats-submit@freebsd.org
Subject: Perl POSIX::strftime bugfeature
X-Send-Pr-Version: 3.2

>Number:         14813
>Category:       bin
>Synopsis:       Perl POSIX::strftime bugfeature
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    freebsd-bugs
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed Nov 10 11:20:00 PST 1999
>Closed-Date:    Sat Nov 13 12:09:37 PST 1999
>Last-Modified:  Sat Nov 13 12:10:25 PST 1999
>Originator:     Valentin Nechayev <netch@lucky.net>
>Release:        FreeBSD 3.3-RC i386
>Organization:
Lucky Net Ltd.
>Environment:

FreeBSD 3.3-RC
Perl 5.00503 in basic system

>Description:

GMTime of unixtime 941284799 is 30th of October, 1999, 11:59:59.

The following code:

#!/usr/bin/perl
use POSIX;
print strftime("%Y %m %d %H %M %S", gmtime(941284799)), "\n";

prints

1999 10 30 12 59 59

(note environment information: timezone is Europe/Kiev)

This effect (adding of 1 hour) appeared in perl 5.00503 and did not exist in
perl 5.00502.

Diff of ${perl}/ext/POSIX/POSIX.xs between 5.00502 and 5.00503
contains following:

==={
@@ -3591,7 +3603,7 @@
        RETVAL

 char *
-strftime(fmt, sec, min, hour, mday, mon, year, wday = 0, yday = 0, isdst = 0)
+strftime(fmt, sec, min, hour, mday, mon, year, wday = -1, yday = -1, isdst = -1
        char *          fmt
        int             sec
        int             min
@@ -3617,8 +3629,45 @@
            mytm.tm_wday = wday;
            mytm.tm_yday = yday;
            mytm.tm_isdst = isdst;
+           (void) mktime(&mytm);
            len = strftime(tmpbuf, sizeof tmpbuf, fmt, &mytm);
===}

Well, test it and see that mktime() normalizes time according to
local time zone (Europe/Kiev in our case):

==={
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

void f( int x )
{
   char buf[ 200 ];
   struct tm stm;
   bzero( &stm, sizeof stm );
   stm.tm_year = 99; stm.tm_mon = 9; stm.tm_mday = 30; stm.tm_hour = 11;
   stm.tm_min = 59; stm.tm_sec = 59; stm.tm_isdst = 0;
   if( x )
      mktime( &stm );
   bzero( buf, sizeof buf );
   strftime( buf, sizeof buf, "%Y %m %d %H %M %S", &stm );
   puts( buf );
   printf( "%d %d %d %d %d %d\n", stm.tm_year, stm.tm_mon, stm.tm_mday,
         stm.tm_hour, stm.tm_min, stm.tm_sec, stm.tm_isdst );
}

int main() {
   f( 0 ); f( 1 ); return 1;
}
===}

output is:

==={
netch@burka:~/prog/tiny/2>./3
1999 10 30 11 59 59
99 9 30 11 59 59 0
1999 10 30 12 59 59
99 9 30 12 59 59 1
===}

This mktime() behavior possibly correct and in any case accords to its man page:

==={
     On successful completion, the values of the tm_wday and tm_yday compo-
     nents of the structure are set appropriately, and the other components
     are set to represent the specified calendar time, but with their values
     forced to their normal ranges; the final value of tm_mday is not set un-
     til tm_mon and tm_year are determined.  Mktime() returns the specified
     calendar time; if the calendar time cannot be represented, it returns -1;
===}

But, the time in question was GMT time, not local time.

Thus, perl MUST NOT call mktime() in strftime() because strftime MUST
ONLY PRINT its data and MUST NOT have any opinion of its content because
it cannot know real time zone of the data.


>How-To-Repeat:

See above.

>Fix:
	
Disable the mktime() call in POSIX::strftime. Also disable init_tm(),
whis is really localtime(time()) - IMHO the better solution for FreeBSD
in case of tm_gmtoff & tm_zone patameters is to set them to most safe value,
i.e. 0.

(note: spaces/tabs are incorrect in this diff)
==={
--- src/contrib/perl5/ext/POSIX/POSIX.xs.orig   Wed May  5 16:15:29 1999
+++ src/contrib/perl5/ext/POSIX/POSIX.xs        Wed Nov 10 20:04:30 1999
@@ -3617,21 +3617,20 @@
     CODE:
        {
            char tmpbuf[128];
            struct tm mytm;
            int len;
-           init_tm(&mytm);     /* XXX workaround - see init_tm() above */
+           bzero(&mytm, sizeof(mytm));
            mytm.tm_sec = sec;
            mytm.tm_min = min;
            mytm.tm_hour = hour;
            mytm.tm_mday = mday;
            mytm.tm_mon = mon;
            mytm.tm_year = year;
            mytm.tm_wday = wday;
            mytm.tm_yday = yday;
            mytm.tm_isdst = isdst;
-           (void) mktime(&mytm);
            len = strftime(tmpbuf, sizeof tmpbuf, fmt, &mytm);
            /*
            ** The following is needed to handle to the situation where
            ** tmpbuf overflows.  Basically we want to allocate a buffer
            ** and try repeatedly.  The reason why it is so complicated
===}

>Release-Note:
>Audit-Trail:

From: "Andrey A. Chernov" <ache@freebsd.org>
To: Valentin Nechayev <netch@lucky.net>
Cc: FreeBSD-gnats-submit@freebsd.org
Subject: Re: bin/14813: Perl POSIX::strftime bugfeature
Date: Thu, 11 Nov 1999 15:04:23 -0800

 On Wed, Nov 10, 1999 at 09:12:44PM +0200, Valentin Nechayev wrote:
 > Disable the mktime() call in POSIX::strftime. Also disable init_tm(),
 > whis is really localtime(time()) - IMHO the better solution for FreeBSD
 > in case of tm_gmtoff & tm_zone patameters is to set them to most safe value,
 > i.e. 0.
 
 What about just replacing mktime() with timegm()? It seems to produce correct 
 results, but I not check it.
 
 -- 
 Andrey A. Chernov
 http://nagual.pp.ru/~ache/
 MTH/SH/HE S-- W-- N+ PEC>+ D A a++ C G>+ QH+(++) 666+>++ Y
 

From: Valentin Nechayev <netch@lucky.net>
To: "Andrey A. Chernov" <ache@FreeBSD.ORG>
Cc: FreeBSD-gnats-submit@FreeBSD.ORG
Subject: Re: bin/14813: Perl POSIX::strftime bugfeature
Date: Fri, 12 Nov 1999 08:12:03 +0200

 Hello Andrey A. Chernov! 
 
  Thu, Nov 11, 1999 at 15:04:23, ache wrote about "Re: bin/14813: Perl POSIX::strftime bugfeature": 
 
 > On Wed, Nov 10, 1999 at 09:12:44PM +0200, Valentin Nechayev wrote:
 > > Disable the mktime() call in POSIX::strftime. Also disable init_tm(),
 > > whis is really localtime(time()) - IMHO the better solution for FreeBSD
 > > in case of tm_gmtoff & tm_zone patameters is to set them to most safe value,
 > > i.e. 0.
 > 
 > What about just replacing mktime() with timegm()? It seems to produce correct 
 > results, but I not check it.
 
 No, it is bad also. In that variant, it will print correctly GMT time,
 but incorrectly local time, possibly ;( (Consider variant, where timegm()
 normalizes time with tm_isdst==1. What shall happen? If you know current
 timegm() behavior, it can change.) Also, timegm() AFAIR exists not in all
 unices.
 
 IMHO, the only normal variant is to disable this call. There is another,
 ideological basis of disabling: every subroutine must do its own work and
 must produce minimum of side effects. Goal of strftime() is to print time
 and date according to given format, it must not normalize or convert time.
 If I ask it to print 31th of February, it must print this date literally.
 If I want normalize data, I can do it by special normalizing call, i.e.,
 mktime() or timegm() accordingly to my knowledge of this date origin.
 
 --
 NVA
 
State-Changed-From-To: open->closed 
State-Changed-By: ache 
State-Changed-When: Sat Nov 13 12:09:37 PST 1999 
State-Changed-Why:  
Sligtly different fix applied 
>Unformatted:
