X-Authentication-Warning: delorie.com: mail set sender to djgpp-workers-bounces using -f X-Recipient: djgpp-workers AT delorie DOT com X-Authenticated: #27081556 X-Provags-ID: V01U2FsdGVkX1/MlwlAJTfM517k1g5fJ74URTY4rhGRZgm5xq7fgM ASwn75aHqQSc/w From: Juan Manuel Guerrero To: djgpp-workers AT delorie DOT com Subject: Implementation of some conversion specifiers for strftime(). Date: Sat, 7 Jun 2008 14:54:13 +0200 User-Agent: KMail/1.9.5 MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline Message-Id: <200806071454.15316.juan.guerrero@gmx.de> X-Y-GMX-Trusted: 0 Reply-To: djgpp-workers AT delorie DOT com According to: the following conversion specifiers are missed in the current implementation of strftime(): %F, %G, %g and %V. They all concern the year, week and date representation according to ISO 8601:2000. A comparision with GNU libc shows that also the %P and %s specifiers are missed. The %s specifier computes the seconds since 1970-01-01 using mktime(). For some reason the result is different from what I get on my linux box, but it matches the result that was expected by the test file. I have not investigated this further. Also the GNU specific # flag, that changes the case for certain conversion specifiers has been implemented. The parsing of the format string now allows any order of the flags. The documentation has been adjusted accordingly. Also all still not documented flags has been explained. The test file has been modified to check the flags, the new modifiers E and O implemented in a previous patch and the new conversion specifiers. The result produced by the test file has been compared with the result produced by the test file if compiled on my linux box. The only difference is the result produced by %s. Please inspect wording of the texi file. Comments, objections, suggestions are welcome. Regards, Juan M. Guerrero 2008-06-07 Juan Manuel Guerrero Diffs against djgpp CVS head of 2008-05-30. * src/libc/ansi/time/strftime.c: Added the following specifiers: %F, %G, %g, %P, %s and %V. Added the following flag: #. * src/libc/ansi/time/strftime.txi: Document the specifiers: %F, %G, %g, %P, %s and %V and flag: #. * src/docs/kb/wc204.txh: Info about %F, %G, %g, %P, %s and %V specifiers and # flag added. * tests/libc/ansi/time/strftime.c: Tests for %F, %G, %g, %P, %s and %V specifiers, E and O modifiers and # flag added. Index: src/docs/kb/wc204.txi =================================================================== RCS file: /cvs/djgpp/djgpp/src/docs/kb/wc204.txi,v retrieving revision 1.185 diff -p -U3 -r1.185 wc204.txi --- src/docs/kb/wc204.txi 30 May 2008 20:57:19 -0000 1.185 +++ src/docs/kb/wc204.txi 7 Jun 2008 12:43:26 -0000 @@ -1145,5 +1145,9 @@ are now supported by @code{_doprnt} and family of functions. @findex strftime AT r{, and C99 conversion modifiers} -The modifiers @code{%}@code{E} and @code{%}@code{O} of the conversion specifiers -are ignored because djgpp only supports C/POSIX locale. +The modifiers @code{%E} and @code{%O} of the conversion specifiers are ignored +because djgpp only supports C/POSIX locale. + +@findex strftime AT r{, and C99 conversion specifiers} +The conversion specifiers @code{%F}, @code{%G}, @code{%g}, @code{%P}, @code{%s}, and @code{%V} have been added. +Also the @code{#} flag has been added. Index: src/libc/ansi/time/strftime.c =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/ansi/time/strftime.c,v retrieving revision 1.7 diff -p -U3 -r1.7 strftime.c --- src/libc/ansi/time/strftime.c 30 May 2008 20:57:22 -0000 1.7 +++ src/libc/ansi/time/strftime.c 7 Jun 2008 12:43:28 -0000 @@ -8,7 +8,17 @@ #include #include -#define TM_YEAR_BASE 1900 +#undef FALSE +#define FALSE 0 +#undef TRUE +#define TRUE 1 + +#define THURSDAY 4 +#define SATURDAY 6 +#define SUNDAY 7 + +#define TM_YEAR_BASE 1900 +#define IS_LEAP(year) ((((year) % 4) == 0) && ((((year) % 100) != 0) || (((year) % 400) == 0))) static const char *afmt[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", @@ -25,12 +35,97 @@ static const char *Bfmt[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; +static const char __ISO8601_date_format[] = "%Y-%m-%d"; char __dj_date_format[10] = "%m/%d/%y"; char __dj_time_format[16] = "%H:%M:%S"; static size_t gsize; static char *pt; +static __inline__ int +_compute_iso_wday_of_jan_01(const struct tm *t) +{ + /* + * ISO week starts with Monday = 1 and ends with Sunday = 7. + */ + + int wday_jan_01; + + + wday_jan_01 = (7 + t->tm_wday - t->tm_yday % 7) % 7; + if (wday_jan_01 == 0) + wday_jan_01 = 7; + + return wday_jan_01; +} + +static int +_compute_iso_standard_week(const struct tm *t) +{ + /* + * In ISO 8601:2000 standard week-based year system, + * weeks begin on a Monday and week 1 of the year ismore + * the week that includes January 4th, which is also + * the week that includes the first Thursday of the + * year, and is also the first week that contains at + * least four days in the year. + */ + + int iso_wday_of_jan_01, iso_week; + + + iso_wday_of_jan_01 = _compute_iso_wday_of_jan_01(t); /* Mon = 1, ..., Sun = 7. */ + iso_week = (6 + t->tm_yday - (6 + t->tm_wday) % 7) / 7; + if (iso_week == 0 && iso_wday_of_jan_01 > THURSDAY) /* Week belongs to the previous year. */ + { + if ((iso_wday_of_jan_01 == SUNDAY) || + (iso_wday_of_jan_01 == SATURDAY && !IS_LEAP(t->tm_year - 1 + TM_YEAR_BASE))) + iso_week = 52; + else + iso_week = 53; + } + else + { + int is_leap_year = IS_LEAP(t->tm_year + TM_YEAR_BASE); + int iso_wday_of_dec_31 = (365 + is_leap_year - t->tm_yday + (6 + t->tm_wday) % 7) % 7; /* Mon = 1, ..., Sun = 7. */ + + if (t->tm_yday > (360 + is_leap_year) && iso_wday_of_dec_31 < THURSDAY) /* Belongs to the following year. */ + iso_week = 1; + else /* Belongs to the current year. */ + iso_week++; + } + + return iso_week; +} + +static int +_compute_iso_week_based_year(const struct tm *t) +{ + /* + * ISO 8601:2000 standard week-based year system. + */ + + int iso_wday_of_jan_01, iso_year, week; + + + iso_wday_of_jan_01 = _compute_iso_wday_of_jan_01(t); /* Mon = 1, ..., Sun = 7. */ + week = (6 + t->tm_yday - (6 + t->tm_wday) % 7) / 7; + if (week == 0 && iso_wday_of_jan_01 > THURSDAY) /* Belongs to the previous year. */ + iso_year = t->tm_year - 1 + TM_YEAR_BASE; + else + { + int is_leap_year = IS_LEAP(t->tm_year + TM_YEAR_BASE); + int iso_wday_of_dec_31 = (365 + is_leap_year - t->tm_yday + (6 + t->tm_wday) % 7) % 7; /* Mon = 1, ..., Sun = 7. */ + + if (t->tm_yday > (360 + is_leap_year) && iso_wday_of_dec_31 < THURSDAY) /* Belongs to the following year. */ + iso_year = t->tm_year + 1 + TM_YEAR_BASE; + else /* Belongs to the current year. */ + iso_year = t->tm_year + TM_YEAR_BASE; + } + + return iso_year; +} + static int _add(const char *str, int upcase) { @@ -70,15 +165,24 @@ _fmt(const char *format, const struct tm { if (*format == '%') { - int pad = '0', space=' '; - if (format[1] == '_') - pad = space = ' ', format++; - if (format[1] == '-') - pad = space = 0, format++; - if (format[1] == '0') - pad = space = '0', format++; - if (format[1] == '^') - upcase = 1, format++; + int flag_seen, pad = '0', space=' ', swap_case = FALSE; + + /* Parse flags. */ + do { + flag_seen = FALSE; + if (format[1] == '_') + flag_seen = TRUE, pad = space = ' ', format++; + if (format[1] == '-') + flag_seen = TRUE, pad = space = 0, format++; + if (format[1] == '0') + flag_seen = TRUE, pad = space = '0', format++; + if (format[1] == '^')more + flag_seen = TRUE, upcase = TRUE, format++; + if (format[1] == '#') + flag_seen = TRUE, swap_case = TRUE, format++; + } while (flag_seen); + + /* Parse modifiers. */ if (format[1] == 'E' || format[1] == 'O') format++; /* Only C/POSIX locale is supported. */ @@ -88,18 +192,24 @@ _fmt(const char *format, const struct tm --format; break; case 'A': + if (swap_case) + upcase = TRUE; if (t->tm_wday < 0 || t->tm_wday > 6) return 0; if (!_add(Afmt[t->tm_wday], upcase)) return 0; continue; case 'a': + if (swap_case) + upcase = TRUE; if (t->tm_wday < 0 || t->tm_wday > 6) return 0; if (!_add(afmt[t->tm_wday], upcase)) return 0; continue; case 'B': + if (swap_case) + upcase = TRUE; if (t->tm_mon < 0 || t->tm_mon > 11) return 0; if (!_add(Bfmt[t->tm_mon], upcase)) @@ -107,6 +217,8 @@ _fmt(const char *format, const struct tm continue; case 'b': case 'h': + if (swap_case) + upcase = TRUE; if (t->tm_mon < 0 || t->tm_mon > 11) return 0; if (!_add(bfmt[t->tm_mon], upcase)) @@ -132,6 +244,18 @@ _fmt(const char *format, const struct tm if (!_conv(t->tm_mday, 2, pad)) return 0; continue; + case 'F': + if (!_fmt(__ISO8601_date_format, t, upcase)) + return 0; + continue; + case 'G': + if (!_conv(_compute_iso_week_based_year(t), 4, pad)) + return 0; + continue; + case 'g': + if (!_conv(_compute_iso_week_based_year(t) % 100, 2, pad)) + return 0; + continue; case 'H': if (!_conv(t->tm_hour, 2, pad)) return 0; @@ -166,8 +290,13 @@ _fmt(const char *format, const struct tm 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)) + upcase = swap_case ? FALSE : TRUE; + if (!_add(t->tm_hour >= 12 ? "pm" : "am", upcase)) return 0; continue; case 'R': @@ -182,6 +311,17 @@ _fmt(const char *format, const struct tm if (!_conv(t->tm_sec, 2, pad)) return 0; continue; + case 's': + { + struct tm _t; + time_t _time; + + _t = *t; + _time = mktime(&_t); + if (_time == (time_t)-1 || !_conv(_time, -1, pad)) + return 0; + } + continue; case 'T': if (!_fmt("%H:%M:%S", t, upcase)) return 0; @@ -199,6 +339,10 @@ _fmt(const char *format, const struct tm 2, pad)) return 0; continue; + case 'V': + if (!_conv(_compute_iso_standard_week(t), 2, pad)) + return 0; + continue; case 'W': if (!_conv((t->tm_yday + 7 - (t->tm_wday ? (t->tm_wday - 1) : 6)) @@ -218,12 +362,10 @@ _fmt(const char *format, const struct tm 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; @@ -234,9 +376,27 @@ _fmt(const char *format, const struct tm return 0; continue; case 'Z': - if (!t->tm_zone || !_add(t->tm_zone, upcase)) + if (t->tm_zone) + { + char tm_zone[32]; + + strcpy(tm_zone, t->tm_zone); + if (swap_case) + { + upcase = FALSE; + strlwr(tm_zone); + } + if (!_add(tm_zone, upcase)) + return 0; + } + else return 0; continue; + case '+': + /* + * The date and time in date(1) format. An extension introduced + * with Olson's timezone package and still not supported. + */ case '%': /* * X311J/88-090 (4.12.3.5): if conversion char is @@ -260,7 +420,7 @@ strftime(char *s, size_t maxsize, const pt = s; if ((gsize = maxsize) < 1) return 0; - if (_fmt(format, t, 0)) + if (_fmt(format, t, FALSE)) { *pt = '\0'; return maxsize - gsize; Index: src/libc/ansi/time/strftime.txh =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/ansi/time/strftime.txh,v retrieving revision 1.7 diff -p -U3 -r1.7 strftime.txh --- src/libc/ansi/time/strftime.txh 30 May 2008 20:57:23 -0000 1.7 +++ src/libc/ansi/time/strftime.txh 7 Jun 2008 12:43:28 -0000 @@ -41,12 +41,20 @@ The abbreviated month name (@code{Oct}) @item %C -Short for @code{%a %b %e %H:%M:%S %Y} (@code{Fri Oct 1 15:30:34 1993}) +The century number (year/100) as a 2-digit integer (@code{19}) @item %c Short for @code{%m/%d/%y %H:%M:%S} (@code{10/01/93 15:30:34}) +@item %D + +Short for @code{%m/%d/%y} (@code{10/01/93}) + +@item %d + +The day of the month, zero padded to two characters (@code{02}) + @item %Ex In some locales, the @code{E} modifier selects alternative representations of certain conversion specifiers @code{x}. But in the "C" locale supported @@ -57,13 +65,20 @@ mapped to @code{%C}. The day of the month, blank padded to two characters (@code{ 2}) -@item %D +@item %F -Short for @code{%m/%d/%y} (@code{10/01/93}) +The ISO 8601:2000 date format, in the form @code{%Y-%m-%d} (@code{1993-10-01}) -@item %d +@item %G -The day of the month, zero padded to two characters (@code{02}) +The ISO 8601:2000 standard week-based year with century as a decimal number. +The 4-digit year corresponding to the ISO week number (see @code{%V}). This has the +same format and value as @code{%Y}, except that if the ISO week number belongs to the +previous or next year, that year is used instead (@code{1993}) + +@item %g + +Like @code{%G}, but without century, i.e., with a 2-digit year (@code{93}) @item %H @@ -75,7 +90,7 @@ The hour (1-12), zero padded to two char @item %j -The Julian day, zero padded to three characters (@code{275}) +The Julian day (1-366), zero padded to three characters (@code{275}) @item %k @@ -87,7 +102,7 @@ The hour (1-12), space padded to two cha @item %M -The minutes, zero padded to two characters (@code{30}) +The minutes (0-59), zero padded to two characters (@code{30}) @item %m @@ -107,6 +122,10 @@ mapped to @code{%H}. AM or PM (@code{PM}) +@item %P + +Like @code{%p} but in lowercase: am or pm (@code{pm}) + @item %R Short for @code{%H:%M} (@code{15:30}) @@ -119,6 +138,10 @@ Short for @code{%I:%M:%S %p} (@code{03:3 The seconds, zero padded to two characters (@code{35}) +@item %s + +The seconds since the Epoch, i.e., since 1970-01-01 00:00:00 UTC (@code{}) + @item %T Short for @code{%H:%M:%S} (@code{15:30:35}) @@ -134,7 +157,12 @@ the year, zero padded to two characters @item %u -The day of the week (1-7) (@code{6}) +The day of the week (1-7), Monday being 1 (@code{6}) + +@item %V + +The ISO week of the year (01-53), where weeks start on Monday, with the first +week defined by the first Thursday of the year, zero padded to two characters (@code{39}) @item %W @@ -143,7 +171,7 @@ the year, zero padded to two characters @item %w -The day of the week (0-6) (@code{5}) +The day of the week (0-6), Sunday being 0 (@code{5}) @item %x @@ -165,12 +193,53 @@ The year, zero padded to four digits (@c The timezone abbreviation (@code{EDT}) +@item %z + +The time-zone as hour offset from GMT in the ISO 8601:2000 standard format (@code{+hhmm} or @code{-hhmm}), +or by no characters if no timezone is determinable. Required to emit RFC 822-conformant +dates using @code{%a, %d %b %Y %H:%M:%S %z} (@code{Fri, 01 Oct 1993 03:30:34 +}) + +@item %+ + +The date and time in date(1) format. Not supported in djgpp + @item %% A percent symbol (@code{%}) @end table + +The following flag characters, preceding the conversion specifier characters described +above, eare permitted: + +@table @code + +@item _ + +(underscore) Pad a numeric result string with spaces + + +@item - + +(dash) Do not pad a numeric result string + +@item 0 + +Pad a numeric result string with zeros even if the conversion specifier character +uses space-padding by default + +@item ^ + +Convert alphabetic characters in result string to upper case + +@item # + +Swap the case of the result string. (This flag only works with certain conversion specifier characters, +and of these, it is only really useful with %Z). + +@end table + @subheading Return Value The number of characters stored. Index: tests/libc/ansi/time/strftime.c =================================================================== RCS file: /cvs/djgpp/djgpp/tests/libc/ansi/time/strftime.c,v retrieving revision 1.3 diff -p -U3 -r1.3 strftime.c --- tests/libc/ansi/time/strftime.c 26 May 2002 16:12:46 -0000 1.3 +++ tests/libc/ansi/time/strftime.c 7 Jun 2008 12:43:34 -0000 @@ -1,7 +1,6 @@ /* this is the Autoconf test program that GNU programs use to detect if strftime is working. - Apart from missing formats (which probably can be ignored) - it seems that the results from "%c" and "%C" are wrong. + The results are checked against strftime() from GNU libc. */ #include @@ -9,7 +8,10 @@ #include #include #include +#ifdef DJGPP +/* Only to be able to compile the code with linux too. */ #include +#endif static int compare (const char *fmt, const struct tm *tm, const char *expected) @@ -37,7 +39,9 @@ main (void) /* This is necessary to make strftime give consistent zone strings and e.g., seconds since the epoch (%s). */ +#ifdef DJGPP putenv (unconst("TZ=GMT0", char *)); +#endif #undef CMP #define CMP(Fmt, Expected) n_fail += compare ((Fmt), tm, (Expected)) @@ -53,7 +57,7 @@ main (void) CMP ("%H", "13"); CMP ("%I", "01"); CMP ("%M", "06"); - CMP ("%M", "06"); + CMP ("%P", "pm"); CMP ("%R", "13:06"); /* POSIX.2 */ CMP ("%S", "07"); CMP ("%T", "13:06:07"); /* POSIX.2 */ @@ -90,7 +94,74 @@ main (void) CMP ("%y", "70"); CMP ("%z", "+0000"); /* GNU */ - exit (n_fail ? 1 : 0); -} + /* Check GNU flag #. Inverts the case. */ + CMP ("%#A", "FRIDAY"); + CMP ("%#^A", "FRIDAY"); + CMP ("%^#A", "FRIDAY"); + CMP ("%#a", "FRI"); + CMP ("%#^a", "FRI"); + CMP ("%^#a", "FRI"); + CMP ("%#B", "JANUARY"); + CMP ("%#^B", "JANUARY"); + CMP ("%^#B", "JANUARY"); + CMP ("%#b", "JAN"); + CMP ("%#^b", "JAN"); + CMP ("%^#b", "JAN"); + CMP ("%p", "PM"); + CMP ("%#p", "pm"); + CMP ("%#Z", "gmt"); + /* Check E and O mofifier. Ignore it. */ + CMP ("%EC", "19"); + CMP ("%Ec", "Fri Jan 9 13:06:07 1970"); + CMP ("%EX", "13:06:07"); + CMP ("%Ex", "01/09/70"); + CMP ("%EY", "1970"); + CMP ("%Ey", "70"); + CMP ("%Od", "09"); + CMP ("%Oe", " 9"); + CMP ("%OH", "13"); + CMP ("%OI", "01"); + CMP ("%OM", "06"); + CMP ("%Om", "01"); + CMP ("%OS", "07"); + CMP ("%Ou", "5"); + CMP ("%OU", "01"); + CMP ("%OV", "02"); + CMP ("%OW", "01"); + CMP ("%Ow", "5"); + CMP ("%Oy", "70"); + + /* Check G, g and V specifiers. + Examples from: + + */ + t = 883441421; /* Tue Dec 30 0:23:41 1997. */ + tm = gmtime (&t); + CMP ("%V", "01"); + CMP ("%W", "52"); + CMP ("%G", "1998"); + CMP ("%Y", "1997"); + CMP ("%g", "98"); + CMP ("%y", "97"); + t = 915245172; /* Sat Jan 2 02:46:12 1999. */ + tm = gmtime (&t); + CMP ("%V", "53"); + CMP ("%W", "00"); + CMP ("%G", "1998"); + CMP ("%Y", "1999"); + CMP ("%g", "98"); + CMP ("%y", "99"); + + t = 1212808584; /* Sat Jun 7 03:16:24 2008. */ + tm = gmtime (&t); + CMP ("%V", "23"); + CMP ("%W", "22"); + CMP ("%G", "2008"); + CMP ("%Y", "2008"); + CMP ("%g", "08"); + CMP ("%y", "08"); + + exit (n_fail ? 1 : 0); +}