delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp-workers/2001/01/14/04:46:27

From: Jason Green <news AT jgreen4 DOT fsnet DOT co DOT uk>
To: djgpp-workers AT delorie DOT com
Cc: Esa A E Peuha <peuha AT cc DOT helsinki DOT fi>
Subject: strftime patch
Date: Sun, 14 Jan 2001 09:45:49 +0000
Message-ID: <phs26tgvhm0ir3j1fus6tl8lf6nkh7637a@4ax.com>
X-Mailer: Forte Agent 1.7/32.534
MIME-Version: 1.0
X-MIME-Autoconverted: from quoted-printable to 8bit by delorie.com id EAA06631
Reply-To: djgpp-workers AT delorie DOT com

Attached below are two patches to make strftime() compliant with
latest ISO C standard.

The first patch fixes or adds support for the following conversion
specifiers: %F, %g, %G, %V & %z.  The first patch also adds %P and %s
for compatibility with the GNU C library.

The second patch modifies behaviour of %c, %p & %x, to be compliant
with ISO C using the "C" locale.  I have not read up about locales so
I am assuming that behaviour in DJGPP should be according to the "C"
locale.  This patch is separate because the changes introduce slight
incompatibility with glibc2.  (Applying only the first patch gives an
exact match here, using the test program attached below.)

I will make a patch to the docs when I know how much of the changes
are accepted and in what form.

Thanks go to Esa A E Peuha <peuha AT cc DOT helsinki DOT fi> for writing the code
to calculate ISO 8601 week and year numbers.

I have used the glibc2 man page and the last public draft of ISO C as
references.  Any inaccuracies in these documents will be reflected in
the code.  So I think someone with a copy of the final standard should
compare strftime() definition with the one I used.

gcc 2.95.2 emits a warning when strftime() is used with %G, this
happens both in DJGPP and under Linux.  So I think it would be good if
someone with latest gcc sources installed could compile the example
test program below to see if the problem still exists and/or look at
the source for comments to understand if this is an issue of concern.

#include <time.h>
int main (void)
{
  char str[10];
  time_t t;
  struct tm *tm_ptr;
  time(&t);
  tm_ptr = localtime(&t);
  strftime (str, sizeof (str), "%G", tm_ptr);
  return 0;
}

$ gcc -Wall -pedantic percentg.c
percentg.c: In function `main':
percentg.c:9: warning: ANSI C does not support `%G'

--------------------------------------------------------------------
Here is a program to generally test strftime() function:

#include <stdio.h>
#include <time.h>

int
main (void)
{
  const char *p, *testspec =
"aAbBcCdDeFgGhHIjklmMnpPrRsStTuUVwWxXyYzZ";
  char str[80], fmt[10];
  time_t t = 987654321;
  struct tm *tm_ptr = gmtime(&t);

  for (p = testspec; *p; p++)
    {
      sprintf(fmt, "%%%c", *p);
      strftime (str, sizeof (str), fmt, tm_ptr);
      printf("%s = \"%s\"\n", fmt, str);
    }

  return 0;
}

--------------------------------------------------------------------
Here are heavily snipped excerpts from the last public draft of ISO C
and the glibc2 man page:

From n869.txt:

       [#3]  Each  conversion  specifier is replaced by appropriate
       characters  as  described  in  the  following   list...   
         %F  is  equivalent  to  ``%Y-%m-%d''  (the  ISO  8601 date
             format).  [tm_year, tm_mon, tm_mday]
         %g  is replaced by the last 2  digits  of  the  week-based
             year   (see   below)  as  a  decimal  number  (00-99).
             [tm_year, tm_wday, tm_yday]
         %G  is replaced by the week-based year (see  below)  as  a
             decimal   number  (e.g.,  1997).   [tm_year,  tm_wday,
             tm_yday]
         %p  is  replaced  by  the locale's equivalent of the AM/PM
             designations  associated   with   a   12-hour   clock.
             [tm_hour]
         %V  is replaced by the ISO 8601 week number (see below) as
             a decimal number (01-53).  [tm_year, tm_wday, tm_yday]
         %z  is replaced by the offset from UTC  in  the  ISO  8601
             format  ``-0430''  (meaning  4 hours 30 minutes behind
             UTC, west of Greenwich), or by  no  characters  if  no
             time zone is determinable.  [tm_isdst]

       [#4]  Some  conversion  specifiers  can  be  modified by the |
       inclusion of an E or O modifier  character  to  indicate  an |
       alternative  format  or  specification.   If the alternative
       format or specification  does  not  exist  for  the  current
       locale, the modifier is ignored.

       [#7]  In  the  "C" locale, the E and O modifiers are ignored
       and the replacement strings  for  the  following  specifiers
       are:
         %c  equivalent to ``%A %B %d %T %Y''.
         %p  one of ``am'' or ``pm''.
         %x  equivalent to ``%A %B %d %Y''.

Differences found in the glibc2 man page:

       %s     The number of seconds since the Epoch, i.e.,  since
              1970-01-01 00:00:00 UTC. (TZ)
       %p     Either `AM' or `PM' according  to  the  given  time
              value, or the corresponding strings for the current
              locale.  Noon is treated as `pm'  and  midnight  as
              `am'.
       %P     Like  %p but in lowercase: `am' or `pm' or a corre
              sponding string for the current locale. (GNU)

--------------------------------------------------------------------
Here are the patches libc:

--- src/libc/ansi/time/strftime.c.orig	Thu Jun  3 13:27:34 1999
+++ src/libc/ansi/time/strftime.c	Sat Jan 13 17:57:06 2001
@@ -1,3 +1,5 @@
+/* Copyright (C) 2001 DJ Delorie, see COPYING.DJ for details */
+/* Copyright (C) 2000 DJ Delorie, see COPYING.DJ for details */
 /* Copyright (C) 1999 DJ Delorie, see COPYING.DJ for details */
 /* Copyright (C) 1998 DJ Delorie, see COPYING.DJ for details */
 /* Copyright (C) 1994 DJ Delorie, see COPYING.DJ for details */
@@ -7,6 +9,11 @@
 
 #define TM_YEAR_BASE 1900
 
+/* Leap year calculation */
+#define isleap(y) \
+          ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0)
+#define days_in_year(y) ((isleap(y))? 366:365)
+
 static const char *afmt[] = {
   "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
 };
@@ -41,9 +48,9 @@
 }
 
 static int
-_conv(int n, int digits, char pad)
+_conv(unsigned int n, int digits, char pad)
 {
-  static char buf[10];
+  static char buf[12];
   char *p = buf + sizeof(buf) - 2;
 
   do {
@@ -58,14 +65,73 @@
   return _add(++p, 0);
 }
 
+/* number_of_week: Helper function to iso_week_num()
+*/
+static int
+number_of_week (int wday, int yday)
+{
+  int day_of_week_now = (wday + 6) % 7;
+  int day_of_week_jan1 = (day_of_week_now + 53 * 7 - yday) % 7;
+  int days_since_monday_of_first_week = yday + day_of_week_jan1 - 7
+    * (day_of_week_jan1 >= 4);
+
+  return days_since_monday_of_first_week / 7 + 1;
+}
+
+/* iso_year: Calculates an adjustment for tm_year to account for
+   ISO-8601 dates as used by %V, %g and %G.  Returns -1, 0, or 1,
+   which can be added to tm_year to determine the ISO-8601 year.
+*/
+static int
+iso_year_adjust (const struct tm *t)
+{
+  if (t->tm_mon == 11 &&
+      ((t->tm_wday == 1 && t->tm_mday >= 29) ||
+       (t->tm_wday == 2 && t->tm_mday >= 30) ||
+       (t->tm_wday == 3 && t->tm_mday == 31)))
+    /* day belongs to first week of next year */
+    return 1;
+  else if (t->tm_mon == 0 &&
+	   ((t->tm_wday == 5 && t->tm_mday == 1) ||
+	    (t->tm_wday == 6 && t->tm_mday <= 2) ||
+	    (t->tm_wday == 0 && t->tm_mday <= 3)))
+    /* day belongs to last week of previous year */
+    return -1;
+  else
+    /* the usual case */
+    return 0;
+}
+
+/* iso_week_num: Returns the ISO-8601 week number as required by %V.
+*/
+static int
+iso_week_num (const struct tm *t)
+{
+  switch (iso_year_adjust (t))
+    {
+    case 1:
+      return 1;
+    case -1:
+      /* day belongs to last week of previous year */
+      return number_of_week (t->tm_wday, t->tm_yday +
+		days_in_year (t->tm_year + TM_YEAR_BASE - 1));
+    default:
+      /* the usual case */
+      return number_of_week (t->tm_wday, t->tm_yday);
+    }
+}
+
 static size_t
 _fmt(const char *format, const struct tm *t, int upcase)
 {
+  long gmtoffset, gmtoffset_mins, gmtoffset_hrs;
+  struct tm t_copy;
+
   for (; *format; ++format)
   {
     if (*format == '%')
     {
-      int pad = '0', space=' ';
+      int pad = '0', space=' ', modifier = 0;
       if (format[1] == '_')
 	pad = space = ' ', format++;
       if (format[1] == '-')
@@ -74,6 +140,11 @@
 	pad = space = '0', format++;
       if (format[1] == '^')
 	upcase = 1, format++;
+      /* 'E' and 'O' should modify the specifier to indicate
+         an alternate format according to the current locale,
+         this is currently unsupported by DJGPP. */
+      if ((format[1] == 'E') || (format[1] == 'O'))
+	modifier = format[1], format++;
 
       switch(*++format)
       {
@@ -125,6 +196,20 @@
 	if (!_conv(t->tm_mday, 2, pad))
 	  return 0;
 	continue;
+      case 'F':
+	if (!_fmt("%Y-%m-%d", t, upcase))
+	  return 0;
+	continue;
+      case 'g':
+	if (!_conv((t->tm_year + iso_year_adjust(t) + TM_YEAR_BASE)
+	           % 100, 2, pad))
+	  return 0;
+	continue;
+      case 'G':
+	if (!_conv(t->tm_year + iso_year_adjust(t) + TM_YEAR_BASE,
+	           4, pad))
+	  return 0;
+	continue;
       case 'H':
 	if (!_conv(t->tm_hour, 2, pad))
 	  return 0;
@@ -159,6 +244,10 @@
 	if (!_add("\n", upcase))
 	  return 0;
 	continue;
+      case 'P':
+	if (!_add(t->tm_hour >= 12 ? "pm" : "am", upcase))
+	  return 0;
+	continue;
       case 'p':
 	if (!_add(t->tm_hour >= 12 ? "PM" : "AM", upcase))
 	  return 0;
@@ -175,6 +264,11 @@
 	if (!_conv(t->tm_sec, 2, pad))
 	  return 0;
 	continue;
+      case 's': /* There must be a more efficient way to do this. */
+        memcpy(&t_copy, t, sizeof(t_copy));
+	if (!_conv(mktime(&t_copy), 0, 0))
+	  return 0;
+	continue;
       case 'T':
       case 'X':
 	if (!_fmt("%H:%M:%S", t, upcase))
@@ -193,6 +287,10 @@
 		   2, pad))
 	  return 0;
 	continue;
+      case 'V':
+	if (!_conv(iso_week_num(t), 2, pad))
+	  return 0;
+	continue;
       case 'W':
 	if (!_conv((t->tm_yday + 7 -
 		    (t->tm_wday ? (t->tm_wday - 1) : 6))
@@ -208,20 +306,31 @@
 	  return 0;
 	continue;
       case 'y':
-      case 'g':
 	if (!_conv((t->tm_year + TM_YEAR_BASE) % 100, 2, pad))
 	  return 0;
 	continue;
       case 'Y':
-      case 'G':
 	if (!_conv(t->tm_year + TM_YEAR_BASE, 4, pad))
 	  return 0;
 	continue;
       case 'z':
-	if (!_add(t->__tm_gmtoff<0 ? "-" : "+", 0))
-	  return 0;
-	if (!_conv(t->__tm_gmtoff, 4, pad))
-	  return 0;
+        gmtoffset_mins = (t->__tm_gmtoff / 60) % 60;
+        gmtoffset_hrs = t->__tm_gmtoff / 3600;
+        gmtoffset = (gmtoffset_hrs * 100) + gmtoffset_mins;
+	if (gmtoffset<0)
+	{
+	  if (!_add("-", 0))
+	    return 0;
+	  if (!_conv(-gmtoffset, 4, pad))
+	    return 0;
+	}
+	else
+	{
+	  if (!_add("+", 0))
+	    return 0;
+	  if (!_conv(gmtoffset, 4, pad))
+	    return 0;
+	}
 	continue;
       case 'Z':
 	if (!t->tm_zone || !_add(t->tm_zone, upcase))

--- src/libc/ansi/time/strftime.c.bak	Sat Jan 13 17:57:06 2001
+++ src/libc/ansi/time/strftime.c	Sat Jan 13 18:00:40 2001
@@ -181,7 +181,7 @@
 	  return 0;
 	continue;
       case 'c':
-	if (!_fmt("%a %b %e %H:%M:%S %Y", t, upcase))
+	if (!_fmt("%A %B %d %T %Y", t, upcase))
 	  return 0;
 	continue;
       case 'e':
@@ -249,7 +249,7 @@
 	  return 0;
 	continue;
       case 'p':
-	if (!_add(t->tm_hour >= 12 ? "PM" : "AM", upcase))
+	if (!_add(t->tm_hour >= 12 ? "pm" : "am", upcase))
 	  return 0;
 	continue;
       case 'R':
@@ -302,7 +302,7 @@
 	  return 0;
 	continue;
       case 'x':
-	if (!_fmt("%m/%d/%y", t, upcase))
+	if (!_fmt("%A %B %d %Y", t, upcase))
 	  return 0;
 	continue;
       case 'y':
-- 
[please cc replies if possible]

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019