This is the mail archive of the newlib@sources.redhat.com mailing list for the newlib project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

strftime improvements


Following up to my post last month about broken strftime(3), here is a
patch that implements all the remaining missing functionality required by
POSIX, as well as improving the documentation.

Note that there are still some useful non-POSIX extensions out there that
might be worth implementing.  In glibc, strftime(NULL, (size_t)UINT_MAX,
format, &tm) returns the number of characters that would be placed into a
non-NULL string, great for allocating a string to have exact length.  And
in Solaris, strftime(s, len, NULL, &tm) uses "%c" as the default format.

Also, something I did not know how to fix - POSIX requires that <time.h>
include extern int daylight, extern long timezone, and extern char
*tzname.  But as currently written, <time.h> has daylight and tzname
exposed only under __CYGWIN__, and timezone is a function instead of a
long unless timezonevar is defined.

2005-02-14  Eric Blake  <ebb9@byu.net>

	* libc/time/time.tex: Improve the documentation.
	* libc/time/strftime.c: Improve the documentation.
	(iso_year_adjust): New helper function.
	(strftime): Simplify '%E' and '%O'. Change '%c' to use
	recursion. Fix '%C', '%y', and '%Y' to deal with years with more
	than 4 characters.  Combine '%d' and '%e'. Implement '%D', '%F',
	'%g', '%G', '%n', '%R', '%t', '%T', '%u', '%V', '%X', and '%z'.
	Avoid core dumps on valid inputs (maxsize == 0, or
	tim_p->tm_isdst > 1).

-- 
Life is short - so eat dessert first!

Eric Blake             ebb9@byu.net
Index: strftime.c
===================================================================
RCS file: /cvs/src/src/newlib/libc/time/strftime.c,v
retrieving revision 1.6
diff -u -p -r1.6 strftime.c
--- strftime.c	20 Jan 2005 19:56:27 -0000	1.6
+++ strftime.c	14 Feb 2005 13:46:50 -0000
@@ -32,96 +32,195 @@ TRAD_SYNOPSIS
 
 DESCRIPTION
 <<strftime>> converts a <<struct tm>> representation of the time (at
-<[timp]>) into a string, starting at <[s]> and occupying no more than
-<[maxsize]> characters.
+<[timp]>) into a null-terminated string, starting at <[s]> and occupying
+no more than <[maxsize]> characters.
 
 You control the format of the output using the string at <[format]>.
 <<*<[format]>>> can contain two kinds of specifications: text to be
 copied literally into the formatted string, and time conversion
-specifications.  Time conversion specifications are two-character
-sequences beginning with `<<%>>' (use `<<%%>>' to include a percent
-sign in the output).  Each defined conversion specification selects a
-field of calendar time data from <<*<[timp]>>>, and converts it to a
-string in one of the following ways:
+specifications.  Time conversion specifications are two- and
+three-character sequences beginning with `<<%>>' (use `<<%%>>' to
+include a percent sign in the output).  Each defined conversion
+specification selects only the specified field(s) of calendar time
+data from <<*<[timp]>>>, and converts it to a string in one of the
+following ways:
 
 o+
 o %a
-An abbreviation for the day of the week.
+A three-letter abbreviation for the day of the week. [tm_wday]
 
 o %A
-The full name for the day of the week.
+The full name for the day of the week, one of `<<Sunday>>',
+`<<Monday>>', `<<Tuesday>>', `<<Wednesday>>', `<<Thursday>>',
+`<<Friday>>', or `<<Saturday>>'. [tm_wday]
 
 o %b
-An abbreviation for the month name.
+A three-letter abbreviation for the month name. [tm_mon]
 
 o %B
-The full name of the month.
+The full name of the month, one of `<<January>>', `<<February>>',
+`<<March>>', `<<April>>', `<<May>>', `<<June>>', `<<July>>',
+`<<August>>', `<<September>>', `<<October>>', `<<November>>',
+`<<December>>'. [tm_mon]
 
 o %c
 A string representing the complete date and time, in the form
-. Mon Apr 01 13:13:13 1992
+`<<"%a %b %e %H:%M:%S %Y">>' (example "Mon Apr 01 13:13:13
+1992"). [tm_sec, tm_min, tm_hour, tm_mday, tm_mon, tm_year, tm_wday]
 
+o %C
+The century, that is, the year divided by 100 then truncated.  For
+4-digit years, the result is zero-padded and exactly two characters;
+but for other years, there may a negative sign or more digits.  In
+this way, `<<%C%y>>' is equivalent to `<<%Y>>'. [tm_year]
+ 
 o %d
-The day of the month, formatted with two digits.
+The day of the month, formatted with two digits (from `<<01>>' to
+`<<31>>'). [tm_mday]
+
+o %D
+A string representing the date, in the form `<<"%m/%d/%y">>'.
+[tm_mday, tm_mon, tm_year]
 
 o %e
-The day of the month, formatted with leading space if single digit.
+The day of the month, formatted with leading space if single digit
+(from `<< 1>>' to `<<31>>'). [tm_mday]
+
+o %E<<x>>
+In some locales, the E modifier selects alternative representations of
+certain modifiers <<x>>.  But in the "C" locale supported by newlib,
+it is ignored, and treated as %<<x>>.
+
+o %F
+A string representing the ISO 8601:2000 date format, in the form
+`<<"%Y-%m-%d">>'. [tm_mday, tm_mon, tm_year]
+
+o %g
+The last two digits of the week-based year, see specifier %G (from
+`<<00>>' to `<<99>>'). [tm_year, tm_wday, tm_yday]
+
+o %G
+The week-based year. In the ISO 8601:2000 calendar, week 1 of the year
+includes January 4th, and begin on Mondays. Therefore, if January 1st,
+2nd, or 3rd falls on a Sunday, that day and earlier belong to week 53
+of the previous year; and if December 29th, 30th, or 31st falls on
+Monday, that day and later belong to week 1 of the next year.  For
+consistency with %Y, it always has at least four characters. 
+Example: "%G" for Saturday 2nd January 1999 gives "1998", and for
+Tuesday 30th December 1997 gives "1998". [tm_year, tm_wday, tm_yday]
+
+o %h
+A three-letter abbreviation for the month name (synonym for
+"%b"). [tm_mon]
 
 o %H
-The hour (on a 24-hour clock), formatted with two digits.
+The hour (on a 24-hour clock), formatted with two digits (from
+`<<00>>' to `<<23>>'). [tm_hour]
 
 o %I
-The hour (on a 12-hour clock), formatted with two digits.
+The hour (on a 12-hour clock), formatted with two digits (from
+`<<01>>' to `<<12>>'). [tm_hour]
 
 o %j
 The count of days in the year, formatted with three digits
-(from `<<001>>' to `<<366>>').
+(from `<<001>>' to `<<366>>'). [tm_yday]
+
+o %k
+The hour (on a 24-hour clock), formatted with leading space if single
+digit (from `<< 0>>' to `<<23>>'). Non-POSIX extension. [tm_hour]
+
+o %l
+The hour (on a 12-hour clock), formatted with leading space if single
+digit (from `<< 1>>' to `<<12>>'). Non-POSIX extension. [tm_hour]
 
 o %m
-The month number, formatted with two digits.
+The month number, formatted with two digits (from `<<01>>' to `<<12>>').
+[tm_mon]
 
 o %M
-The minute, formatted with two digits.
+The minute, formatted with two digits (from `<<00>>' to `<<59>>'). [tm_min]
+
+o %n
+A newline character (`<<\n>>').
+
+o %O<<x>>
+In some locales, the O modifier selects alternative digit characters
+for certain modifiers <<x>>.  But in the "C" locale supported by newlib, it
+is ignored, and treated as %<<x>>.
 
 o %p
-Either `<<AM>>' or `<<PM>>' as appropriate.
+Either `<<AM>>' or `<<PM>>' as appropriate. [tm_hour]
 
 o %r
-Equivalent to "%I:%M:%S %p".
+The 12-hour time, to the second.  Equivalent to "%I:%M:%S %p". [tm_sec,
+tm_min, tm_hour]
+
+o %R
+The 24-hour time, to the minute.  Equivalent to "%H:%M". [tm_min, tm_hour]
 
 o %S
-The second, formatted with two digits.
+The second, formatted with two digits (from `<<00>>' to `<<60>>').  The
+value 60 accounts for the occasional leap second. [tm_sec]
+
+o %t
+A tab character (`<<\t>>').
+
+o %T
+The 24-hour time, to the second.  Equivalent to "%H:%M:%S". [tm_sec,
+tm_min, tm_hour]
+
+o %u
+The weekday as a number, 1-based from Monday (from `<<1>>' to
+`<<7>>'). [tm_wday]
 
 o %U
-The week number, formatted with two digits (from `<<00>>' to `<<53>>';
-week number 1 is taken as beginning with the first Sunday in a year).
-See also <<%W>>.
+The week number, where weeks start on Sunday, week 1 contains the first
+Sunday in a year, and earlier days are in week 0.  Formatted with two
+digits (from `<<00>>' to `<<53>>').  See also <<%W>>. [tm_wday, tm_yday]
+
+o %V
+The week number, where weeks start on Monday, week 1 contains January 4th,
+and earlier days are in the previous year.  Formatted with two digits
+(from `<<01>>' to `<<53>>').  See also <<%G>>. [tm_year, tm_wday, tm_yday]
 
 o %w
-A single digit representing the day of the week: Sunday is day <<0>>.
+The weekday as a number, 0-based from Sunday (from `<<0>>' to `<<6>>').
+[tm_wday]
 
 o %W
-Another version of the week number: like `<<%U>>', but counting week 1
-as beginning with the first Monday in a year.
+The week number, where weeks start on Monday, week 1 contains the first
+Monday in a year, and earlier days are in week 0.  Formatted with two
+digits (from `<<00>>' to `<<53>>'). [tm_wday, tm_yday]
 
-o
 o %x
 A string representing the complete date, equivalent to "%m/%d/%y".
+[tm_mon, tm_mday, tm_year]
 
 o %X
 A string representing the full time of day (hours, minutes, and
-seconds), equivalent to "%T".
+seconds), equivalent to "%H:%M:%S". [tm_sec, tm_min, tm_hour]
 
 o %y
-The last two digits of the year.
+The last two digits of the year (from `<<00>>' to `<<99>>'). [tm_year]
 
 o %Y
-The full year, formatted with four digits to include the century.
+The full year, equivalent to <<%C%y>>.  It will always have at least four
+characters, but may have more.  The year is accurate even when tm_year
+added to the offset of 1900 overflows an int. [tm_year]
+
+o %z
+The offset from UTC.  The format consists of a sign (negative is west of
+Greewich), two characters for hour, then two characters for minutes
+(-hhmm or +hhmm).  If tm_isdst is negative, the offset is unknown and no
+output is generated; if it is zero, the offset is the standard offset for
+the current time zone; and if it is positive, the offset is the daylight
+savings offset for the current timezone. The offset is determined from
+the TZ environment variable, as if by calling tzset(). [tm_isdst]
 
 o %Z
-The time zone name.  If tm_isdst is -1, no output is generated.
-Otherwise, the time zone name based on the TZ environment variable
-is used.
+The time zone name.  If tm_isdst is negative, no output is generated.
+Otherwise, the time zone name is based on the TZ environment variable,
+as if by calling tzset(). [tm_isdst]
 
 o %%
 A single character, `<<%>>'.
@@ -138,7 +237,12 @@ parts of <<*<[format]>>> that could be c
 PORTABILITY
 ANSI C requires <<strftime>>, but does not specify the contents of
 <<*<[s]>>> when the formatted string would require more than
-<[maxsize]> characters.
+<[maxsize]> characters.  Unrecognized specifiers and fields of
+<<timp>> that are out of range cause undefined results.  Since some
+formats expand to 0 bytes, it is wise to set <<*<[s]>>> to a nonzero
+value beforehand to distinguish between failure and an empty string.
+This implementation does not support <<s>> being NULL, nor overlapping
+<<s>> and <<format>>.
 
 <<strftime>> requires no supporting OS subroutines.
 */
@@ -164,6 +268,53 @@ static _CONST char *_CONST mname[12] =
  "May", "June", "July", "August", "September", "October", "November",
  "December"};
 
+/* Using the tm_year, tm_wday, and tm_yday components of TIM_P, return
+   -1, 0, or 1 as the adjustment to add to the year for the ISO week
+   numbering used in "%g%G%V", avoiding overflow.  */
+static int
+_DEFUN (iso_year_adjust, (tim_p),
+	_CONST struct tm *tim_p)
+{
+  /* Account for fact that tm_year==0 is year 1900.  */
+  int leap = isleap (tim_p->tm_year + (YEAR_BASE
+				       - (tim_p->tm_year < 0 ? 0 : 2000)));
+
+  /* Pack the yday, wday, and leap year into a single int since there are so
+     many disparate cases.  */
+#define PACK(yd, wd, lp) (((yd) << 4) + (wd << 1) + (lp))
+  switch (PACK (tim_p->tm_yday, tim_p->tm_wday, leap))
+    {
+    case PACK (0, 5, 0): /* Jan 1 is Fri, not leap.  */
+    case PACK (0, 6, 0): /* Jan 1 is Sat, not leap.  */
+    case PACK (0, 0, 0): /* Jan 1 is Sun, not leap.  */
+    case PACK (0, 5, 1): /* Jan 1 is Fri, leap year.  */
+    case PACK (0, 6, 1): /* Jan 1 is Sat, leap year.  */
+    case PACK (0, 0, 1): /* Jan 1 is Sun, leap year.  */
+    case PACK (1, 6, 0): /* Jan 2 is Sat, not leap.  */
+    case PACK (1, 0, 0): /* Jan 2 is Sun, not leap.  */
+    case PACK (1, 6, 1): /* Jan 2 is Sat, leap year.  */
+    case PACK (1, 0, 1): /* Jan 2 is Sun, leap year.  */
+    case PACK (2, 0, 0): /* Jan 3 is Sun, not leap.  */
+    case PACK (2, 0, 1): /* Jan 3 is Sun, leap year.  */
+      return -1; /* Belongs to last week of previous year.  */
+    case PACK (362, 1, 0): /* Dec 29 is Mon, not leap.  */
+    case PACK (363, 1, 1): /* Dec 29 is Mon, leap year.  */
+    case PACK (363, 1, 0): /* Dec 30 is Mon, not leap.  */
+    case PACK (363, 2, 0): /* Dec 30 is Tue, not leap.  */
+    case PACK (364, 1, 1): /* Dec 30 is Mon, leap year.  */
+    case PACK (364, 2, 1): /* Dec 30 is Tue, leap year.  */
+    case PACK (364, 1, 0): /* Dec 31 is Mon, not leap.  */
+    case PACK (364, 2, 0): /* Dec 31 is Tue, not leap.  */
+    case PACK (364, 3, 0): /* Dec 31 is Wed, not leap.  */
+    case PACK (365, 1, 1): /* Dec 31 is Mon, leap year.  */
+    case PACK (365, 2, 1): /* Dec 31 is Tue, leap year.  */
+    case PACK (365, 3, 1): /* Dec 31 is Wed, leap year.  */
+      return 1; /* Belongs to first week of next year.  */
+    }
+  return 0; /* Belongs to specified year.  */
+#undef PACK
+}
+
 size_t
 _DEFUN (strftime, (s, maxsize, format, tim_p),
 	char *s _AND
@@ -188,8 +339,8 @@ _DEFUN (strftime, (s, maxsize, format, t
 	break;
 
       format++;
-
-check_format:
+      if (*format == 'E' || *format == 'O')
+	format++;
 
       switch (*format)
 	{
@@ -235,68 +386,140 @@ check_format:
 	    }
 	  break;
 	case 'c':
-	  if (count < maxsize - 24)
-	    {
-	      for (i = 0; i < 3; i++)
-		s[count++] =
-		  dname[tim_p->tm_wday][i];
-	      s[count++] = ' ';
-	      for (i = 0; i < 3; i++)
-		s[count++] =
-		  mname[tim_p->tm_mon][i];
-
-	      sprintf (&s[count],
-		       " %.2d %2.2d:%2.2d:%2.2d %.4d",
-		       tim_p->tm_mday, tim_p->tm_hour,
-		       tim_p->tm_min,
-		       tim_p->tm_sec, 1900 +
-		       tim_p->tm_year);
-	      count += 17;
-	    }
-	  else
-	    return 0;
+	  {
+	    /* Length is not known because of %C%y, so recurse. */
+	    size_t adjust = strftime (&s[count], maxsize - count,
+				      "%a %b %e %H:%M:%S %C%y", tim_p);
+	    if (adjust > 0)
+	      count += adjust;
+	    else
+	      return 0;
+	  }
+	  break;
+	case 'C':
+	  {
+	    /* Examples of (tm_year + YEAR_BASE) that show how %Y == %C%y
+	       with 32-bit int.
+	       %Y		%C		%y
+	       2147485547	21474855	47
+	       10000		100		00
+	       9999		99		99
+	       0999		09		99
+	       0099		00		99
+	       0001		00		01
+	       0000		00		00
+	       -001		-0		01
+	       -099		-0		99
+	       -999		-9		99
+	       -1000		-10		00
+	       -10000		-100		00
+	       -2147481748	-21474817	48
+
+	       Be careful of both overflow and sign adjustment due to the
+	       asymmetric range of years.
+	    */
+	    int neg = tim_p->tm_year < -YEAR_BASE;
+	    int century = tim_p->tm_year >= 0
+	      ? tim_p->tm_year / 100 + YEAR_BASE / 100
+	      : abs (tim_p->tm_year + YEAR_BASE) / 100;
+            count += snprintf (&s[count], maxsize - count, "%s%.*d",
+                               neg ? "-" : "", 2 - neg, century);
+            if (count >= maxsize)
+              return 0;
+	  }
 	  break;
 	case 'd':
+	case 'e':
 	  if (count < maxsize - 2)
 	    {
-	      sprintf (&s[count], "%.2d",
+	      sprintf (&s[count], *format == 'd' ? "%.2d" : "%2d",
 		       tim_p->tm_mday);
 	      count += 2;
 	    }
 	  else
 	    return 0;
 	  break;
-	case 'E':
-	  ++format;
-	  switch (*format)
-            {
-              case 'c':
-              case 'C':
-              case 'x':
-              case 'X':
-              case 'y':
-              case 'Y':
-	        /* Ignore.  */
-	        goto check_format;
-	      default:
-		--format;
-            }
+	case 'D':
+	case 'x':
+	  /* %m/%d/%y */
+	  if (count < maxsize - 8)
+	    {
+	      sprintf (&s[count], "%.2d/%.2d/%.2d",
+		       tim_p->tm_mon + 1, tim_p->tm_mday,
+		       (tim_p->tm_year % 100 + 100) % 100);
+	      count += 8;
+	    }
+	  else
+	    return 0;
+	  break;
+        case 'F':
+	  {
+	    /* Length is not known because of %C%y, so recurse. */
+	    size_t adjust = strftime (&s[count], maxsize - count,
+				      "%C%y-%m-%d", tim_p);
+	    if (adjust > 0)
+	      count += adjust;
+	    else
+	      return 0;
+	  }
           break;
-	case 'e':
+        case 'g':
 	  if (count < maxsize - 2)
 	    {
-	      sprintf (&s[count], "%2d",
-		       tim_p->tm_mday);
+	      /* Be careful of both overflow and negative years, thanks to
+		 the asymmetric range of years.  */
+	      int adjust = iso_year_adjust (tim_p);
+	      int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100
+		: abs (tim_p->tm_year + YEAR_BASE) % 100;
+	      if (adjust < 0 && tim_p->tm_year <= -YEAR_BASE)
+		adjust = 1;
+	      else if (adjust > 0 && tim_p->tm_year < -YEAR_BASE)
+		adjust = -1;
+	      sprintf (&s[count], "%.2d",
+		       ((year + adjust) % 100 + 100) % 100);
 	      count += 2;
 	    }
 	  else
 	    return 0;
-	  break;
+          break;
+        case 'G':
+	  {
+	    /* See the comments for 'C' and 'Y'; this is a variable length
+	       field.  Although there is no requirement for a minimum number
+	       of digits, we use 4 for consistency with 'Y'.  */
+	    int neg = tim_p->tm_year < -YEAR_BASE;
+	    int adjust = iso_year_adjust (tim_p);
+	    int century = tim_p->tm_year >= 0
+	      ? tim_p->tm_year / 100 + YEAR_BASE / 100
+	      : abs (tim_p->tm_year + YEAR_BASE) / 100;
+	    int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100
+	      : abs (tim_p->tm_year + YEAR_BASE) % 100;
+	    if (adjust < 0 && tim_p->tm_year <= -YEAR_BASE)
+	      neg = adjust = 1;
+	    else if (adjust > 0 && neg)
+	      adjust = -1;
+	    year += adjust;
+	    if (year == -1)
+	      {
+		year = 99;
+		--century;
+	      }
+	    else if (year == 100)
+	      {
+		year = 0;
+		++century;
+	      }
+            count += snprintf (&s[count], maxsize - count, "%s%.*d%.2d",
+                               neg ? "-" : "", 2 - neg, century, year);
+            if (count >= maxsize)
+              return 0;
+	  }
+          break;
 	case 'H':
 	case 'k':
 	  if (count < maxsize - 2)
 	    {
-	      sprintf (&s[count], *format == 'k' ? "%2d" : "%2.2d",
+	      sprintf (&s[count], *format == 'k' ? "%2d" : "%.2d",
 		       tim_p->tm_hour);
 	      count += 2;
 	    }
@@ -315,7 +538,7 @@ check_format:
 		}
 	      else
 		{
-		  sprintf (&s[count], (*format == 'I') ? "%.2d" : "%2d",
+		  sprintf (&s[count], *format == 'I' ? "%.2d" : "%2d",
 			   tim_p->tm_hour % 12);
 		  count += 2;
 		}
@@ -346,36 +569,19 @@ check_format:
 	case 'M':
 	  if (count < maxsize - 2)
 	    {
-	      sprintf (&s[count], "%2.2d",
+	      sprintf (&s[count], "%.2d",
 		       tim_p->tm_min);
 	      count += 2;
 	    }
 	  else
 	    return 0;
 	  break;
-	case 'O':
-	  ++format;
-	  switch (*format)
-            {
-              case 'd':
-              case 'e':
-              case 'H':
-              case 'I':
-              case 'm':
-              case 'M':
-              case 'S':
-              case 'u':
-              case 'U':
-              case 'V':
-              case 'w':
-              case 'W':
-              case 'y':
-	        /* Ignore.  */
-	        goto check_format;
-	      default:
-		--format;
-            }
-          break;
+	case 'n':
+	  if (count < maxsize - 1)
+	    s[count++] = '\n';
+	  else
+	    return 0;
+	  break;
 	case 'p':
 	  if (count < maxsize - 2)
 	    {
@@ -404,11 +610,11 @@ check_format:
 		  count += 2;
 		}
 	      s[count++] = ':';
-	      sprintf (&s[count], "%2.2d",
+	      sprintf (&s[count], "%.2d",
 		       tim_p->tm_min);
 	      count += 2;
 	      s[count++] = ':';
-	      sprintf (&s[count], "%2.2d",
+	      sprintf (&s[count], "%.2d",
 		       tim_p->tm_sec);
 	      count += 2;
 	      s[count++] = ' ';
@@ -422,20 +628,57 @@ check_format:
 	  else
 	    return 0;
 	  break;
+        case 'R':
+          if (count < maxsize - 5)
+            {
+              sprintf (&s[count], "%.2d:%.2d", tim_p->tm_hour, tim_p->tm_min);
+              count += 5;
+            }
+          else
+            return 0;
+          break;
 	case 'S':
 	  if (count < maxsize - 2)
 	    {
-	      sprintf (&s[count], "%2.2d",
+	      sprintf (&s[count], "%.2d",
 		       tim_p->tm_sec);
 	      count += 2;
 	    }
 	  else
 	    return 0;
 	  break;
+	case 't':
+	  if (count < maxsize - 1)
+	    s[count++] = '\t';
+	  else
+	    return 0;
+	  break;
+        case 'T':
+        case 'X':
+          if (count < maxsize - 8)
+            {
+              sprintf (&s[count], "%.2d:%.2d:%.2d", tim_p->tm_hour,
+                       tim_p->tm_min, tim_p->tm_sec);
+              count += 8;
+            }
+          else
+            return 0;
+          break;
+        case 'u':
+          if (count < maxsize - 1)
+            {
+              if (tim_p->tm_wday == 0)
+                s[count++] = '7';
+              else
+                s[count++] = '0' + tim_p->tm_wday;
+            }
+          else
+            return 0;
+          break;
 	case 'U':
 	  if (count < maxsize - 2)
 	    {
-	      sprintf (&s[count], "%2.2d",
+	      sprintf (&s[count], "%.2d",
 		       (tim_p->tm_yday + 7 -
 			tim_p->tm_wday) / 7);
 	      count += 2;
@@ -443,57 +686,42 @@ check_format:
 	  else
 	    return 0;
 	  break;
-	case 'w':
-	  if (count < maxsize - 1)
-	    {
-	      sprintf (&s[count], "%1.1d",
-		       tim_p->tm_wday);
-	      count++;
-	    }
-	  else
-	    return 0;
-	  break;
-	case 'W':
+        case 'V':
 	  if (count < maxsize - 2)
 	    {
+	      int adjust = iso_year_adjust (tim_p);
 	      int wday = (tim_p->tm_wday) ? tim_p->tm_wday - 1 : 6;
-	      sprintf (&s[count], "%2.2d",
-		       (tim_p->tm_yday + 7 -
-			wday) / 7);
+	      int week = (tim_p->tm_yday + 10 - wday) / 7;
+	      if (adjust > 0)
+		week = 1;
+	      else if (adjust < 0)
+		/* Previous year has 53 weeks if current year starts on
+		   Fri, and also if current year starts on Sat and
+		   previous year was leap year.  */
+		week = 52 + (4 >= (wday - tim_p->tm_yday
+				   - isleap (tim_p->tm_year
+					     + (YEAR_BASE - 1
+						- (tim_p->tm_year < 0
+						   ? 0 : 2000)))));
+	      sprintf (&s[count], "%.2d", week);
 	      count += 2;
 	    }
 	  else
 	    return 0;
-	  break;
-	case 'x':
-	  if (count < maxsize - 8)
-	    {
-	      sprintf (&s[count], "%.2d",
-		       tim_p->tm_mon + 1);
-	      count += 2;
-	      s[count++] = '/';
-	      sprintf (&s[count], "%.2d",
-		       tim_p->tm_mday);
-	      count += 2;
-	      s[count++] = '/';
-	      /* The year could be greater than 100, so we need the value
-		 modulo 100.  The year could be negative, so we need to
-		 correct for a possible negative remainder.  */
-	      sprintf (&s[count], "%2.2d",
-		       (tim_p->tm_year % 100 + 100) % 100);
-	      count += 2;
-	    }
+          break;
+	case 'w':
+	  if (count < maxsize - 1)
+            s[count++] = '0' + tim_p->tm_wday;
 	  else
 	    return 0;
 	  break;
-	case 'X':
-	  if (count < maxsize - 8)
+	case 'W':
+	  if (count < maxsize - 2)
 	    {
-	      sprintf (&s[count],
-		       "%2.2d:%2.2d:%2.2d",
-		       tim_p->tm_hour, tim_p->tm_min,
-		       tim_p->tm_sec);
-	      count += 8;
+	      int wday = (tim_p->tm_wday) ? tim_p->tm_wday - 1 : 6;
+	      sprintf (&s[count], "%.2d",
+		       (tim_p->tm_yday + 7 - wday) / 7);
+	      count += 2;
 	    }
 	  else
 	    return 0;
@@ -501,36 +729,57 @@ check_format:
 	case 'y':
 	  if (count < maxsize - 2)
 	    {
-	      /* The year could be greater than 100, so we need the value
-		 modulo 100.  The year could be negative, so we need to
-		 correct for a possible negative remainder.  */
-	      sprintf (&s[count], "%2.2d",
-		       (tim_p->tm_year % 100 + 100) % 100);
+	      /* Be careful of both overflow and negative years, thanks to
+		 the asymmetric range of years.  */
+	      int year = tim_p->tm_year >= 0 ? tim_p->tm_year % 100
+		: abs (tim_p->tm_year + YEAR_BASE) % 100;
+	      sprintf (&s[count], "%.2d", year);
 	      count += 2;
 	    }
 	  else
 	    return 0;
 	  break;
 	case 'Y':
-	  if (count < maxsize - 4)
-	    {
-	      sprintf (&s[count], "%.4d",
-		       1900 + tim_p->tm_year);
-	      count += 4;
-	    }
-	  else
-	    return 0;
+	  {
+	    /* Length is not known because of %C%y, so recurse. */
+	    size_t adjust = strftime (&s[count], maxsize - count,
+				      "%C%y", tim_p);
+	    if (adjust > 0)
+	      count += adjust;
+	    else
+	      return 0;
+	  }
 	  break;
+        case 'z':
+          if (tim_p->tm_isdst >= 0)
+            {
+	      if (count < maxsize - 5)
+		{
+		  int offset;
+		  TZ_LOCK;
+		  /* The sign of this is exactly opposite the envvar TZ.  We
+		     could directly use the global _timezone for tm_isdst==0,
+		     but have to use __tzrule for daylight savings.  */
+		  offset = -__tzrule[tim_p->tm_isdst > 0].offset;
+		  TZ_UNLOCK;
+		  sprintf (&s[count], "%+03d%.2d", offset / SECSPERHOUR,
+			   abs (offset / SECSPERMIN) % 60);
+		  count += 5;
+		}
+	      else
+		return 0;
+            }
+          break;
 	case 'Z':
 	  if (tim_p->tm_isdst >= 0)
 	    {
 	      int size;
 	      TZ_LOCK;
-	      size = strlen(_tzname[tim_p->tm_isdst]);
+	      size = strlen(_tzname[tim_p->tm_isdst > 0]);
 	      for (i = 0; i < size; i++)
 		{
 		  if (count < maxsize - 1)
-		    s[count++] = _tzname[tim_p->tm_isdst][i];
+		    s[count++] = _tzname[tim_p->tm_isdst > 0][i];
 		  else
 		    {
 		      TZ_UNLOCK;
@@ -552,7 +801,8 @@ check_format:
       else
 	break;
     }
-  s[count] = '\0';
+  if (maxsize)
+    s[count] = '\0';
 
   return count;
 }
Index: time.tex
===================================================================
RCS file: /cvs/src/src/newlib/libc/time/time.tex,v
retrieving revision 1.3
diff -u -p -r1.3 time.tex
--- time.tex	16 Sep 2004 21:14:45 -0000	1.3
+++ time.tex	14 Feb 2005 13:46:50 -0000
@@ -18,28 +18,28 @@ the following fields: 
 
 @table @code
 @item tm_sec
-Seconds.
+Seconds, between 0 and 60 inclusive (60 allows for leap seconds).
 
 @item tm_min
-Minutes.
+Minutes, between 0 and 59 inclusive.
 
 @item tm_hour
-Hours.
+Hours, between 0 and 23 inclusive.
 
 @item tm_mday
-Day.
+Day of the month, between 1 and 31 inclusive.
 
 @item tm_mon
-Month.
+Month, between 0 (January) and 11 (December).
 
 @item tm_year
-Year (since 1900).
+Year (since 1900), can be negative for earlier years.
 
 @item tm_wday
-Day of week: the number of days since Sunday.
+Day of week, between 0 (Sunday) and 6 (Saturday).
 
 @item tm_yday
-Number of days elapsed since last January 1.
+Number of days elapsed since last January 1, between 0 and 365 inclusive.
 
 @item tm_isdst
 Daylight Savings Time flag: positive means DST in effect, zero means DST

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]