Mail Archives: djgpp-workers/2001/01/14/04:46:27
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 -