php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #8828 mktime using mday<=0
Submitted: 2001-01-21 08:06 UTC Modified: 2002-06-12 11:37 UTC
From: dieter at fiebelkorn dot net Assigned:
Status: Closed Package: Date/time related
PHP Version: 4.0.4pl1 OS: MacOS X (Darwin 1.2)
Private report: No CVE-ID: None
 [2001-01-21 08:06 UTC] dieter at fiebelkorn dot net
On PHP documantation:
mktime(hour,min,sec, year,0,mon) refers the last day of 
month 'mon-1'. On MacOS X this failed, there it refers 
the first day of month 'mon' and mktime(hour,min,sec, 
year,-1,mon) refers the last day of month 'mon-1'.

See the test:

[aragorn:~/Downloads] dieter% cat A.c
#include <stdio.h>
#include <time.h>

main()
{
        struct tm mytm;
        int i;

        mytm.tm_sec = 0; mytm.tm_min = 0;
        mytm.tm_hour = 0; mytm.tm_year = 101;
        mytm.tm_wday = 0; mytm.tm_yday = 0;
        mytm.tm_isdst = 0; mytm.tm_gmtoff = 0;
        mytm.tm_zone = 0;

        for (i = 2; i >= -2; i--)
        {
                mytm.tm_mday = i;
                mytm.tm_mon = 2;
                printf ("%02d.%02d ",
                           mytm.tm_mday, mytm.tm_mon+1);
                printf (" --> %ld", mktime(&mytm));
                printf (" --> %02d.%02d\n",
                           mytm.tm_mday, mytm.tm_mon+1);
        }
}
[aragorn:~/Downloads] dieter% cc -o A A.c
[aragorn:~/Downloads] dieter% ./A
02.03  --> 983487600 --> 02.03
01.03  --> 983401200 --> 01.03
00.03  --> 983401200 --> 01.03
-1.03  --> 983314800 --> 28.02
-2.03  --> 983228400 --> 27.02
[aragorn:~/Downloads] dieter% 

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2001-01-22 22:32 UTC] jimw@php.net
macos x is behaving in a non-standard way. we just use the underlying mktime() function.

you might try reporting it to the darwin developers.

http://www.opensource.apple.com/projects/bugs.html
 [2001-01-27 07:26 UTC] dieter at fiebelkorn dot net
I have send a bug report to darwin-developers and 
apple, too, but

i have check some UNIX-systems and i haven't found 
any man-page, where is describe what should be 
happend on "tm_mday" <= 0.
IMHO this is a undocumented feature, which is used by 
PHP.

man mktime (Linux 2.2.13)
------------------------------------
       tm_mday
              The day of the month, in the range 1 to 31.

Sun Release 4.1
----------------------
       int tm_mday;     /* day of month (1 - 31) */


SunOS 5.5
---------------
       int  tm_mday;       /* day of the month - [1, 31] */

HP-UX Release 10.20
------------------------------
       int tm_mday;     /* day of month - [1,31] */


HP-UX Release 11.00
-------------------------------
       int tm_mday;     /* day of month - [1,31] */

AIX 4.3.2
------------

       int tm_mday;    /* Day of month (1 - 31) */


Darwin 1.0.2
-----------------
       int tm_mday;     /* day of month (1 - 31) */

I think i should by much better to check mktime() on 
configure and set a #define for compilation. Only on 
"datetime.c" must be made a patch to support MacOS 
X's / Darwin's mktime()-systemcall, i think!

 [2001-03-14 13:34 UTC] jason@php.net
This actually is part of the Ansi C standard.

Solaris 8 manpage -----------------------

The original values of the components may be either greater than or less than the specified range. For example, a tm_hour of -1 means 1 hour before midnight, tm_mday of 0 means the day preceding the current month, and tm_mon of -2 means 2 months before January of tm_year


---------------------------------------

Alg from c standard
----------------------------------------

#define QUOT(a,b) ((a)>0 ? (a)/(b) : -(((b)-(a)-1)/(b)))
#define REM(a,b) ((a)-(b)*QUOT(a,b))

SS = tm_hour*3600 + tm_min*60 + tm_sec +
  (tm_leapsecs == _NO_LEAP_SECONDS ? X1 :
                 tm_leapsecs) -
  (tm_zone == _LOCALTIME ? X2 : tm_zone) * 60;

// X1 is the appropriate number of leap seconds, determined by
// the implementation, or 0 if it cannot be determined. 
// X2 is the appropriate offset from local time to UTC,
// determined by the implementation, or

// (tm_isdst >= 0 ? tm_isdst : 0)
// if the offset cannot be determined

         M = REM(tm_mon, 12);
         Y = tm_year + 1900 + QUOT(tm_mon, 12);
         Z = Y - (M < 2 ? 1 : 0);
         D = Y*365 + (Z/400)*97 + (Z%400)/4 +
         M[(int []){0,31,59,90,120,151,181,212,243,273,
                               304,335}] +
        tm_mday + QUOT(SS, 86400);
               S = REM(SS, 86400);
------------------------------------------------------
Mac OS X is not following this standard

-Jason

 [2001-03-14 15:56 UTC] jason@php.net
I was incorrect, This is not a part of C99(ANSI/ISO/IEC9899-1999) this is just implemented in Solaris, Linux, Irix, and possibly a few others.

-Jason
 [2001-04-15 10:59 UTC] dieter at fiebelkorn dot net
FIRST patch to make php_mktime() more compatible [i think it is not complete for the whole PHP4-project]. It includes a time correction for values less then 1 on "mday", "hour", "min", "sec". NOT on "mon"!!


*** ext/standard/datetime.c.orig        Fri Dec  8 12:38:02 2000
--- ext/standard/datetime.c     Sun Apr 15 16:42:37 2001
***************
*** 81,87 ****
        struct tm *ta, tmbuf;
        time_t t;
        int i, gmadjust, seconds, arg_count = ZEND_NUM_ARGS();
!       int is_dst = -1;
  
        if (arg_count > 7 || zend_get_parameters_array_ex(arg_count,arguments) == FAILURE) {
                WRONG_PARAM_COUNT;
--- 81,87 ----
        struct tm *ta, tmbuf;
        time_t t;
        int i, gmadjust, seconds, arg_count = ZEND_NUM_ARGS();
!       int is_dst = -1, val, chgsecs = 0;
  
        if (arg_count > 7 || zend_get_parameters_array_ex(arg_count,arguments) == FAILURE) {
                WRONG_PARAM_COUNT;
***************
*** 148,172 ****
                          - (((*arguments[5])->value.lval > 1000) ? 1900 : 0);
                /* fall-through */
        case 5:
!               ta->tm_mday = (*arguments[4])->value.lval;
                /* fall-through */
        case 4:
                ta->tm_mon = (*arguments[3])->value.lval - 1;
                /* fall-through */
        case 3:
!               ta->tm_sec = (*arguments[2])->value.lval;
                /* fall-through */
        case 2:
!               ta->tm_min = (*arguments[1])->value.lval;
                /* fall-through */
        case 1:
!               ta->tm_hour = (*arguments[0])->value.lval;
                /* fall-through */
        case 0:
                break;
        }
  
!       seconds = mktime(ta);
        if (is_dst == -1)
                is_dst = ta->tm_isdst;
  
--- 148,180 ----
                          - (((*arguments[5])->value.lval > 1000) ? 1900 : 0);
                /* fall-through */
        case 5:
!               val = (*arguments[4])->value.lval;
!               if (val < 1) { chgsecs += (1-val) * 60*60*24; val = 1; }
!               ta->tm_mday = val;
                /* fall-through */
        case 4:
                ta->tm_mon = (*arguments[3])->value.lval - 1;
                /* fall-through */
        case 3:
!               val = (*arguments[2])->value.lval;
!               if (val < 1) { chgsecs += (1-val); val = 1; }
!               ta->tm_sec = val;
                /* fall-through */
        case 2:
!               val = (*arguments[1])->value.lval;
!               if (val < 1) { chgsecs += (1-val) * 60; val = 1; }
!               ta->tm_min = val;
                /* fall-through */
        case 1:
!               val = (*arguments[0])->value.lval;
!               if (val < 1) { chgsecs += (1-val) * 60*60; val = 1; }
!               ta->tm_hour = val;
                /* fall-through */
        case 0:
                break;
        }
  
!       seconds = mktime(ta) - chgsecs;
        if (is_dst == -1)
                is_dst = ta->tm_isdst;

 [2001-04-16 03:20 UTC] dieter at fiebelkorn dot net
Now for tm_mon < 0, too:

*** ext/standard/datetime.c.orig	Fri Dec  8 12:38:02 2000
--- ext/standard/datetime.c	Sun Apr 15 17:28:46 2001
***************
*** 81,87 ****
  	struct tm *ta, tmbuf;
  	time_t t;
  	int i, gmadjust, seconds, arg_count = ZEND_NUM_ARGS();
! 	int is_dst = -1;
  
  	if (arg_count > 7 || 
zend_get_parameters_array_ex(arg_count,arguments) == 
FAILURE) {
  		WRONG_PARAM_COUNT;
--- 81,87 ----
  	struct tm *ta, tmbuf;
  	time_t t;
  	int i, gmadjust, seconds, arg_count = ZEND_NUM_ARGS();
! 	int is_dst = -1, val, chgsecs = 0;
  
  	if (arg_count > 7 || 
zend_get_parameters_array_ex(arg_count,arguments) == 
FAILURE) {
  		WRONG_PARAM_COUNT;
***************
*** 148,172 ****
  			  - (((*arguments[5])->value.lval > 1000) ? 1900 : 0);
  		/* fall-through */
  	case 5:
! 		ta->tm_mday = (*arguments[4])->value.lval;
  		/* fall-through */
  	case 4:
! 		ta->tm_mon = (*arguments[3])->value.lval - 1;
  		/* fall-through */
  	case 3:
! 		ta->tm_sec = (*arguments[2])->value.lval;
  		/* fall-through */
  	case 2:
! 		ta->tm_min = (*arguments[1])->value.lval;
  		/* fall-through */
  	case 1:
! 		ta->tm_hour = (*arguments[0])->value.lval;
  		/* fall-through */
  	case 0:
  		break;
  	}
  
! 	seconds = mktime(ta);
  	if (is_dst == -1)
  		is_dst = ta->tm_isdst;
  
--- 148,182 ----
  			  - (((*arguments[5])->value.lval > 1000) ? 1900 : 0);
  		/* fall-through */
  	case 5:
! 		val = (*arguments[4])->value.lval;
! 		if (val < 1) { chgsecs += (1-val) * 60*60*24; val = 1; 
}
! 		ta->tm_mday = val;
  		/* fall-through */
  	case 4:
! 		val = (*arguments[3])->value.lval - 1;
! 		while (val < 0) { val += 12; ta->tm_year--; }
! 		ta->tm_mon = val;
  		/* fall-through */
  	case 3:
! 		val = (*arguments[2])->value.lval;
! 		if (val < 1) { chgsecs += (1-val); val = 1; }
! 		ta->tm_sec = val;
  		/* fall-through */
  	case 2:
! 		val = (*arguments[1])->value.lval;
! 		if (val < 1) { chgsecs += (1-val) * 60; val = 1; }
! 		ta->tm_min = val;
  		/* fall-through */
  	case 1:
! 		val = (*arguments[0])->value.lval;
! 		if (val < 1) { chgsecs += (1-val) * 60*60; val = 1; }
! 		ta->tm_hour = val;
  		/* fall-through */
  	case 0:
  		break;
  	}
  
! 	seconds = mktime(ta) - chgsecs;
  	if (is_dst == -1)
  		is_dst = ta->tm_isdst;
  

 [2001-05-07 12:54 UTC] derick@php.net
Dup of #10686
 [2002-06-12 11:37 UTC] hholzgra@php.net
fixed in latest CVS, should work on all OS/libc versions now
as negative offsets are now taken care of internaly so libc mktime() won't receive any negative (or 0 for tm_mday) values anymore (see also #10686)
 
PHP Copyright © 2001-2022 The PHP Group
All rights reserved.
Last updated: Fri Jan 28 00:03:34 2022 UTC