Subject: lib/5161: strptime(3) gets %j, %m, and %S conversions off by one and breaks %C
To: None <gnats-bugs@gnats.netbsd.org>
From: None <woods@most.weird.com>
List: netbsd-bugs
Date: 03/14/1998 22:06:19
>Number:         5161
>Category:       lib
>Synopsis:       strptime(3) gets %j, %m, and %S conversions off by one and breaks %C
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    lib-bug-people (Library Bug People)
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sat Mar 14 19:20:00 1998
>Last-Modified:
>Originator:     Greg A. Woods
>Organization:
Planix, Inc.; Toronto, Ontario; Canada
>Release:        NetBSD-1998/03/03 (Tue Mar  3 07:52:24 EST 1998)
>Environment:

System: NetBSD most 1.3 NetBSD 1.3 (GENERIC_SCSI3) #0: Thu Jan 1 19:03:39 MET 1998 pk@flambard:/usr/src1/sys/arch/sparc/compile/GENERIC_SCSI3 sparc

>Description:

	The code in strptime(3) does not take into account the valid
	ranges for various fields in "struct tm", in particular tm_sec,
	tm_mon, and tm_yday when doing the conversions for '%S', '%m',
	and '%j' repsectively.

	The '%C' obliterates the value of tm_year from '%y' with the
	value of only the century, and conversely '%y' obliterates the
	value from '%C'.  Since these conversions are only valid in
	conjunction with each other their effect must be additive.  I
	have no idea what XPG-4 has to say about this, but if it doesn't
	agree with me then it's clearly broken!  ;-)

	Unfortunately the regression test for strptime(3) is also
	incorrect and incomplete.

	Lastly there are some minor errors in the manual page, and some
	silly "KNF"-like fixes that should be done.

>How-To-Repeat:

	Convert various dates from ASCII through struct tm and back to
	ASCII again using strptime(3) and strftime(3) and observe the
	differences.

>Fix:

	Apply the following patches.  Note that I've corrected the
	regression test, but have not made it more complete.  (The
	second and third hunks of the man-page diff contain the critical
	updates.)

Index: lib/libc/time/strptime.c
===================================================================
RCS file: /cvs/NetBSD/src/lib/libc/time/strptime.c,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 strptime.c
*** strptime.c	1998/02/20 00:32:20	1.1.1.1
--- strptime.c	1998/03/15 02:41:32
***************
*** 216,222 ****
  			if (!(_conv_num(&bp, &i, 0, 99)))
  				return (0);
  
! 			tm->tm_year = i * 100;
  			break;
  
  		case 'd':	/* The day of month. */
--- 216,222 ----
  			if (!(_conv_num(&bp, &i, 0, 99)))
  				return (0);
  
! 			tm->tm_year += i * 100;
  			break;
  
  		case 'd':	/* The day of month. */
***************
*** 246,253 ****
  
  		case 'j':	/* The day of year. */
  			_LEGAL_ALT(0);
! 			if (!(_conv_num(&bp, &tm->tm_yday, 1, 366)))
  				return (0);
  			break;
  
  		case 'M':	/* The minute. */
--- 246,254 ----
  
  		case 'j':	/* The day of year. */
  			_LEGAL_ALT(0);
! 			if (!(_conv_num(&bp, &i, 1, 366)))
  				return (0);
+ 			tm->tm_yday = i - 1;
  			break;
  
  		case 'M':	/* The minute. */
***************
*** 258,265 ****
  
  		case 'm':	/* The month. */
  			_LEGAL_ALT(_ALT_O);
! 			if (!(_conv_num(&bp, &tm->tm_mon, 1, 12)))
  				return (0);
  			break;
  
  		case 'p':	/* The locale's equivalent of AM/PM. */
--- 259,267 ----
  
  		case 'm':	/* The month. */
  			_LEGAL_ALT(_ALT_O);
! 			if (!(_conv_num(&bp, &i, 1, 12)))
  				return (0);
+ 			tm->tm_mon = i - 1;
  			break;
  
  		case 'p':	/* The locale's equivalent of AM/PM. */
***************
*** 290,297 ****
  
  		case 'S':	/* The seconds. */
  			_LEGAL_ALT(_ALT_O);
! 			if (!(_conv_num(&bp, &tm->tm_sec, 1, 61)))
  				return (0);
  			break;
  
  		case 'U':	/* The week of year, beginning on sunday. */
--- 292,300 ----
  
  		case 'S':	/* The seconds. */
  			_LEGAL_ALT(_ALT_O);
! 			if (!(_conv_num(&bp, &i, 1, 61)))
  				return (0);
+ 			tm->tm_sec = i - 1;
  			break;
  
  		case 'U':	/* The week of year, beginning on sunday. */
***************
*** 327,335 ****
  				return (0);
  
  			if (i <= 68)
! 				tm->tm_year = i + 2000 - TM_YEAR_BASE;
  			else
! 				tm->tm_year = i + 1900 - TM_YEAR_BASE;
  			break;
  
  		/*
--- 330,338 ----
  				return (0);
  
  			if (i <= 68)
! 				tm->tm_year += i + 2000 - TM_YEAR_BASE;
  			else
! 				tm->tm_year += i + 1900 - TM_YEAR_BASE;
  			break;
  
  		/*
Index: lib/libc/time/strptime.3
===================================================================
RCS file: /cvs/NetBSD/src/lib/libc/time/strptime.3,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 strptime.3
*** strptime.3	1998/02/20 00:32:20	1.1.1.1
--- strptime.3	1998/03/15 03:00:41
***************
*** 57,71 ****
  .Pp
  The
  .Fa format
! string consists of zero or more conversion specifications and
! ordinary characters.  All ordinary characters are copied directly into
! the buffer.  A conversion specification consists of a percent sign `%'
! followed by one or two conversion characters which specify the replacement
! required. There must be white-space or other non-alphanumeric characters
! between any two conversion specifications.
  .Pp
  The LC_TIME category defines the locale values for the conversion
! specifications. The following conversion specifications are supported:
  .Bl -tag -width "xxxx"
  .It Cm \&%a
  the day of week, using the locale's weekday names;
--- 57,71 ----
  .Pp
  The
  .Fa format
! string consists of zero or more conversion specifications and ordinary
! characters.  All ordinary characters are copied directly into the
! buffer.  A conversion specification consists of a percent sign `%'
! followed by one or two conversion characters which specify the
! replacement required.  There must be white-space or other
! non-alphanumeric characters between any two conversion specifications.
  .Pp
  The LC_TIME category defines the locale values for the conversion
! specifications.  The following conversion specifications are supported:
  .Bl -tag -width "xxxx"
  .It Cm \&%a
  the day of week, using the locale's weekday names;
***************
*** 82,92 ****
  .It Cm \&%c
  the date and time, using the locale's date and time format.
  .It Cm \&%C
! the century number [0,99];
! leading zeros are permitted but not required.
  .It Cm \&%d
  the day of month [1,31];
! leading zeros are permitted but required.
  .It Cm \&%D
  the date as %m/%d/%y.
  .It Cm \&%e
--- 82,93 ----
  .It Cm \&%c
  the date and time, using the locale's date and time format.
  .It Cm \&%C
! the century number [0,99]; leading zeros are permitted but not required.
! Note that the converted value is added to the current value of the
! ``tm_year'' field (in order that the "\&%y" conversion be useful).
  .It Cm \&%d
  the day of month [1,31];
! leading zeros are permitted but not required.
  .It Cm \&%D
  the date as %m/%d/%y.
  .It Cm \&%e
***************
*** 150,160 ****
  the time, using the locale's time format.
  .It Cm \&%y
  the year within the 20th century [69,99] or the 21st century [0,68];
! leading zeros are permitted but not required.
  .It Cm \&%Y
  the year, including the century (i.e., 1996).
  .It Cm \&%%
! A `%' is written. No argument is converted.
  .El
  .Ss Modified conversion specifications
  For compatibility, certain conversion specifications can be modified
--- 151,163 ----
  the time, using the locale's time format.
  .It Cm \&%y
  the year within the 20th century [69,99] or the 21st century [0,68];
! leading zeros are permitted but not required.  Note that the converted
! value is added to the current value of the ``tm_year'' field (in order
! that the "\&%C" conversion be useful).
  .It Cm \&%Y
  the year, including the century (i.e., 1996).
  .It Cm \&%%
! A `%' is written.  No argument is converted.
  .El
  .Ss Modified conversion specifications
  For compatibility, certain conversion specifications can be modified
***************
*** 162,177 ****
  .Cm E
  and
  .Cm O
! modifier characters to indicate that an alternative format or specification
! should be used rather than the one normally used by the unmodified
! conversion specification. As there are currently neither alternative formats
! nor specifications supported by the system, the behavior will be as if the
! unmodified conversion specification were used.
  .Sh RETURN VALUES
  If successful, the
  .Nm
  function returns a pointer to the character following the last character
! parsed. Otherwise, a null pointer is returned.
  .Sh SEE ALSO
  .Xr strftime 3
  .Sh STANDARDS
--- 165,181 ----
  .Cm E
  and
  .Cm O
! modifier characters to indicate that an alternative format or
! specification should be used rather than the one normally used by the
! unmodified conversion specification.  As there are currently neither
! alternative formats nor specifications supported by the system, the
! behavior will be as if the unmodified conversion specification were
! used.
  .Sh RETURN VALUES
  If successful, the
  .Nm
  function returns a pointer to the character following the last character
! parsed.  Otherwise, a null pointer is returned.
  .Sh SEE ALSO
  .Xr strftime 3
  .Sh STANDARDS
Index: regress/lib/libc/time/strptime/expected
===================================================================
RCS file: /cvs/NetBSD/src/regress/lib/libc/time/strptime/expected,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 expected
*** expected	1998/02/20 00:33:23	1.1.1.1
--- expected	1998/03/15 00:09:11
***************
*** 1,14 ****
  --- ctime --- 1
  succeeded
! 46 27 23 20 0 98 2 -1
  
  --- ctime --- 2
  succeeded
! 46 27 23 20 0 98 2 -1
  
  --- ctime --- 3
  succeeded
! 46 27 23 20 0 98 2 -1
  
  --- %a --- 1
  succeeded
--- 1,14 ----
  --- ctime --- 1
  succeeded
! 45 27 23 20 0 98 2 -1
  
  --- ctime --- 2
  succeeded
! 45 27 23 20 0 98 2 -1
  
  --- ctime --- 3
  succeeded
! 45 27 23 20 0 98 2 -1
  
  --- %a --- 1
  succeeded
>Audit-Trail:
>Unformatted: