X-Authentication-Warning: delorie.com: mail set sender to djgpp-workers-bounces using -f Date: Thu, 18 Mar 2004 14:02:37 -0700 From: Brian Inglis Subject: Re: C99 strftime and Related Changes In-reply-to: <94ti5092bc7d45eq1bm28l44jbr3ujv6nj@4ax.com> To: djgpp-workers Message-id: Organization: Systematic Software MIME-version: 1.0 X-Mailer: Forte Agent 1.93/32.576 English (American) Content-type: text/plain; charset=us-ascii Content-transfer-encoding: 7BIT References: <200403180757 DOT IAA22862 AT lws256 DOT emw DOT erisoft DOT se> <94ti5092bc7d45eq1bm28l44jbr3ujv6nj AT 4ax DOT com> Reply-To: djgpp-workers AT delorie DOT com Errors-To: nobody AT delorie DOT com X-Mailing-List: djgpp-workers AT delorie DOT com X-Unsubscribes-To: listserv AT delorie DOT com Precedence: bulk On Thu, 18 Mar 2004 03:07:04 -0700, Brian Inglis wrote: >13 files, 1748 lines, 57294 bytes Here they are inline, then. -- Thanks. Take care, Brian Inglis CONTENTS C99 Time Related Changes liblocal.02/src/ansi/locale/setlocal.pat to liblocal.02/src/ansi/locale/setlocal.c liblocal.02/src/ansi/time/strftime.pat to liblocal.02/src/ansi/time/strftime.c djgpp/src/libc/ansi/time/strftime.pat to djgpp/src/libc/ansi/time/strftime.c djgpp/src/libc/ansi/time/strftxh.pat to djgpp/src/libc/ansi/time/strftime.txh djgpp/tests/libc/ansi/time/makefile.pat to djgpp/tests/libc/ansi/time/makefile djgpp/tests/libc/ansi/time/strftime.pat to djgpp/tests/libc/ansi/time/strftime.c djgpp/tests/libc/ansi/time/xstrftm.pat to djgpp/tests/libc/ansi/time/xstrftm.c djgpp/tests/libc/ansi/time/strftiml.pat to djgpp/tests/libc/ansi/time/strftiml.c NEW djgpp/tests/libc/ansi/time/strftimt.pat to djgpp/tests/libc/ansi/time/strftimt. NEW CHANGES C99 Time Related Changes liblocal.02/src/ansi/locale/setlocal.c fix problem resetting standard locale and returning updated locale name liblocal.02/src/ansi/time/strftime.c src/libc/ansi/time/strftime.c #define required constants; add YEAR() conversion macro and ISLEAPYEAR() test macro; add weekday enum definitions; add iso_year() and iso_week() functions; accept and ignore C99 E and O modifiers; add C99 %F, %G, %g, %V formats; add %s (GNU) format; fix %z ISO 8601/RFC 822 time zone results src/libc/ansi/time/strftime.txh add new formats; add all modifiers; make descriptions consistent tests/libc/ansi/time/makefile add strftiml.c and strftimt.c tests tests/libc/ansi/time/strftime.c change inline macro calls to table driven tests; add new tests and results for all formats, including unsupported letters tests/libc/ansi/time/xstrftm.c add standard and current locale changes; output each locale name tests/libc/ansi/time/strftiml.c list all formats, including unsupported letters tests/libc/ansi/time/strftimt.c exception tester covering all formats; table driven supporting locales and time zones; many corner cases for non/ISO week and weekdays --- setlocal.c 2002-07-17 02:16:36.000000000 -0600 +++ setlocal.c 2004-02-24 01:57:18.000000000 -0700 @@ -465,15 +465,17 @@ setlocale(int category, const char *loca if (locale[0] == '\0') { const char *env; - if ((env = getenv ("LANG")) != NULL) + if ((env = getenv (cat[i].env)) != NULL) { locale = env; } - if ((env = getenv (cat[i].env)) != NULL) + else + if ((env = getenv ("LC_ALL")) != NULL) { locale = env; } - if ((env = getenv ("LC_ALL")) != NULL) + else + if ((env = getenv ("LANG")) != NULL) { locale = env; } @@ -481,7 +483,10 @@ setlocale(int category, const char *loca if ((stricmp(locale, "C") == 0) || (stricmp(locale, "POSIX") == 0)) { if (cat[i].reset() == 0) + { honored = 0; + continue; + } } else { @@ -559,10 +564,10 @@ setlocale(int category, const char *loca honored = 0; continue; } - - strncpy(lc_current[i], locale, LC_MAXNAMESIZE); - lc_current[i][LC_MAXNAMESIZE - 1] = '\0'; } + + strncpy(lc_current[i], locale, LC_MAXNAMESIZE); + lc_current[i][LC_MAXNAMESIZE - 1] = '\0'; } } if (segment != -1) @@ -607,8 +612,8 @@ setlocale(int category, const char *loca #include unsigned char __dj_collate_table[256]; -char __dj_date_format[10] = "%m/%d/%y"; -char __dj_time_format[16] = "%H:%M:%S"; +extern char __dj_date_format[10]; +extern char __dj_time_format[16]; int main(int ac, char *av[]) --- strftime.c 2002-06-08 04:18:06.000000000 -0600 +++ strftime.c 2004-02-29 00:04:48.000000000 -0700 @@ -1,13 +1,57 @@ +/* Copyright (C) 2003 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 2001 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 */ - #include #include #include +#include +#if 0 /* DEBUG */ +#include +#endif /* 0 */ + + + +#define TM_YEAR_BASE 1900 /* tm year epoch base */ +#define LEAP_CENTURIES 4 /* centuries between leap centuries */ +#define CENTURY_YEARS 100 /* years per century */ +#define LEAP_CENTURY_YEARS (LEAP_CENTURIES * CENTURY_YEARS) /* years */ +#define LEAP_YEARS 4 /* years between leap years */ +#define YEAR_DAYS 365 /* days in common year */ +#define HOUR_MINS 60 /* minutes per hour */ +#define MIN_SECS 60 /* seconds per minute */ + + +#define FMT_DATE_ISO "%Y-%m-%d" /* ISO date format */ + + +/* convert tm year to Gregorian year */ +#define YEAR(tm_y) ((tm_y) + TM_YEAR_BASE) +/* test if Gregorian year is a leap year */ +#define IS_LEAP_YEAR(y) (!((y) % LEAP_YEARS) && (((y) % CENTURY_YEARS) \ + || !((y) % LEAP_CENTURY_YEARS))) + -#define TM_YEAR_BASE 1900 + +/* tm wday values */ +typedef enum weekday +{ + SUNDAY, + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY, + WEEK_DAYS + +} weekday; /* enum */ + + + + +/* locale dependent strings */ static const char *afmt[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", @@ -24,11 +68,92 @@ static const char *Bfmt[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; + + +/* locale dependent formats */ + char __dj_date_format[10] = "%m/%d/%y"; char __dj_time_format[16] = "%H:%M:%S"; -static size_t gsize; -static char *pt; + +/* internal global variables */ + +static size_t gsize; /* user buffer space left */ +static char *pt; /* next char to use in buffer */ + + + +/* calculate ISO year from Gregorian year and days of year and week */ +static int +iso_year( int year, int doy, int dow) +{ + int ISOyear = year; /* ISO year may not be same as Gregorian */ + int near_thu = doy + THURSDAY - (dow ? dow : WEEK_DAYS); + /* day in year of closest Thu to date */ + + +#if 0 /* DEBUG */ + printf( "year %d doy %d dow %d thu %d near thu %d max %d \n", + year, doy, dow, THURSDAY, near_thu, YEAR_DAYS + IS_LEAP_YEAR( year )); +#endif /* 0 */ + +/* if nearest Thu before start of year, then it's in last year */ + if (near_thu < 0) + { + --ISOyear; + } + else +/* if nearest Thu after end of year, then it's in next year */ + if (near_thu >= YEAR_DAYS + IS_LEAP_YEAR( year )) + { + ++ISOyear; + } + + return ISOyear; + +} /* iso_year() */ + + + +/* calculate ISO week from Gregorian year and days of year and week */ +static int +iso_week( int year, int doy, int dow) +{ + int near_thu = doy + THURSDAY - (dow ? dow : WEEK_DAYS); + /* day in year of closest Thu to date */ + + +#if 0 /* DEBUG */ + printf( "year %d doy %d dow %d thu %d near thu %d" + " max prev %d this %d \n", + year, doy, dow, THURSDAY, near_thu, + YEAR_DAYS + IS_LEAP_YEAR( year - 1 ), + YEAR_DAYS + IS_LEAP_YEAR( year )); +#endif /* 0 */ + +/* if nearest Thu before start of year, then it's in last year */ + if (near_thu < 0) + { +/* adjust day to make it part of last year */ + near_thu += YEAR_DAYS + IS_LEAP_YEAR( year - 1 ); + } + else +/* if nearest Thu after end of year, then it's in next year */ + if (near_thu >= YEAR_DAYS + IS_LEAP_YEAR( year )) + { +/* adjust day to make it part of next year */ + near_thu -= YEAR_DAYS + IS_LEAP_YEAR( year ); + } + +#if 0 /* DEBUG */ + printf( "near thu %d week %d \n", near_thu, near_thu / WEEK_DAYS + 1); +#endif /* 0 */ + + return near_thu / WEEK_DAYS + 1; /* 0-365 -> 0-52 -> 1-53 */ + +} /* iso_week() */ + + static int _add(const char *str, int upcase) @@ -47,7 +172,7 @@ _add(const char *str, int upcase) static int _conv(int n, int digits, char pad) { - static char buf[10]; + static char buf[12]; char *p = buf + sizeof(buf) - 2; do { @@ -70,6 +195,10 @@ _fmt(const char *format, const struct tm if (*format == '%') { int pad = '0', space=' '; + int era = 0; /* locale era modifier */ + int ordinal = 0; /* locale ordinal modifier */ + + if (format[1] == '_') pad = space = ' ', format++; if (format[1] == '-') @@ -78,6 +207,10 @@ _fmt(const char *format, const struct tm pad = space = '0', format++; if (format[1] == '^') upcase = 1, format++; + if (format[1] == 'E') /* if locale era modifier */ + era = 1, format++; + if (format[1] == 'O') /* if locale ordinal modifier */ + ordinal = 1, format++; switch(*++format) { @@ -129,6 +262,16 @@ _fmt(const char *format, const struct tm if (!_conv(t->tm_mday, 2, pad)) return 0; continue; + +/* + * %F is equivalent to ``%Y-%m-%d'' (the ISO 8601 date format). + * [tm_year, tm_mon, tm_mday] + */ + case 'F': + if (!_fmt( FMT_DATE_ISO, t, upcase)) + return 0; + continue; + case 'H': if (!_conv(t->tm_hour, 2, pad)) return 0; @@ -179,6 +322,29 @@ _fmt(const char *format, const struct tm if (!_conv(t->tm_sec, 2, pad)) return 0; continue; + +/* + * + %s is replaced by the number of seconds since the epoch + * 1970-01-01 00:00:00 as a decimal number. [all] + */ + case 's': + { +/* copy user tm in case modified */ + struct tm tm = *t; +/* convert to seconds since epoch */ + time_t secs = mktime( &tm ); + +#if 0 /* DEBUG */ + printf( "\n %u s \n", secs); +#endif /* 0 */ + + if (secs == (time_t)-1) /* if invalid time, quit */ + return 0; + if (!_conv( secs, 10, '\0')) /* if conversion failed, quit */ + return 0; + continue; + } + case 'T': if (!_fmt("%H:%M:%S", t, upcase)) return 0; @@ -196,6 +362,37 @@ _fmt(const char *format, const struct tm 2, pad)) return 0; continue; + +/* + * %V is replaced by the ISO 8601 week number (see above) + * as a decimal number (01-53). [tm_year, tm_wday, tm_yday] + */ + case 'V': + if (!_conv( iso_week( YEAR( t->tm_year ), t->tm_yday, t->tm_wday), + 2, pad)) + return 0; + continue; + +/* + * %g is replaced by the last 2 digits of the week-based year (see above) + * as a decimal number (00-99). [tm_year, tm_wday, tm_yday] + */ + case 'g': + if (!_conv( iso_year( YEAR( t->tm_year ), t->tm_yday, t->tm_wday) + % CENTURY_YEARS, 2, pad)) + return 0; + continue; + +/* + * %G is replaced by the week-based year (see above) as a + * decimal number (e.g., 1997). [tm_year, tm_wday, tm_yday] + */ + case 'G': + if (!_conv( iso_year( YEAR( t->tm_year ), t->tm_yday, t->tm_wday), + 4, pad)) + return 0; + continue; + case 'W': if (!_conv((t->tm_yday + 7 - (t->tm_wday ? (t->tm_wday - 1) : 6)) @@ -215,24 +412,31 @@ _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; case 'z': - if (!_add(t->__tm_gmtoff<0 ? "-" : "+", 0)) - return 0; - if (!_conv(t->__tm_gmtoff<0 ? -t->__tm_gmtoff : t->__tm_gmtoff, 4, pad)) - return 0; +/* if time zone available, do conversion */ + if (t->tm_isdst >= 0 && t->__tm_zone && *t->__tm_zone) + { + int offset = abs( t->__tm_gmtoff ) / MIN_SECS; + + if (!_add(t->__tm_gmtoff<0 ? "-" : "+", 0)) + return 0; + if (!_conv( offset / HOUR_MINS * 100 + offset % HOUR_MINS, + 4, pad)) + return 0; + } continue; case 'Z': - if (!t->tm_zone || !_add(t->tm_zone, upcase)) - return 0; +/* if time zone available, do conversion */ + if (t->tm_isdst >= 0 && t->__tm_zone && *t->__tm_zone) + if (!_add(t->__tm_zone, upcase)) + return 0; continue; case '%': /* Index: strftime.c =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/ansi/time/strftime.c,v retrieving revision 1.6 diff -p -u -t -r1.6 strftime.c --- strftime.c 8 Nov 2003 12:19:40 -0000 1.6 +++ strftime.c 17 Mar 2004 06:36:05 -0000 @@ -6,8 +6,52 @@ #include #include #include +#include +#if 0 /* DEBUG */ +#include +#endif /* 0 */ -#define TM_YEAR_BASE 1900 + + +#define TM_YEAR_BASE 1900 /* tm year epoch base */ +#define LEAP_CENTURIES 4 /* centuries between leap centuries */ +#define CENTURY_YEARS 100 /* years per century */ +#define LEAP_CENTURY_YEARS (LEAP_CENTURIES * CENTURY_YEARS) /* years */ +#define LEAP_YEARS 4 /* years between leap years */ +#define YEAR_DAYS 365 /* days in common year */ +#define HOUR_MINS 60 /* minutes per hour */ +#define MIN_SECS 60 /* seconds per minute */ + + +#define FMT_DATE_ISO "%Y-%m-%d" /* ISO date format */ + + +/* convert tm year to Gregorian year */ +#define YEAR(tm_y) ((tm_y) + TM_YEAR_BASE) +/* test if Gregorian year is a leap year */ +#define IS_LEAP_YEAR(y) (!((y) % LEAP_YEARS) && (((y) % CENTURY_YEARS) \ + || !((y) % LEAP_CENTURY_YEARS))) + + + +/* tm wday values */ +typedef enum weekday +{ + SUNDAY, + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY, + WEEK_DAYS + +} weekday; /* enum */ + + + + +/* locale dependent strings */ static const char *afmt[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", @@ -24,11 +68,92 @@ static const char *Bfmt[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; + + +/* locale dependent formats */ + char __dj_date_format[10] = "%m/%d/%y"; char __dj_time_format[16] = "%H:%M:%S"; -static size_t gsize; -static char *pt; + +/* internal global variables */ + +static size_t gsize; /* user buffer space left */ +static char *pt; /* next char to use in buffer */ + + + +/* calculate ISO year from Gregorian year and days of year and week */ +static int +iso_year( int year, int doy, int dow) +{ + int ISOyear = year; /* ISO year may not be same as Gregorian */ + int near_thu = doy + THURSDAY - (dow ? dow : WEEK_DAYS); + /* day in year of closest Thu to date */ + + +#if 0 /* DEBUG */ + printf( "year %d doy %d dow %d thu %d near thu %d max %d \n", + year, doy, dow, THURSDAY, near_thu, YEAR_DAYS + IS_LEAP_YEAR( year )); +#endif /* 0 */ + +/* if nearest Thu before start of year, then it's in last year */ + if (near_thu < 0) + { + --ISOyear; + } + else +/* if nearest Thu after end of year, then it's in next year */ + if (near_thu >= YEAR_DAYS + IS_LEAP_YEAR( year )) + { + ++ISOyear; + } + + return ISOyear; + +} /* iso_year() */ + + + +/* calculate ISO week from Gregorian year and days of year and week */ +static int +iso_week( int year, int doy, int dow) +{ + int near_thu = doy + THURSDAY - (dow ? dow : WEEK_DAYS); + /* day in year of closest Thu to date */ + + +#if 0 /* DEBUG */ + printf( "year %d doy %d dow %d thu %d near thu %d" + " max prev %d this %d \n", + year, doy, dow, THURSDAY, near_thu, + YEAR_DAYS + IS_LEAP_YEAR( year - 1 ), + YEAR_DAYS + IS_LEAP_YEAR( year )); +#endif /* 0 */ + +/* if nearest Thu before start of year, then it's in last year */ + if (near_thu < 0) + { +/* adjust day to make it part of last year */ + near_thu += YEAR_DAYS + IS_LEAP_YEAR( year - 1 ); + } + else +/* if nearest Thu after end of year, then it's in next year */ + if (near_thu >= YEAR_DAYS + IS_LEAP_YEAR( year )) + { +/* adjust day to make it part of next year */ + near_thu -= YEAR_DAYS + IS_LEAP_YEAR( year ); + } + +#if 0 /* DEBUG */ + printf( "near thu %d week %d \n", near_thu, near_thu / WEEK_DAYS + 1); +#endif /* 0 */ + + return near_thu / WEEK_DAYS + 1; /* 0-365 -> 0-52 -> 1-53 */ + +} /* iso_week() */ + + static int _add(const char *str, int upcase) @@ -47,7 +172,7 @@ _add(const char *str, int upcase) static int _conv(int n, int digits, char pad) { - static char buf[10]; + static char buf[12]; char *p = buf + sizeof(buf) - 2; do { @@ -70,6 +195,10 @@ _fmt(const char *format, const struct tm if (*format == '%') { int pad = '0', space=' '; + int era = 0; /* locale era modifier */ + int ordinal = 0; /* locale ordinal modifier */ + + if (format[1] == '_') pad = space = ' ', format++; if (format[1] == '-') @@ -78,6 +207,10 @@ _fmt(const char *format, const struct tm pad = space = '0', format++; if (format[1] == '^') upcase = 1, format++; + if (format[1] == 'E') /* if locale era modifier */ + era = 1, format++; + if (format[1] == 'O') /* if locale ordinal modifier */ + ordinal = 1, format++; switch(*++format) { @@ -129,6 +262,16 @@ _fmt(const char *format, const struct tm if (!_conv(t->tm_mday, 2, pad)) return 0; continue; + +/* + * %F is equivalent to ``%Y-%m-%d'' (the ISO 8601 date format). + * [tm_year, tm_mon, tm_mday] + */ + case 'F': + if (!_fmt( FMT_DATE_ISO, t, upcase)) + return 0; + continue; + case 'H': if (!_conv(t->tm_hour, 2, pad)) return 0; @@ -179,6 +322,29 @@ _fmt(const char *format, const struct tm if (!_conv(t->tm_sec, 2, pad)) return 0; continue; + +/* + * + %s is replaced by the number of seconds since the epoch + * 1970-01-01 00:00:00 as a decimal number. [all] + */ + case 's': + { +/* copy user tm in case modified */ + struct tm tm = *t; +/* convert to seconds since epoch */ + time_t secs = mktime( &tm ); + +#if 0 /* DEBUG */ + printf( "\n %u s \n", secs); +#endif /* 0 */ + + if (secs == (time_t)-1) /* if invalid time, quit */ + return 0; + if (!_conv( secs, 10, '\0')) /* if conversion failed, quit */ + return 0; + continue; + } + case 'T': if (!_fmt("%H:%M:%S", t, upcase)) return 0; @@ -196,6 +362,37 @@ _fmt(const char *format, const struct tm 2, pad)) return 0; continue; + +/* + * %V is replaced by the ISO 8601 week number (see above) + * as a decimal number (01-53). [tm_year, tm_wday, tm_yday] + */ + case 'V': + if (!_conv( iso_week( YEAR( t->tm_year ), t->tm_yday, t->tm_wday), + 2, pad)) + return 0; + continue; + +/* + * %g is replaced by the last 2 digits of the week-based year (see above) + * as a decimal number (00-99). [tm_year, tm_wday, tm_yday] + */ + case 'g': + if (!_conv( iso_year( YEAR( t->tm_year ), t->tm_yday, t->tm_wday) + % CENTURY_YEARS, 2, pad)) + return 0; + continue; + +/* + * %G is replaced by the week-based year (see above) as a + * decimal number (e.g., 1997). [tm_year, tm_wday, tm_yday] + */ + case 'G': + if (!_conv( iso_year( YEAR( t->tm_year ), t->tm_yday, t->tm_wday), + 4, pad)) + return 0; + continue; + case 'W': if (!_conv((t->tm_yday + 7 - (t->tm_wday ? (t->tm_wday - 1) : 6)) @@ -215,24 +412,31 @@ _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; case 'z': - if (!_add(t->__tm_gmtoff<0 ? "-" : "+", 0)) - return 0; - if (!_conv(t->__tm_gmtoff<0 ? -t->__tm_gmtoff : t->__tm_gmtoff, 4, pad)) - return 0; +/* if time zone available, do conversion */ + if (t->tm_isdst >= 0 && t->__tm_zone && *t->__tm_zone) + { + int offset = abs( t->__tm_gmtoff ) / MIN_SECS; + + if (!_add(t->__tm_gmtoff<0 ? "-" : "+", 0)) + return 0; + if (!_conv( offset / HOUR_MINS * 100 + offset % HOUR_MINS, + 4, pad)) + return 0; + } continue; case 'Z': - if (!t->tm_zone || !_add(t->tm_zone, upcase)) - return 0; +/* if time zone available, do conversion */ + if (t->tm_isdst >= 0 && t->__tm_zone && *t->__tm_zone) + if (!_add(t->__tm_zone, upcase)) + return 0; continue; case '%': /* Index: strftime.txh =================================================================== RCS file: /cvs/djgpp/djgpp/src/libc/ansi/time/strftime.txh,v retrieving revision 1.6 diff -u -t -F^@[[:alpha:]] -r1.6 strftime.txh --- strftime.txh 8 Nov 2003 12:19:40 -0000 1.6 +++ strftime.txh 17 Mar 2004 06:39:48 -0000 @@ -5,145 +5,187 @@ @example #include -size_t strftime(char *buf, size_t n, const char *format, - const struct tm *time_info); +size_t strftime(char * restrict string, + size_t maxsize, + const char * restrict format, + const struct tm * restrict time_info); + @end example @subheading Description -This function formats the time data in @var{time_info} according to the -given @var{format} and stores it in @var{buf}, not exceeding @var{n} -bytes. - -The format string is like @code{printf} in that any character other than -@code{%} is added to the output string, and for each character following -a @code{%} a pattern is added to the string as follows, with the -examples as if the time was Friday, October 1, 1993, at 03:30:34 PM EDT: +This function formats the calendar time data in @var{time_info} +according to the given format string @var{format} +and stores it in the output string @var{string}, +not exceeding @var{maxsize} bytes. + +The format string is like @code{sprintf} in that any character other +than the flag character @code{%} is added to the output string, and +depending on the conversion specifier character after the @code{%}, +characters are added to the output string, with the following examples +shown as if the time was Friday, October 1, 1993, at 03:30:34 PM EDT: @table @code @item %A -The full weekday name (@code{Friday}) +The full weekday name represented according to the +current locale: (@code{Friday}) in the C locale @item %a -The abbreviated weekday name (@code{Fri}) +The abbreviated weekday name represented according to +the current locale: (@code{Fri}) in the C locale @item %B -The full month name (@code{October}) +The full month name represented according to the +current locale: (@code{October}) in the C locale @item %b @itemx %h -The abbreviated month name (@code{Oct}) +The abbreviated month name represented according to +the current locale: (@code{Oct}) in the C locale @item %C -Short for @code{%a %b %e %H:%M:%S %Y} (@code{Fri Oct 1 15:30:34 1993}) +The century of the year, zero padded to two digits (@code{19}) @item %c -Short for @code{%m/%d/%y %H:%M:%S} (@code{10/01/93 15:30:34}) +The date and time represented according to the current locale: short for +@code{%a %b %e %T %Y} (@code{Fri Oct 1 15:30:34 1993}) in the C locale + +@item %D + +The date in POSIX format; short for @code{%m/%d/%y} (@code{10/01/93}) + +@item %d + +The day of the month (01-31), zero padded to two digits (@code{01}) @item %e -The day of the month, blank padded to two characters (@code{ 2}) +The day of the month (1-31), blank padded to two characters (@code{ 1}) -@item %D +@item %F -Short for @code{%m/%d/%y} (@code{10/01/93}) +The date in ISO format; short for @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 week based year, zero padded to four digits (@code{1993}) + +@item %g + +The ISO week based year of the century (00-99), +zero padded to two digits (@code{93}) @item %H -The hour (0-24), zero padded to two characters (@code{15}) +The hour in the 24 hour clock (00-24), zero padded to two digits (@code{15}) @item %I -The hour (1-12), zero padded to two characters (@code{03}) +The hour in the 12 hour clock (01-12), zero padded to two digits (@code{03}) @item %j -The Julian day, zero padded to three characters (@code{275}) +The Julian day of the year (001-366), zero padded to three digits (@code{274}) @item %k -The hour (0-24), space padded to two characters (@code{15}) +The hour in the 24 hour clock (0-24), +space padded to two characters (@code{15}) @item %l -The hour (1-12), space padded to two characters(@code{ 3}) +The hour in the 12 hour clock (1-12), +space padded to two characters (@code{ 3}) @item %M -The minutes, zero padded to two characters (@code{30}) +The minutes in the hour (00-59), zero padded to two digits (@code{30}) @item %m -The month (1-12), zero padded to two characters (@code{10}) +The month in the year (01-12), zero padded to two digits (@code{10}) @item %n -A newline (@code{\n}) +The newline control character (@code{\n}) @item %p -AM or PM (@code{PM}) +The AM/PM indicator in the 12 hour clock represented according +to the current locale: (@code{PM}) in the C locale @item %R -Short for @code{%H:%M} (@code{15:30}) +The time in the 24 hour clock, excluding seconds; +short for @code{%H:%M} (@code{15:30}) @item %r -Short for @code{%I:%M:%S %p} (@code{03:30:35 PM}) +The time in the 12 hour clock represented according to the current locale: +short for @code{%I:%M:%S %p} (@code{03:30:35 PM}) in the C locale @item %S -The seconds, zero padded to two characters (@code{35}) +The seconds in the minute (00-60), zero padded to two digits (@code{35}); +the maximum value is 60 to allow leap seconds to be represented + +@item %s + +The number of seconds since 1970-01-01 00:00:00 UTC (@code{749503834}) @item %T -Short for @code{%H:%M:%S} (@code{15:30:35}) +The time in the 24 hour clock, including seconds; +short for @code{%H:%M:%S} (@code{15:30:35}) @item %t -A tab (@code{\t}) +The horizontal tab control character (@code{\t}) @item %U -The week of the year, with the first week defined by the first Sunday of -the year, zero padded to two characters (@code{39}) +The Sunday based week of the year (00-53), with the first week defined +by the first Sunday of the year, zero padded to two digits (@code{39}) @item %u -The day of the week (1-7) (@code{6}) +The ISO Monday based day of the week (Mon 1-Sun 7) (@code{5}) + +@item %V + +The ISO Monday based week of the year (01-53), with the first and last +weeks defined by the first and last Thursdays of the year, zero padded +to two digits (@code{39}) @item %W -The week of the year, with the first week defined by the first Monday of -the year, zero padded to two characters (@code{39}) +The Monday based week of the year (00-53), with the first week defined +by the first Monday of the year, zero padded to two digits (@code{39}) @item %w -The day of the week (0-6) (@code{5}) +The Sunday based day of the week (Sun 0-Sat 6) (@code{5}) @item %x -Date represented according to the current locale. +The date represented according to the current locale: +short for @code{%D} (@code{10/01/93}) in the C locale @item %X -Time represented according to the current locale. +The time in the 24 hour clock represented according to the current +locale: short for @code{%T} (@code{15:30:34}) in the C locale @item %y -The year (00-99) of the century (@code{93}) +The year of the century (00-99), zero padded to two digits (@code{93}) @item %Y @@ -151,11 +193,54 @@ @item %Z -The timezone abbreviation (@code{EDT}) +The timezone abbreviation represented according +to the current locale (@code{EDT}) + +@item %z + +The ISO timezone offset in [+-]HHMM format (@code{-0400}) @item %% -A percent symbol (@code{%}) +The percent symbol character (@code{%}) + +@end table + +@subheading Conversion Modifiers + +The following conversion modifier characters affect the conversion into +the output string and must appear after the flag character (@code{%}) +and before the conversion specifier character. + +@table @code + +@item - + +Left justify the output with no padding characters, +for example @code{%-d} gives @code{1} + +@item 0 + +Right justify the output with zero digit padding, +for example @code{%0d} gives @code{01} + +@item _ + +Right justify the output with space character padding, +for example @code{%_d} gives @code{ 1} + +@item ^ + +Convert lowercase letters in the output to uppercase, +for example @code{%^a} gives @code{FRI} + +@item E + +Locale alternate era output format; ignored in the C locale + +@item O + +Locale alternate digits output format; ignored in the C locale @end table @@ -165,7 +250,7 @@ @subheading Portability -@portability ansi, posix +@portability ANSI, POSIX @subheading Example Index: strftime.c =================================================================== RCS file: /cvs/djgpp/djgpp/zoneinfo/src/strftime.c,v retrieving revision 1.2 diff -p -u -t -r1.2 strftime.c --- strftime.c 28 May 1998 16:56:10 -0000 1.2 +++ strftime.c 17 Mar 2004 06:43:26 -0000 @@ -1,6 +1,6 @@ #ifndef lint #ifndef NOID -static char elsieid[] = "@(#)strftime.c 7.57"; +static char elsieid[] = "@(#)strftime.c 7.62"; /* ** Based on the UCB version with the ID appearing below. ** This is ANSIish only when "multibyte character == plain character". @@ -80,24 +80,21 @@ static const struct lc_time_T C_time_loc /* ** x_fmt - ** Since the C language standard calls for - ** "date, using locale's date format," anything goes. + ** C99 requires this format. ** Using just numbers (as here) makes Quakers happier; ** it's also compatible with SVR4. - ** - ** XXX--might it be better to use the year-2000 friendly - ** %Y-%m-%d - ** here? */ "%m/%d/%y", /* ** c_fmt + ** C99 requires this format. + ** Previously this code used "%D %X", but we now conform to C99. ** Note that - ** "%a %b %d %H:%M:%S %Y" + ** "%a %b %d %H:%M:%S %Y" ** is used by Solaris 2.3. */ - "%D %X", /* %m/%d/%y %H:%M:%S */ + "%a %b %e %T %Y", /* am */ "AM", @@ -236,20 +233,21 @@ label: case 'E': case 'O': /* - ** POSIX locale extensions, a la - ** Arnold Robbins' strftime version 3.0. + ** C99 locale modifiers. ** The sequences - ** %Ec %EC %Ex %Ey %EY + ** %Ec %EC %Ex %EX %Ey %EY ** %Od %oe %OH %OI %Om %OM ** %OS %Ou %OU %OV %Ow %OW %Oy ** are supposed to provide alternate ** representations. - ** (ado, 1993-05-24) */ goto label; case 'e': pt = _conv(t->tm_mday, "%2d", pt, ptlim); continue; + case 'F': + pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); + continue; case 'H': pt = _conv(t->tm_hour, "%02d", pt, ptlim); continue; @@ -492,10 +490,66 @@ label: pt = _add(t->TM_ZONE, pt, ptlim); else #endif /* defined TM_ZONE */ - if (t->tm_isdst == 0 || t->tm_isdst == 1) { - pt = _add(tzname[t->tm_isdst], + if (t->tm_isdst >= 0) + pt = _add(tzname[t->tm_isdst != 0], pt, ptlim); - } else pt = _add("?", pt, ptlim); + /* + ** C99 says that %Z must be replaced by the + ** empty string if the time zone is not + ** determinable. + */ + continue; + case 'z': + { + int diff; + char const * sign; + + if (t->tm_isdst < 0) + continue; +#ifdef TM_GMTOFF + diff = t->TM_GMTOFF; +#else /* !defined TM_GMTOFF */ + /* + ** C99 says that the UTC offset must + ** be computed by looking only at + ** tm_isdst. This requirement is + ** incorrect, since it means the code + ** must rely on magic (in this case + ** altzone and timezone), and the + ** magic might not have the correct + ** offset. Doing things correctly is + ** tricky and requires disobeying C99; + ** see GNU C strftime for details. + ** For now, punt and conform to the + ** standard, even though it's incorrect. + ** + ** C99 says that %z must be replaced by the + ** empty string if the time zone is not + ** determinable, so output nothing if the + ** appropriate variables are not available. + */ + if (t->tm_isdst == 0) +#ifdef USG_COMPAT + diff = -timezone; +#else /* defined USG_COMPAT */ + continue; +#endif /* !defined USG_COMPAT */ + else +#ifdef ALTZONE + diff = -altzone; +#else /* !defined ALTZONE */ + continue; +#endif /* !defined ALTZONE */ +#endif /* !defined TM_GMTOFF */ + if (diff < 0) { + sign = "-"; + diff = -diff; + } else sign = "+"; + pt = _add(sign, pt, ptlim); + diff /= 60; + pt = _conv((diff/60)*100 + diff%60, + "%04d", pt, ptlim); + } continue; case '+': pt = _fmt(Locale->date_fmt, t, pt, ptlim, @@ -503,10 +557,10 @@ label: continue; case '%': /* - * X311J/88-090 (4.12.3.5): if conversion char is - * undefined, behavior is undefined. Print out the - * character itself as printf(3) also does. - */ + ** X311J/88-090 (4.12.3.5): if conversion char is + ** undefined, behavior is undefined. Print out the + ** character itself as printf(3) also does. + */ default: break; } Index: makefile =================================================================== RCS file: /cvs/djgpp/djgpp/tests/libc/ansi/time/makefile,v retrieving revision 1.3 diff -u -t -r1.3 makefile --- makefile 8 Nov 2003 12:19:41 -0000 1.3 +++ makefile 17 Mar 2004 06:57:18 -0000 @@ -4,6 +4,8 @@ SRC += strftime.c SRC += tzinfo.c SRC += xstrftm.c +SRC += strftimt.c +SRC += strftiml.c include $(TOP)/../makefile.inc Index: strftime.c =================================================================== RCS file: /cvs/djgpp/djgpp/tests/libc/ansi/time/strftime.c,v retrieving revision 1.3 diff -p -u -t -r1.3 strftime.c --- strftime.c 26 May 2002 16:12:46 -0000 1.3 +++ strftime.c 17 Mar 2004 06:48:41 -0000 @@ -11,6 +11,86 @@ #include #include + +#undef CMP +#define CMP(Fmt, Expected) n_fail += compare ((Fmt), tm, (Expected)) + + +typedef struct cmp +{ + char *fmt; + char *expected; + +} cmp; + + +struct cmp comparisons[] = +{ + { "%-m", "1" }, /* GNU */ + { "%A", "Friday" }, + { "%^A", "FRIDAY" }, /* GNU */ + { "%B", "January" }, + { "%^B", "JANUARY" }, + { "%C", "19" }, /* POSIX.2 */ + { "%D", "01/09/70" }, /* POSIX.2 */ + { "%E", "E" }, /* C99 */ + { "%F", "1970-01-09"}, /* C99 */ + { "%G", "1970" }, /* GNU */ + { "%H", "13" }, + { "%I", "01" }, + { "%J", "J" }, + { "%K", "K" }, + { "%L", "L" }, + { "%M", "06" }, + { "%N", "N" }, + { "%O", "O" }, /* C99 */ + { "%P", "P" }, + { "%Q", "Q" }, + { "%R", "13:06" }, /* POSIX.2 */ + { "%S", "07" }, + { "%T", "13:06:07" }, /* POSIX.2 */ + { "%U", "01" }, + { "%V", "02" }, + { "%W", "01" }, + { "%X", "13:06:07" }, + { "%Y", "1970" }, + { "%Z", "GMT" }, + { "%_m", " 1" }, /* GNU */ + { "%a", "Fri" }, + { "%^a", "FRI" }, + { "%b", "Jan" }, + { "%^b", "JAN" }, + { "%c", "Fri Jan 9 13:06:07 1970" }, + { "%^c", "FRI JAN 9 13:06:07 1970" }, + { "%d", "09" }, + { "%e", " 9" }, /* POSIX.2 */ + { "%f", "f" }, /* ISO */ + { "%g", "70" }, /* GNU */ + { "%h", "Jan" }, /* POSIX.2 */ + { "%^h", "JAN" }, + { "%i", "i" }, + { "%j", "009" }, + { "%k", "13" }, /* GNU */ + { "%l", " 1" }, /* GNU */ + { "%m", "01" }, + { "%n", "\n" }, /* POSIX.2 */ + { "%o", "o" }, + { "%p", "PM" }, + { "%q", "q" }, + { "%r", "01:06:07 PM"}, /* POSIX.2 */ + { "%s", "738367" }, /* GNU */ + { "%t", "\t" }, /* POSIX.2 */ + { "%u", "5" }, /* POSIX.2 */ + { "%v", "v" }, + { "%w", "5" }, + { "%x", "01/09/70" }, + { "%y", "70" }, + { "%z", "+0000" }, /* GNU */ + { "%%", "%" }, + +}; /* comparisons[] */ + + static int compare (const char *fmt, const struct tm *tm, const char *expected) { @@ -19,7 +99,8 @@ compare (const char *fmt, const struct t if (strcmp (buf, expected)) { #if 1 - printf ("fmt: \"%s\", expected \"%s\", got \"%s\"\n", + printf ("\n fmt: \"%-3s\" expected: \"%s\" \n" + " got: \"%s\" \n", fmt, expected, buf); #endif return 1; @@ -27,68 +108,24 @@ compare (const char *fmt, const struct t return 0; } + int main (void) { - int n_fail = 0; - struct tm *tm; - time_t t = 738367; /* Fri Jan 9 13:06:07 1970 */ - tm = gmtime (&t); + struct cmp * c; + int n_fail = 0; + time_t t = 738367; /* Fri Jan 9 13:06:07 1970 */ + struct tm * tm = gmtime (&t); + /* This is necessary to make strftime give consistent zone strings and e.g., seconds since the epoch (%s). */ putenv (unconst("TZ=GMT0", char *)); -#undef CMP -#define CMP(Fmt, Expected) n_fail += compare ((Fmt), tm, (Expected)) - - CMP ("%-m", "1"); /* GNU */ - CMP ("%A", "Friday"); - CMP ("%^A", "FRIDAY"); /* The ^ is a GNU extension. */ - CMP ("%B", "January"); - CMP ("%^B", "JANUARY"); - CMP ("%C", "19"); /* POSIX.2 */ - CMP ("%D", "01/09/70"); /* POSIX.2 */ - CMP ("%G", "1970"); /* GNU */ - CMP ("%H", "13"); - CMP ("%I", "01"); - CMP ("%M", "06"); - CMP ("%M", "06"); - CMP ("%R", "13:06"); /* POSIX.2 */ - CMP ("%S", "07"); - CMP ("%T", "13:06:07"); /* POSIX.2 */ - CMP ("%U", "01"); - CMP ("%V", "02"); - CMP ("%W", "01"); - CMP ("%X", "13:06:07"); - CMP ("%Y", "1970"); - CMP ("%Z", "GMT"); - CMP ("%_m", " 1"); /* GNU */ - CMP ("%a", "Fri"); - CMP ("%^a", "FRI"); - CMP ("%b", "Jan"); - CMP ("%^b", "JAN"); - CMP ("%c", "Fri Jan 9 13:06:07 1970"); - CMP ("%^c", "FRI JAN 9 13:06:07 1970"); - CMP ("%d", "09"); - CMP ("%e", " 9"); /* POSIX.2 */ - CMP ("%g", "70"); /* GNU */ - CMP ("%h", "Jan"); /* POSIX.2 */ - CMP ("%^h", "JAN"); - CMP ("%j", "009"); - CMP ("%k", "13"); /* GNU */ - CMP ("%l", " 1"); /* GNU */ - CMP ("%m", "01"); - CMP ("%n", "\n"); /* POSIX.2 */ - CMP ("%p", "PM"); - CMP ("%r", "01:06:07 PM"); /* POSIX.2 */ - CMP ("%s", "738367"); /* GNU */ - CMP ("%t", "\t"); /* POSIX.2 */ - CMP ("%u", "5"); /* POSIX.2 */ - CMP ("%w", "5"); - CMP ("%x", "01/09/70"); - CMP ("%y", "70"); - CMP ("%z", "+0000"); /* GNU */ + for (c = comparisons; c < comparisons + sizeof comparisons / sizeof *comparisons; ++ c) + { + CMP( c->fmt, c->expected); + } exit (n_fail ? 1 : 0); } Index: xstrftm.c =================================================================== RCS file: /cvs/djgpp/djgpp/tests/libc/ansi/time/xstrftm.c,v retrieving revision 1.1 diff -p -u -t -r1.1 xstrftm.c --- xstrftm.c 8 Nov 2003 12:19:41 -0000 1.1 +++ xstrftm.c 17 Mar 2004 06:49:25 -0000 @@ -1,19 +1,38 @@ +#include #include #include #include -extern char __dj_date_format[10]; -extern char __dj_time_format[16]; +extern char __dj_date_format[]; +extern char __dj_time_format[]; int main(int ac, char *av[]) { char buf[99]; time_t t = time(NULL); + char *locale; +/* default C locale */ + locale = setlocale( LC_TIME, NULL ); + strftime(buf, sizeof(buf), "%x %X", gmtime(&t)); + printf("%s locale %s\n", locale, buf); + +/* current locale */ + locale = setlocale( LC_TIME, "" ); + strftime(buf, sizeof(buf), "%x %X", gmtime(&t)); + printf("%s locale %s\n", locale, buf); + +/* custom locale */ + locale = "custom"; strcpy(__dj_date_format, "%d|%m|%Y"); strcpy(__dj_time_format, "[%H|%M|%S]"); + strftime(buf, sizeof(buf), "%x %X", gmtime(&t)); + printf("%s locale %s\n", locale, buf); +/* default C locale */ + locale = setlocale( LC_TIME, "C" ); strftime(buf, sizeof(buf), "%x %X", gmtime(&t)); - printf("%s\n", buf); + printf("%s locale %s\n", locale, buf); + return 0; } --- strftiml.c 2004-01-01 00:00:00.000000000 -0700 +++ strftiml.c 2004-02-18 22:32:40.000000000 -0700 @@ -0,0 +1,38 @@ +/* strftiml.c - basic time conversion calls */ + + +#include +#include + + + +int main( void ) +{ + char *p; + struct tm *tm_now = NULL; + time_t t_now = 0; + int n; + char fmts[] = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ%"; + char fmt[] = "%%\t'%%'\n"; + char msg[1024]; + + + t_now = time( NULL ); /* get current time */ + tm_now = localtime( &t_now ); /* get local time */ + t_now = mktime( tm_now ); /* convert back to time */ + + p = ctime( &t_now ); /* convert time_t to string */ + printf( "ctime '%s'\n", p); + p = asctime( tm_now ); /* convert struct tm to string */ + printf( "asctime '%s'\n", p); + for (p = fmts; p < fmts + sizeof( fmts ); ++p) + { + n = sprintf( fmt, "%c\t'%%%c'\n", *p, *p); + n = strftime( msg, sizeof( msg ), fmt, tm_now); + n = printf( "%s", msg); + } + + return 0; + +} /* main() */ + --- strftimt.c 2004-01-01 00:00:00.000000000 -0700 +++ strftimt.c 2004-02-24 02:30:52.000000000 -0700 @@ -0,0 +1,192 @@ +/* @(#)strftimt.c 1.1 24 Feb 2004 BWI */ + + + +#include +#include +#include +#include +#include +#include + + + +static const struct +{ + const char * locale; + const char * tz; + const char * format; + const char * text; + struct tm tm; +} tests[] = +/* locale tz format text */ +/* sec min hr day mon year wdy ydy dst */ +/* -1 -1970 0-6 0-* -=+ */ +{ +/* 1 */ { "C", "Universal", "%u %Y-%m-%d %H:%M:%S", "7 1999-01-31 21:01:10", + { 10, 1, 21, 31, 0, 99, 0, 30,-1 }}, +/* 2 */ { "C", "Universal", "%u %C%y.%b.%e %I.%M %p","7 1999.Feb.28 09.01 PM", + { 10, 1, 21, 28, 1, 99, 0, 58,-1 }}, +/* 3 */ { "C", "Universal", "%w %y/%h %j %k-%M-%S", "2 00/Feb 060 16-30-46", + { 46, 30, 16, 29, 1, 100, 2, 59,-1 }}, +/* 4 */ { "C", "Universal", "%a %y %B %e %l:%M %p", "Sat 00 January 1 12:34 PM", + { 12, 34, 12, 1, 0, 100, 6, 0,-1 }}, +/* 5 */ { "C", "Universal", "%A %y-%m-%d %T","Friday 00-03-03 17:41:01", + { 1, 41, 17, 3, 2, 100, 5, 62,-1 }}, +/* 6 */ { "C", "Universal", "%W %F %R", "36 1999-09-09 21:01", + { 10, 1, 21, 9, 8, 99, 4, 251,-1 }}, +/* 7 */ { "C", "Universal", "%U %D %X", "18 05/02/99 12:34:12", + { 12, 34, 12, 2, 4, 99, 1, 121,-1 }}, +/* 8 */ { "C", "Universal", "%x %t %r", "05/21/01 \t 09:01:10 PM", + { 10, 1, 21, 21, 4, 101, 1, 140,-1 }}, +/* 9 */ { "C", "Universal", "%c %n", "Mon May 21 16:30:46 2001 \n", + { 46, 30, 16, 21, 4, 101, 1, 140,-1 }}, +/* 10 */ { "C", "Universal", "%F %T %Z", "2000-01-05 17:41:01 UTC", + { 1, 41, 17, 5, 0, 100, 3, 4,-1 }}, +/* 11 */ { "C", "Universal", "%F %T %z", "2000-01-05 17:41:01 +0000", + { 1, 41, 17, 5, 0, 100, 3, 4,-1 }}, +/* 12 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "1975-12-29 1976-W01-1 1975-w52-1 1975-u52-1", + { 0, 0, 0, 29, 11, 75, 0, 362,-1 }}, +/* 13 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "1977-01-02 1976-W53-7 1977-w00-7 1977-u01-0", + { 0, 0, 0, 2, 0, 77, 0, 1,-1 }}, +/* 14 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "1999-01-02 1998-W53-6 1999-w00-6 1999-u00-6", + { 0, 0, 0, 2, 0, 99, 5, 1,-1 }}, +/* 15 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "1999-12-27 1999-W52-1 1999-w52-1 1999-u52-1", + { 0, 0, 0, 27, 11, 99, 0, 360,-1 }}, +/* 16 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2000-01-02 1999-W52-7 2000-w00-7 2000-u01-0", + { 0, 0, 0, 2, 0, 100, 0, 1,-1 }}, +/* 17 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2003-12-28 2003-W52-7 2003-w51-7 2003-u52-0", + { 0, 0, 0, 28, 11, 103, 0, 361,-1 }}, +/* 18 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2003-12-29 2004-W01-1 2003-w52-1 2003-u52-1", + { 0, 0, 0, 29, 11, 103, 0, 362,-1 }}, +/* 19 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2003-12-31 2004-W01-3 2003-w52-3 2003-u52-3", + { 0, 0, 0, 31, 11, 103, 2, 364,-1 }}, +/* 20 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2004-01-01 2004-W01-4 2004-w00-4 2004-u00-4", + { 0, 0, 0, 1, 0, 104, 3, 0,-1 }}, +/* 21 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2004-01-04 2004-W01-7 2004-w00-7 2004-u01-0", + { 0, 0, 0, 4, 0, 104, 0, 3,-1 }}, +/* 22 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2004-01-05 2004-W02-1 2004-w01-1 2004-u01-1", + { 0, 0, 0, 5, 0, 104, 0, 4,-1 }}, +/* 23 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2004-12-26 2004-W52-7 2004-w51-7 2004-u52-0", + { 0, 0, 0, 26, 11, 104, 0, 360,-1 }}, +/* 24 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2004-12-27 2004-W53-1 2004-w52-1 2004-u52-1", + { 0, 0, 0, 27, 11, 104, 0, 361,-1 }}, +/* 25 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2004-12-31 2004-W53-5 2004-w52-5 2004-u52-5", + { 0, 0, 0, 31, 11, 104, 4, 365,-1 }}, +/* 26 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2005-01-01 2004-W53-6 2005-w00-6 2005-u00-6", + { 0, 0, 0, 1, 0, 105, 5, 0,-1 }}, +/* 27 */ { "C", "Universal", "%F %G-W%V-%u %Y-w%W-%u %Y-u%U-%w", "2005-01-03 2005-W01-1 2005-w01-1 2005-u01-1", + { 0, 0, 0, 3, 0, 105, 0, 2,-1 }}, +/* 28 */ { "C", "Universal", "%F %g-W%V-%u %y-w%W-%u %y-u%U-%w", "2012-01-01 11-W52-7 12-w00-7 12-u01-0", + { 0, 0, 0, 1, 0, 112, 0, 0,-1 }}, +/* 29 */ { "C", "Universal", "%F %g-W%V-%u %y-w%W-%u %y-u%U-%w", "2012-02-29 12-W09-3 12-w09-3 12-u09-3", + { 0, 0, 0, 29, 01, 112, 2, 59,-1 }}, +/* 30 */ { "C", "Universal", "%F %g-W%V-%u %y-w%W-%u %y-u%U-%w", "2012-12-31 13-W01-1 12-w53-1 12-u53-1", + { 0, 0, 0, 31, 11, 112, 0, 365,-1 }}, +/* 31 */ { "C", "EST5EDT", "%c %Z %s W%V w%W u%U", "Fri Oct 1 15:30:34 1993 EDT 749503834 W39 w39 u39", + { 34, 30, 15, 1, 9, 93, 5, 273,-1 }}, +/* 32 */ { "C", "Europe/Berlin","%d.%m.%Y %T %Z","01.08.2000 05:06:07 CEST", + { 7, 6, 5, 1, 7, 100, 0, 0,-1 }}, +/* 33 */ { "C", "Europe/Berlin","%d.%m.%Y %T %z","01.08.2000 05:06:07 +0200", + { 7, 6, 5, 1, 7, 100, 0, 0,-1 }}, +#if 0 /* not yet supported */ +/* 34 */ { "ja_JP.EUC-JP","Universal", "%Y %U %a", "2001 20 \xb7\xee", + { 0, 0, 0, 21, 4, 101, 1, 140,-1 }}, +/* 35 */ { "ja_JP.EUC-JP","Universal", "%Y %W %a", "2001 21 \xb7\xee", + { 0, 0, 0, 21, 4, 101, 1, 140,-1 }}, +/* 36 */ { "ja_JP.EUC-JP","Asia/Tokyo", "%Y %W %a", "2001 21 \xb7\xee", + { 0, 0, 0, 21, 4, 101, 1, 140,-1 }}, + +#endif /* 0 */ +}; + + + +int +main (int argc, char *argv[]) +{ + time_t tt; + size_t t; + size_t len = 0; + int rc = 0; + int errs = 0; + struct tm tm = { 0 }; + char text[BUFSIZ] = ""; + + for (t = 0; t < sizeof tests / sizeof *tests; ++t) + { + errno = 0; + + if (setlocale( LC_TIME, tests[t].locale) == NULL) + { + if (errno) perror( "setlocale()" ); + printf( "test %3d error %6s set locale '%s' category '%s'(%d) \n", + t + 1, "NULL", tests[t].locale, "LC_TIME", LC_TIME); + ++errs; + continue; + } + + if ((rc = setenv( "TZ", tests[t].tz, 1))) + { + if (errno) perror( "setenv()" ); + printf( "test %3d error %6d set environment '%s' '%s' \n", + t + 1, rc, "TZ", tests[t].tz); + ++errs; + continue; + } + + tzset(); + + tm = tests[t].tm; + + if ((tt = mktime( &tm )) == -1) + { + if (errno) perror( "mktime()" ); + printf( "test %3d error %6d make time " + "%04d-%02d-%02d %02d:%02d:%02d j%03d w%01d dst %d \n", + t + 1, tt, + tests[t].tm.tm_year + 1900, + tests[t].tm.tm_mon + 1, + tests[t].tm.tm_mday, + tests[t].tm.tm_hour, + tests[t].tm.tm_min, + tests[t].tm.tm_sec, + tests[t].tm.tm_yday + 1, + tests[t].tm.tm_wday, + tests[t].tm.tm_isdst); + ++errs; + continue; + } + + if (!(len = strftime( text, sizeof text, tests[t].format, &tm))) + { + if (errno) perror( "strftime()" ); + printf( "test %3d error %6d time format '%s' \n", + t + 1, len, tests[t].format); + ++errs; + continue; + } + + if (strlen( tests[t].text ) != strlen( text ) + || strcmp( tests[t].text, text)) + { + printf( "\n test %3d error %6s expected '%s' \n" + " actual '%s' \n", + t + 1, "FAILED", tests[t].text, text); + ++errs; + continue; + } + } + + if (errs) + { + printf( "tests %2d errors %5d passed %d \n", + sizeof tests / sizeof *tests, + errs, + sizeof tests / sizeof *tests - errs); + } + + return errs; + +} /* main() */ +