changeset 604:e54f6bf3d960

Update from GNU libc.
author Jim Meyering <jim@meyering.net>
date Tue, 07 May 1996 03:41:17 +0000
parents 38124984a177
children 1912d46c2c6b
files lib/strftime.c
diffstat 1 files changed, 400 insertions(+), 506 deletions(-) [+]
line wrap: on
line diff
--- a/lib/strftime.c
+++ b/lib/strftime.c
@@ -1,559 +1,453 @@
-/* strftime - custom formatting of date and/or time
-   Copyright (C) 1989, 1991, 1992 Free Software Foundation, Inc.
-
-   This program is free software; you can redistribute it and/or modify
-   it under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+/* Copyright (C) 1991, 92, 93, 94, 95, 96 Free Software Foundation, Inc.
 
-   This program is distributed in the hope that it will be useful,
-   but WITHOUT ANY WARRANTY; without even the implied warranty of
-   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-   GNU General Public License for more details.
-
-   You should have received a copy of the GNU General Public License
-   along with this program; if not, write to the Free Software
-   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+NOTE: The canonical source of this file is maintained with the GNU C Library.
+Bugs can be reported to bug-glibc@prep.ai.mit.edu.
 
-/* Note: this version of strftime lacks locale support,
-   but it is standalone.
-
-   Performs `%' substitutions similar to those in printf.  Except
-   where noted, substituted fields have a fixed size; numeric fields are
-   padded if necessary.  Padding is with zeros by default; for fields
-   that display a single number, padding can be changed or inhibited by
-   following the `%' with one of the modifiers described below.  Unknown
-   field specifiers are copied as normal characters.  All other
-   characters are copied to the output without change.
-
-   Supports a superset of the ANSI C field specifiers.
-
-   Literal character fields:
-   %	%
-   n	newline
-   t	tab
-
-   Numeric modifiers (a nonstandard extension):
-   -	do not pad the field
-   _	pad the field with spaces
+This program is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 2, or (at your option) any
+later version.
 
-   Time fields:
-   %H	hour (00..23)
-   %I	hour (01..12)
-   %k	hour ( 0..23)
-   %l	hour ( 1..12)
-   %M	minute (00..59)
-   %p	locale's AM or PM
-   %r	time, 12-hour (hh:mm:ss [AP]M)
-   %R	time, 24-hour (hh:mm)
-   %s	time in seconds since 00:00:00, Jan 1, 1970 (a nonstandard extension)
-   %S	second (00..61)
-   %T	time, 24-hour (hh:mm:ss)
-   %X	locale's time representation (%H:%M:%S)
-   %z   RFC-822 style numeric timezone (-0500) (a nonstandard extension)
-   %Z	time zone (EDT), or nothing if no time zone is determinable
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
 
-   Date fields:
-   %a	locale's abbreviated weekday name (Sun..Sat)
-   %A	locale's full weekday name, variable length (Sunday..Saturday)
-   %b	locale's abbreviated month name (Jan..Dec)
-   %B	locale's full month name, variable length (January..December)
-   %c	locale's date and time (Sat Nov 04 12:02:33 EST 1989)
-   %C	century (00..99)
-   %d	day of month (01..31)
-   %e	day of month ( 1..31)
-   %D	date (mm/dd/yy)
-   %h	same as %b
-   %j	day of year (001..366)
-   %m	month (01..12)
-   %U	week number of year with Sunday as first day of week (00..53)
-   %V	FIXME
-   %w	day of week (0..6)
-   %W	week number of year with Monday as first day of week (00..53)
-   %x	locale's date representation (mm/dd/yy)
-   %y	last two digits of year (00..99)
-   %Y	year (1970...)
-
-   David MacKenzie <djm@gnu.ai.mit.edu> */
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+USA.  */
 
 #ifdef HAVE_CONFIG_H
-#include <config.h>
+# include <config.h>
+#endif
+
+#ifdef _LIBC
+# define HAVE_LIMITS_H 1
+# define HAVE_MBLEN 1
+# define HAVE_TM_ZONE 1
+# define STDC_HEADERS 1
+# include <ansidecl.h>
+# include "../locale/localeinfo.h"
 #endif
 
 #include <stdio.h>
-#include <sys/types.h>
-#if defined(TM_IN_SYS_TIME) || (!defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME))
-#include <sys/time.h>
+#include <sys/types.h>		/* Some systems define `time_t' here.  */
+
+#ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
 #else
-#include <time.h>
+# ifdef HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#if HAVE_MBLEN
+# include <ctype.h>
+#endif
+
+#if HAVE_LIMITS_H
+# include <limits.h>
 #endif
 
-#ifndef STDC_HEADERS
-time_t mktime ();
+#if STDC_HEADERS
+# include <stddef.h>
+# include <stdlib.h>
+# include <string.h>
+#else
+# define memcpy(d, s, n) bcopy (s, d, n)
 #endif
 
-#if defined(HAVE_TZNAME)
-extern char *tzname[2];
+#ifndef __P
+#if defined (__GNUC__) || (defined (__STDC__) && __STDC__)
+#define __P(args) args
+#else
+#define __P(args) ()
+#endif  /* GCC.  */
+#endif  /* Not __P.  */
+
+#ifndef PTR
+#ifdef __STDC__
+#define PTR void *
+#else
+#define PTR char *
+#endif
 #endif
 
-/* Types of padding for numbers in date and time. */
-enum padding
-{
-  none, blank, zero
-};
+static unsigned int week __P((const struct tm *const, int, int));
 
-static char const* const days[] =
-{
-  "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
-};
-
-static char const * const months[] =
-{
-  "January", "February", "March", "April", "May", "June",
-  "July", "August", "September", "October", "November", "December"
-};
-
-/* Add character C to STRING and increment LENGTH,
-   unless LENGTH would exceed MAX. */
 
-#define add_char(c)							\
-  do									\
-    {									\
-      if (length + 1 <= max)						\
-	string[length++] = (c);						\
-    }									\
-  while (0)
+#define	add(n, f)							      \
+  do									      \
+    {									      \
+      i += (n);								      \
+      if (i >= maxsize)							      \
+	return 0;							      \
+      else								      \
+	if (p)								      \
+	  {								      \
+	    f;								      \
+	    p += (n);							      \
+	  }								      \
+    } while (0)
+#define	cpy(n, s)	add((n), memcpy((PTR) p, (PTR) (s), (n)))
 
-/* Add a 2 digit number to STRING, padding if specified.
-   Return the number of characters added, up to MAX. */
+#ifdef _LIBC
+#define	fmt(n, args)	add((n), if (sprintf args != (n)) return 0)
+#else
+#define	fmt(n, args)	add((n), sprintf args; if (strlen (p) != (n)) return 0)
+#endif
 
-static int
-add_num2 (string, num, max, pad)
-     char *string;
-     int num;
-     int max;
-     enum padding pad;
-{
-  int top = num / 10;
-  int length = 0;
+
 
-  if (top == 0 && pad == blank)
-    add_char (' ');
-  else if (top != 0 || pad == zero)
-    add_char (top + '0');
-  add_char (num % 10 + '0');
-  return length;
-}
-
-/* Add a 3 digit number to STRING, padding if specified.
-   Return the number of characters added, up to MAX. */
+/* Return the week in the year specified by TP,
+   with weeks starting on STARTING_DAY.  */
+#ifdef	__GNUC__
+inline
+#endif
+static unsigned int
+week (tp, starting_day, max_preceding)
+      const struct tm *const tp;
+      int starting_day;
+      int max_preceding;
+{
+  int wday, dl, base;
 
-static int
-add_num3 (string, num, max, pad)
-     char *string;
-     int num;
-     int max;
-     enum padding pad;
-{
-  int top = num / 100;
-  int mid = (num - top * 100) / 10;
-  int length = 0;
+  wday = tp->tm_wday - starting_day;
+  if (wday < 0)
+    wday += 7;
 
-  if (top == 0 && pad == blank)
-    add_char (' ');
-  else if (top != 0 || pad == zero)
-    add_char (top + '0');
-  if (mid == 0 && top == 0 && pad == blank)
-    add_char (' ');
-  else if (mid != 0 || top != 0 || pad == zero)
-    add_char (mid + '0');
-  add_char (num % 10 + '0');
-  return length;
-}
+  /* Set DL to the day in the year of the first day of the week
+     containing the day specified in TP.  */
+  dl = tp->tm_yday - wday;
 
-/* Like strncpy except return the number of characters copied. */
+  /* For the computation following ISO 8601:1988 we set the number of
+     the week containing January 1st to 1 if this week has more than
+     MAX_PRECEDING days in the new year.  For ISO 8601 this number is
+     3, for the other representation it is 7 (i.e., not to be
+     fulfilled).  */
+  base = ((dl + 7) % 7) > max_preceding ? 1 : 0;
 
-static int
-add_str (to, from, max)
-     char *to;
-     const char *from;
-     int max;
-{
-  int i;
-
-  for (i = 0; from[i] && i <= max; ++i)
-    to[i] = from[i];
-  return i;
+  /* If DL is negative we compute the result as 0 unless we have to
+     compute it according ISO 8601.  In this case we have to return 53
+     or 1 if the week containing January 1st has less than 4 days in
+     the new year or not.  If DL is not negative we calculate the
+     number of complete weeks for our week (DL / 7) plus 1 (because
+     only for DL < 0 we are in week 0/53 and plus the number of the
+     first week computed in the last step.  */
+  return dl < 0 ? (dl < -max_preceding ? 53 : base)
+		: base + 1 + dl / 7;
 }
 
-static int
-add_num_time_t (string, max, num)
-     char *string;
-     int max;
-     time_t num;
-{
-  /* This buffer is large enough to hold the character representation
-     (including the trailing NUL) of any unsigned decimal quantity
-     whose binary representation fits in 128 bits.  */
-  char buf[40];
-  int length;
-
-  if (sizeof (num) > 16)
-    abort ();
-  sprintf (buf, "%lu", (unsigned long) num);
-  length = add_str (string, buf, max);
-  return length;
-}
-
-/* Convert MINUTES_EAST into a string suitable for use as the RFC-822
-   timezone indicator.  Write no more than MAX bytes into STRING.
-    Return the number of bytes written into STRING.  */
-
-static int
-add_num_tz (string, max, minutes_east)
-     char *string;
-     int max;
-     int minutes_east;
-{
-  int length;
-
-  if (max < 1)
-    return 0;
-
-  if (minutes_east < 0)
-    {
-      *string = '-';
-      minutes_east = -minutes_east;
-    }
-  else
-    *string = '+';
-
-  length = 1 + add_num2 (&string[1], (minutes_east / 60) % 24, max - 1, zero);
-  length += add_num2 (&string[length], minutes_east % 60, max - length, zero);
-
-  return length;
-}
-
-/* Implement %U.  Return the week in the year of the time in TM,
-   with the weeks starting on Sundays.  */
-
-static int
-sun_week (tm)
-     const struct tm *tm;
-{
-  int dl;
+#ifndef _NL_CURRENT
+static char const weekday_name[][10] =
+  {
+    "Sunday", "Monday", "Tuesday", "Wednesday",
+    "Thursday", "Friday", "Saturday"
+  };
+static char const month_name[][10] =
+  {
+    "January", "February", "March", "April", "May", "June",
+    "July", "August", "September", "October", "November", "December"
+  };
+#endif
 
-  /* %U Week of the year (Sunday as the first day of the week) as a decimal
-     number [00-53].  All days in a new year preceding the first Sunday are
-     considered to be in week 0.  */
-
-  dl = tm->tm_yday - tm->tm_wday;
-  return dl < 0 ? 0 : dl / 7 + 1;
-}
+/* Write information from TP into S according to the format
+   string FORMAT, writing no more that MAXSIZE characters
+   (including the terminating '\0') and returning number of
+   characters written.  If S is NULL, nothing will be written
+   anywhere, so to determine how many characters would be
+   written, use NULL for S and (size_t) UINT_MAX for MAXSIZE.  */
+size_t
+strftime (s, maxsize, format, tp)
+      char *s;
+      size_t maxsize;
+      const char *format;
+      register const struct tm *tp;
+{
+  int hour12 = tp->tm_hour;
+#ifdef _NL_CURRENT
+  const char *const a_wkday = _NL_CURRENT (LC_TIME, ABDAY_1 + tp->tm_wday);
+  const char *const f_wkday = _NL_CURRENT (LC_TIME, DAY_1 + tp->tm_wday);
+  const char *const a_month = _NL_CURRENT (LC_TIME, ABMON_1 + tp->tm_mon);
+  const char *const f_month = _NL_CURRENT (LC_TIME, MON_1 + tp->tm_mon);
+  const char *const ampm = _NL_CURRENT (LC_TIME,
+					hour12 > 11 ? PM_STR : AM_STR);
+  size_t aw_len = strlen(a_wkday);
+  size_t am_len = strlen(a_month);
+  size_t ap_len = strlen (ampm);
+#else
+  const char *const f_wkday = weekday_name[tp->tm_wday];
+  const char *const f_month = month_name[tp->tm_mon];
+  const char *const a_wkday = f_wkday;
+  const char *const a_month = f_month;
+  const char *const ampm = "AMPM" + 2 * (hour12 > 11);
+  size_t aw_len = 3;
+  size_t am_len = 3;
+  size_t ap_len = 2;
+#endif
+  size_t wkday_len = strlen(f_wkday);
+  size_t month_len = strlen(f_month);
+  const unsigned int y_week0 = week (tp, 0, 7);
+  const unsigned int y_week1 = week (tp, 1, 7);
+  const unsigned int y_week2 = week (tp, 1, 3);
+  const char *zone;
+  size_t zonelen;
+  register size_t i = 0;
+  register char *p = s;
+  register const char *f;
+  char number_fmt[5];
 
-/* Implement %V.  Similar to mon_week (%W), but there is no 0'th week --
-   they're numbered [01-53].  And if the week containing January 1 has
-   four or more days in the new year, then it is considered week 1;
-   otherwise, it is week 53 of the previous year, and the next week is
-   week 1. (See the ISO 8601: 1988 standard.)  */
+  /* Initialize the buffer we will use for the sprintf format for numbers.  */
+  number_fmt[0] = '%';
 
-static int
-mon_week_ISO (tm)
-     const struct tm *tm;
-{
-  int dl, n_days_before_first_monday;
-  int week_num;
+  zone = 0;
+#if HAVE_TM_ZONE
+  zone = (const char *) tp->tm_zone;
+#endif
+#if HAVE_TZNAME
+  if (!(zone && *zone) && tp->tm_isdst >= 0)
+    zone = tzname[tp->tm_isdst];
+#endif
+  if (!(zone && *zone))
+    zone = "???";
 
-  n_days_before_first_monday = (tm->tm_yday + 7 - tm->tm_wday + 1) % 7;
-  dl = tm->tm_yday - n_days_before_first_monday;
-  week_num = dl < 0 ? 0 : dl / 7 + 1;
-  if (n_days_before_first_monday >= 4)
+  zonelen = strlen (zone);
+
+  if (hour12 > 12)
+    hour12 -= 12;
+  else
+    if (hour12 == 0) hour12 = 12;
+
+  for (f = format; *f != '\0'; ++f)
     {
-      week_num = (week_num + 1) % 54;
-      if (week_num == 0)
-	week_num = 1;
-    }
-  if (week_num == 0)
-    week_num = 53;
-
-  return week_num;
-}
-
-/* Implement %W.  Return the week in the year of the time in TM,
-   with the weeks starting on Mondays.  */
+      enum { pad_zero, pad_space, pad_none } pad; /* Padding for number.  */
+      unsigned int maxdigits;	/* Max digits for numeric format.  */
+      unsigned int number_value; /* Numeric value to be printed.  */
+      const char *subfmt;
 
-static int
-mon_week (tm)
-     const struct tm *tm;
-{
-  int dl, n_days_before_first_monday;
-
-  n_days_before_first_monday = (tm->tm_yday + 7 - tm->tm_wday + 1) % 7;
-  dl = tm->tm_yday - n_days_before_first_monday;
-  return dl < 0 ? 0 : dl / 7 + 1;
-}
-
-#if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
-char *
-zone_name (tp)
-     struct tm *tp;
-{
-  char *timezone ();
-  struct timeval tv;
-  struct timezone tz;
-
-  gettimeofday (&tv, &tz);
-  return timezone (tz.tz_minuteswest, tp->tm_isdst);
-}
+#if HAVE_MBLEN
+      if (!isascii(*f))
+	{
+	  /* Non-ASCII, may be a multibyte.  */
+	  int len = mblen(f, strlen(f));
+	  if (len > 0)
+	    {
+	      cpy(len, f);
+	      continue;
+	    }
+	}
 #endif
 
-/* Format the time given in TM according to FORMAT, and put the
-   results in STRING.
-   Return the number of characters (not including terminating null)
-   that were put into STRING, or 0 if the length would have
-   exceeded MAX. */
+      if (*f != '%')
+	{
+	  add(1, *p = *f);
+	  continue;
+	}
 
-size_t
-strftime (string, max, format, tm)
-     char *string;
-     size_t max;
-     const char *format;
-     const struct tm *tm;
-{
-  enum padding pad;		/* Type of padding to apply. */
-  size_t length = 0;		/* Characters put in STRING so far. */
-
-  for (; *format && length < max; ++format)
-    {
-      if (*format != '%')
-	add_char (*format);
-      else
+      /* Check for flags that can modify a number format.  */
+      ++f;
+      switch (*f)
 	{
-	  ++format;
-	  /* Modifiers: */
-	  if (*format == '-')
-	    {
-	      pad = none;
-	      ++format;
-	    }
-	  else if (*format == '_')
-	    {
-	      pad = blank;
-	      ++format;
-	    }
-	  else
-	    pad = zero;
+	case '_':
+	  pad = pad_space;
+	  ++f;
+	  break;
+	case '-':
+	  pad = pad_none;
+	  ++f;
+	  break;
+	default:
+	  pad = pad_zero;
+	  break;
+	}
 
-	  switch (*format)
-	    {
-	      /* Literal character fields: */
-	    case 0:
-	    case '%':
-	      add_char ('%');
-	      break;
-	    case 'n':
-	      add_char ('\n');
-	      break;
-	    case 't':
-	      add_char ('\t');
-	      break;
-	    default:
-	      add_char (*format);
-	      break;
+      /* Now do the specified format.  */
+      switch (*f)
+	{
+	case '\0':
+	case '%':
+	  add(1, *p = *f);
+	  break;
+
+	case 'a':
+	  cpy(aw_len, a_wkday);
+	  break;
+
+	case 'A':
+	  cpy(wkday_len, f_wkday);
+	  break;
+
+	case 'b':
+	case 'h':		/* GNU extension.  */
+	  cpy(am_len, a_month);
+	  break;
+
+	case 'B':
+	  cpy(month_len, f_month);
+	  break;
 
-	      /* Time fields: */
-	    case 'H':
-	    case 'k':
-	      length +=
-		add_num2 (&string[length], tm->tm_hour, max - length,
-			  *format == 'H' ? pad : blank);
-	      break;
-	    case 'I':
-	    case 'l':
-	      {
-		int hour12;
+	case 'c':
+#ifdef _NL_CURRENT
+	  subfmt = _NL_CURRENT (LC_TIME, D_T_FMT);
+#else
+	  subfmt = "%a %b %d %H:%M:%S %Z %Y";
+#endif
+	subformat:
+	  {
+	    size_t len = strftime (p, maxsize - i, subfmt, tp);
+	    if (len == 0 && *subfmt)
+	      return 0;
+	    add(len, );
+	  }
+	  break;
+
+#define DO_NUMBER(digits, value) \
+	  maxdigits = digits; number_value = value; goto do_number
+#define DO_NUMBER_NOPAD(digits, value) \
+	  maxdigits = digits; number_value = value; goto do_number_nopad
+
+	case 'C':
+	  DO_NUMBER (2, (1900 + tp->tm_year) / 100);
 
-		if (tm->tm_hour == 0)
-		  hour12 = 12;
-		else if (tm->tm_hour > 12)
-		  hour12 = tm->tm_hour - 12;
-		else
-		  hour12 = tm->tm_hour;
-		length +=
-		  add_num2 (&string[length], hour12, max - length,
-			    *format == 'I' ? pad : blank);
-	      }
-	      break;
-	    case 'M':
-	      length +=
-		add_num2 (&string[length], tm->tm_min, max - length, pad);
-	      break;
-	    case 'p':
-	      if (tm->tm_hour < 12)
-		add_char ('A');
-	      else
-		add_char ('P');
-	      add_char ('M');
-	      break;
-	    case 'r':
-	      length +=
-		strftime (&string[length], max - length, "%I:%M:%S %p", tm);
-	      break;
-	    case 'R':
-	      length +=
-		strftime (&string[length], max - length, "%H:%M", tm);
-	      break;
+	case 'x':
+#ifdef _NL_CURRENT
+	  subfmt = _NL_CURRENT (LC_TIME, D_FMT);
+	  goto subformat;
+#endif
+	  /* Fall through.  */
+	case 'D':		/* GNU extension.  */
+	  subfmt = "%m/%d/%y";
+	  goto subformat;
+
+	case 'd':
+	  DO_NUMBER (2, tp->tm_mday);
+
+	case 'e':		/* GNU extension: %d, but blank-padded.  */
+	  DO_NUMBER_NOPAD (2, tp->tm_mday);
 
-	    case 's':
-	      {
-		struct tm writable_tm;
-		writable_tm = *tm;
-		length += add_num_time_t (&string[length], max - length,
-					  mktime (&writable_tm));
-	      }
-	      break;
+	  /* All numeric formats set MAXDIGITS and NUMBER_VALUE and then
+	     jump to one of these two labels.  */
+
+	do_number_nopad:
+	  /* Force `-' flag.  */
+	  pad = pad_none;
 
-	    case 'S':
-	      length +=
-		add_num2 (&string[length], tm->tm_sec, max - length, pad);
-	      break;
-	    case 'T':
-	      length +=
-		strftime (&string[length], max - length, "%H:%M:%S", tm);
-	      break;
-	    case 'X':
-	      length +=
-		strftime (&string[length], max - length, "%H:%M:%S", tm);
-	      break;
-	    case 'z':
+	do_number:
+	  {
+	    /* Format the number according to the PAD flag.  */
+
+	    register char *nf = &number_fmt[1];
+	    int printed;
+
+	    switch (pad)
 	      {
-		time_t t;
-		struct tm tml, tmg;
-		int diff;
+	      case pad_zero:
+		*nf++ = '0';
+	      case pad_space:
+		*nf++ = '0' + maxdigits;
+	      case pad_none:
+		*nf++ = 'u';
+		*nf = '\0';
+	      }
 
-		tml = *tm;
-		t = mktime (&tml);
-		tml = *localtime (&t); /* Canonicalize the local time */
-		tmg = *gmtime (&t);
+#ifdef _LIBC
+	    add (maxdigits, printed = sprintf (p, number_fmt, number_value));
+#else
+	    add (maxdigits, sprintf (p, number_fmt, number_value);
+		 printed = strlen (p));
+#endif
 
-		/* Compute the difference */
+	    break;
+	  }
 
-		diff = tml.tm_min - tmg.tm_min;
-		diff += 60 * (tml.tm_hour - tmg.tm_hour);
+
+	case 'H':
+	  DO_NUMBER (2, tp->tm_hour);
 
-		if (tml.tm_mon != tmg.tm_mon)
-		  {
-		    /* We assume no timezone differs from UTC by more than
-		       +- 23 hours.  This should be safe. */
-		    if (tmg.tm_mday == 1)
-		      tml.tm_mday = 0;
-		    else /* tml.tm_mday == 1 */
-		      tmg.tm_mday = 0;
-		  }
+	case 'I':
+	  DO_NUMBER (2, hour12);
+
+	case 'k':		/* GNU extension.  */
+	  DO_NUMBER_NOPAD (2, tp->tm_hour);
 
-		diff += 1440 * (tml.tm_mday - tmg.tm_mday);
+	case 'l':		/* GNU extension.  */
+	  DO_NUMBER_NOPAD (2, hour12);
+
+	case 'j':
+	  DO_NUMBER (3, 1 + tp->tm_yday);
 
-		length += add_num_tz (&string[length], max - length, diff);
-	      }
-	      break;
-	    case 'Z':
-#ifdef HAVE_TM_ZONE
-	      length += add_str (&string[length], tm->tm_zone, max - length);
-#else
-#ifdef HAVE_TZNAME
-	      if (tm->tm_isdst && tzname[1] && *tzname[1])
-		length += add_str (&string[length], tzname[1], max - length);
-	      else
-		length += add_str (&string[length], tzname[0], max - length);
-#else
-	      length += add_str (&string[length], zone_name (tm), max - length);
-#endif
-#endif
-	      break;
+	case 'M':
+	  DO_NUMBER (2, tp->tm_min);
+
+	case 'm':
+	  DO_NUMBER (2, tp->tm_mon + 1);
+
+	case 'n':		/* GNU extension.  */
+	  add (1, *p = '\n');
+	  break;
+
+	case 'p':
+	  cpy(ap_len, ampm);
+	  break;
 
-	      /* Date fields: */
-	    case 'a':
-	      add_char (days[tm->tm_wday][0]);
-	      add_char (days[tm->tm_wday][1]);
-	      add_char (days[tm->tm_wday][2]);
-	      break;
-	    case 'A':
-	      length +=
-		add_str (&string[length], days[tm->tm_wday], max - length);
-	      break;
-	    case 'b':
-	    case 'h':
-	      add_char (months[tm->tm_mon][0]);
-	      add_char (months[tm->tm_mon][1]);
-	      add_char (months[tm->tm_mon][2]);
-	      break;
-	    case 'B':
-	      length +=
-		add_str (&string[length], months[tm->tm_mon], max - length);
-	      break;
-	    case 'c':
-	      length +=
-		strftime (&string[length], max - length,
-			  "%a %b %d %H:%M:%S %Z %Y", tm);
-	      break;
-	    case 'C':
-	      length +=
-		add_num2 (&string[length], (tm->tm_year + 1900) / 100,
-			  max - length, pad);
-	      break;
-	    case 'd':
-	      length +=
-		add_num2 (&string[length], tm->tm_mday, max - length, pad);
-	      break;
-	    case 'e':
-	      length +=
-		add_num2 (&string[length], tm->tm_mday, max - length, blank);
-	      break;
-	    case 'D':
-	      length +=
-		strftime (&string[length], max - length, "%m/%d/%y", tm);
-	      break;
-	    case 'j':
-	      length +=
-		add_num3 (&string[length], tm->tm_yday + 1, max - length, pad);
-	      break;
-	    case 'm':
-	      length +=
-		add_num2 (&string[length], tm->tm_mon + 1, max - length, pad);
-	      break;
-	    case 'U':
-	      length +=
-		add_num2 (&string[length], sun_week (tm), max - length, pad);
-	      break;
-	    case 'V':
-	      length += add_num2 (&string[length], mon_week_ISO (tm),
-				  max - length, pad);
-	      break;
-	    case 'w':
-	      add_char (tm->tm_wday + '0');
-	      break;
-	    case 'W':
-	      length +=
-		add_num2 (&string[length], mon_week (tm), max - length, pad);
-	      break;
-	    case 'x':
-	      length +=
-		strftime (&string[length], max - length, "%m/%d/%y", tm);
-	      break;
-	    case 'y':
-	      length +=
-		add_num2 (&string[length], tm->tm_year % 100,
-			  max - length, pad);
-	      break;
-	    case 'Y':
-	      add_char ((tm->tm_year + 1900) / 1000 + '0');
-	      length +=
-		add_num3 (&string[length],
-			  (1900 + tm->tm_year) % 1000, max - length, zero);
-	      break;
-	    }
+	case 'R':		/* GNU extension.  */
+	  subfmt = "%H:%M";
+	  goto subformat;
+
+	case 'r':		/* GNU extension.  */
+	  subfmt = "%I:%M:%S %p";
+	  goto subformat;
+
+	case 'S':
+	  DO_NUMBER (2, tp->tm_sec);
+
+	case 'X':
+#ifdef _NL_CURRENT
+	  subfmt = _NL_CURRENT (LC_TIME, T_FMT);
+	  goto subformat;
+#endif
+	  /* Fall through.  */
+	case 'T':		/* GNU extenstion.  */
+	  subfmt = "%H:%M:%S";
+	  goto subformat;
+
+	case 't':		/* GNU extenstion.  */
+	  add (1, *p = '\t');
+	  break;
+
+	case 'U':
+	  DO_NUMBER (2, y_week0);
+
+	case 'V':
+	  DO_NUMBER (2, y_week2);
+
+	case 'W':
+	  DO_NUMBER (2, y_week1);
+
+	case 'w':
+	  DO_NUMBER (2, tp->tm_wday);
+
+	case 'Y':
+	  DO_NUMBER (4, 1900 + tp->tm_year);
+
+	case 'y':
+	  DO_NUMBER (2, tp->tm_year % 100);
+
+	case 'Z':
+	  cpy(zonelen, zone);
+	  break;
+
+	default:
+	  /* Bad format.  */
+	  break;
 	}
     }
-  add_char (0);
-  return length - 1;
+
+  if (p)
+    *p = '\0';
+  return i;
 }