support for locales
Here is a patch that adds support for locales to the tz package's strftime function and date command. For example, if the LC_TIME environment variable is set to "de", `date' will output something like `Montag, 9. Mai 1994, 02:59:54 Uhr PDT' instead of the usual `Mon May 9 02:59:54 PDT 1994'. The locale files are in /usr/lib/locale, using the same format as in Solaris 2.3 (which I assume is fairly standard, at least among SVR4-ish hosts). If locale files are absent or are not of the proper form, traditional behavior is assumed. I invented a strftime format `%+', which expands to the default format used by `date'; this makes it easier to put all the locale-relevant stuff in strftime.c, and also lets the user type fun commands like `date "+|%+|"'. This patch also extends strftime to use the format "%c" if its format argument is (char*)0; this is the documented behavior in Solaris 2.3. This patch assumes the Posix/GCC cleanup patch I sent a while back. =================================================================== RCS file: RCS/Makefile,v retrieving revision 1994.7.1.1 retrieving revision 1994.7.1.2 diff -c -r1994.7.1.1 -r1994.7.1.2 *** Makefile 1994/05/05 19:10:21 1994.7.1.1 --- Makefile 1994/05/09 09:47:26 1994.7.1.2 *************** *** 82,87 **** --- 82,88 ---- # -DHAVE_ADJTIME=0 if `adjtime' does not exist (SVR0?) # -DHAVE_LONG_DOUBLE if your compiler supports the `long double' type # -DHAVE_MKDIR=0 if the mkdir system call does not work (SVR0?) + # -DHAVE_SETLOCALE=0 if the setlocale function does not work (SVR3) # -DHAVE_SETTIMEOFDAY=0 if settimeofday does not exist (SVR0?) # -DHAVE_SETTIMEOFDAY=1 if settimeofday has just 1 arg (SVR4) # -DHAVE_SETTIMEOFDAY=2 if settimeofday uses 2nd arg (4.3BSD) *************** *** 89,94 **** --- 90,96 ---- # -DHAVE_STDLIB_H=0 if `#include <stdlib.h>' does not work # -DHAVE_UMASK=0 if `umask(0)' does not work # -DHAVE_UNISTD=0 if `#include <unistd.h>' does not work + # -DLOCALE_HOME=\"path\" if locales are in "path", not "/usr/lib/locale" # -DNEED_STRCHR_DECL=1 if <string.h> does not declare strchr fully # -DNEED_TIME_DECL=1 if <time.h> does not declare `time' fully # -Dalloc_size_t=T if the malloc size argument is of type T, not size_t =================================================================== RCS file: RCS/date.1,v retrieving revision 1994.7 retrieving revision 1994.7.1.1 diff -c -r1994.7 -r1994.7.1.1 *** date.1 1992/11/04 18:08:06 1994.7 --- date.1 1994/05/09 09:47:50 1994.7.1.1 *************** *** 37,42 **** --- 37,43 ---- (or by the abbreviation for the time zone specified in the .B TZ environment variable if set). + The exact output format depends on the locale. .PP If a command-line argument starts with a plus sign .RB (` + '), *************** *** 51,70 **** to be output in a particular way (or identify a special character to output): .nf .if t .in +.5i .if n .in +2 ! .ta \w'%M\0\0'u +\w'Wed Mar 8 14:54:40 1989\0\0'u Sample output Explanation ! %a Wed Abbreviated weekday name ! %A Wednesday Full weekday name ! %b Mar Abbreviated month name ! %B March Full month name ! %c 03/08/89 14:54:40 Month/day/year Hour:minute:second ! %C Wed Mar 8 14:54:40 1989 a la \fIasctime\^\fP(3) %d 08 Day of month (always two digits) %D 03/08/89 Month/day/year (eight characters) %e 8 Day of month (leading zero blanked) ! %h Mar Abbreviated month name %H 14 24-hour-clock hour (two digits) %I 02 12-hour-clock hour (two digits) %j 067 Julian day number (three digits) --- 52,72 ---- to be output in a particular way (or identify a special character to output): .nf + .sp .if t .in +.5i .if n .in +2 ! .ta \w'%M\0\0'u +\w'Wed Mar 8 14:54:40 EST 1989\0\0'u Sample output Explanation ! %a Wed Abbreviated weekday name* ! %A Wednesday Full weekday name* ! %b Mar Abbreviated month name* ! %B March Full month name* ! %c Wed Mar 08 14:54:40 1989 Date and time* ! %C 19 Century %d 08 Day of month (always two digits) %D 03/08/89 Month/day/year (eight characters) %e 8 Day of month (leading zero blanked) ! %h Mar Abbreviated month name* %H 14 24-hour-clock hour (two digits) %I 02 12-hour-clock hour (two digits) %j 067 Julian day number (three digits) *************** *** 82,94 **** %U 10 Sunday-based week number (two digits) %w 3 Day number (one digit, Sunday is 0) %W 10 Monday-based week number (two digits) ! %x 03/08/89 Month/day/year (eight characters) ! %X 14:54:40 Hour:minute:second %y 89 Last two digits of year %Y 1989 Year in full %Z EST Time zone abbreviation .if t .in -.5i .if n .in -2 .fi If a character other than one of those shown above appears after a percent sign in the format, --- 84,99 ---- %U 10 Sunday-based week number (two digits) %w 3 Day number (one digit, Sunday is 0) %W 10 Monday-based week number (two digits) ! %x 03/08/89 Date* ! %X 14:54:40 Time* %y 89 Last two digits of year %Y 1989 Year in full %Z EST Time zone abbreviation + %+ Wed Mar 8 14:54:40 EST 1989 Default output format* .if t .in -.5i .if n .in -2 + * The exact output depends on the locale. + .sp .fi If a character other than one of those shown above appears after a percent sign in the format, *************** *** 150,153 **** --- 155,175 ---- On BSD-based systems, the adjustment is made by changing the rate at which time advances; on System-V-based systems, the adjustment is made by changing the time. + .SH FILES + .ta \w'/usr/local/etc/zoneinfo/posixrules\0\0'u + /usr/lib/locale/\f2L\fP/LC_TIME description of time locale \f2L\fP + .br + /usr/local/etc/zoneinfo time zone information directory + .br + /usr/local/etc/zoneinfo/localtime local time zone file + .br + /usr/local/etc/zoneinfo/posixrules used with POSIX-style TZ's + .br + /usr/local/etc/zoneinfo/GMT for UTC leap seconds + .sp + If + .B /usr/local/etc/zoneinfo/GMT + is absent, + UTC leap seconds are loaded from + .BR /usr/local/etc/zoneinfo/posixrules . .\" @(#)date.1 7.2 =================================================================== RCS file: RCS/date.c,v retrieving revision 1994.6.1.1 retrieving revision 1994.6.1.2 diff -c -r1994.6.1.1 -r1994.6.1.2 *** date.c 1994/05/05 16:24:49 1994.6.1.1 --- date.c 1994/05/09 09:47:28 1994.6.1.2 *************** *** 40,45 **** --- 40,49 ---- #endif #include "utmp.h" /* for OLD_TIME (or its absence) */ + #if HAVE_SETLOCALE + # include "locale.h" + #endif + /* ** The two things date knows about time are. . . */ *************** *** 485,495 **** (void) time(&now); tm = *localtime(&now); ! if (format == NULL) { ! timeout(stdout, "%a %b ", &tm); ! (void) printf("%2d ", tm.tm_mday); ! timeout(stdout, "%X %Z %Y", &tm); ! } else timeout(stdout, format, &tm); (void) putchar('\n'); (void) fflush(stdout); (void) fflush(stderr); --- 489,498 ---- (void) time(&now); tm = *localtime(&now); ! #if HAVE_SETLOCALE ! setlocale(LC_TIME, ""); ! #endif ! timeout(stdout, format ? format : "%+", &tm); (void) putchar('\n'); (void) fflush(stdout); (void) fflush(stderr); =================================================================== RCS file: RCS/private.h,v retrieving revision 1994.5.1.1 retrieving revision 1994.5.1.2 diff -c -r1994.5.1.1 -r1994.5.1.2 *** private.h 1994/04/03 02:54:41 1994.5.1.1 --- private.h 1994/05/09 09:47:28 1994.5.1.2 *************** *** 30,35 **** --- 30,38 ---- #ifndef HAVE_MKDIR #define HAVE_MKDIR 1 #endif + #ifndef HAVE_SETLOCALE + #define HAVE_SETLOCALE 1 + #endif #ifndef HAVE_SETTIMEOFDAY #define HAVE_SETTIMEOFDAY 3 #endif *************** *** 48,53 **** --- 51,60 ---- #endif #ifndef NEED_TIME_DECL #define NEED_TIME_DECL 0 + #endif + + #ifndef LOCALE_HOME + #define LOCALE_HOME "/usr/lib/locale" #endif /* =================================================================== RCS file: RCS/strftime.c,v retrieving revision 1994.6 retrieving revision 1994.6.1.1 diff -c -r1994.6 -r1994.6.1.1 *** strftime.c 1994/05/05 15:47:58 1994.6 --- strftime.c 1994/05/09 09:48:02 1994.6.1.1 *************** *** 3,10 **** static char elsieid[] = "@(#)strftime.c 7.19"; /* ** Based on the UCB version with the ID appearing below. ! ** This is ANSIish only when time is treated identically in all locales and ! ** when "multibyte character == plain character". */ #endif /* !defined NOID */ #endif /* !defined lint */ --- 3,9 ---- static char elsieid[] = "@(#)strftime.c 7.19"; /* ** Based on the UCB version with the ID appearing below. ! ** This is ANSIish only when "multibyte character == plain character". */ #endif /* !defined NOID */ #endif /* !defined lint */ *************** *** 35,60 **** #endif /* !defined LIBC_SCCS */ #include "tzfile.h" ! static const char afmt[][4] = { ! "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; ! static const char Afmt[][10] = { ! "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", ! "Saturday" ! }; ! static const char bfmt[][4] = { ! "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", ! "Oct", "Nov", "Dec" ! }; ! static const char Bfmt[][10] = { ! "January", "February", "March", "April", "May", "June", "July", ! "August", "September", "October", "November", "December" }; static char *_add P((const char *, char *, const char *)); static char *_conv P((int, const char *, char *, const char *)); ! static char *_fmt P((const char *, const struct tm *, char *, const char *)); size_t strftime P((char *, size_t, const char *, const struct tm *)); --- 34,98 ---- #endif /* !defined LIBC_SCCS */ #include "tzfile.h" + #include "fcntl.h" ! struct lc_time { ! const char *mon[12]; ! const char *month[12]; ! const char *wday[7]; ! const char *weekday[7]; ! const char *X_fmt; ! const char *x_fmt; ! const char *c_fmt; ! const char *am; ! const char *pm; ! const char *date_fmt; }; ! ! static const struct lc_time C_time_locale = { ! { ! "Jan", "Feb", "Mar", "Apr", "May", "Jun", ! "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ! }, { ! "January", "February", "March", "April", "May", "June", ! "July", "August", "September", "October", "November", "December" ! }, { ! "Sun", "Mon", "Tue", "Wed", ! "Thu", "Fri", "Sat" ! }, { ! "Sunday", "Monday", "Tuesday", "Wednesday", ! "Thursday", "Friday", "Saturday" ! }, ! ! /* X_fmt */ ! "%H:%M:%S", ! ! /* ! ** x_fmt ! ** Since the C language standard calls for ! ** "date, using locale's date format," anything goes. ! ** Using just numbers (as here) makes Quakers happier; ! ** it's also compatible with SVR4. ! */ ! "%m/%d/%y", ! ! /* c_fmt */ ! "%a %b %d %H:%M:%S %Y", ! ! "AM", "PM", ! ! /* date_fmt */ ! "%a %b %e %H:%M:%S %Z %Y" }; + #if HAVE_SETLOCALE + # include <locale.h> + #endif + static char *_add P((const char *, char *, const char *)); static char *_conv P((int, const char *, char *, const char *)); ! static char *_fmt P((const char *, const struct tm *, char *, const char *, struct lc_time *)); ! static const struct lc_time *_loc P((struct lc_time *)); size_t strftime P((char *, size_t, const char *, const struct tm *)); *************** *** 68,75 **** const struct tm *t; { char *p; ! p = _fmt(format, t, s, s + maxsize); if (p == s + maxsize) return 0; *p = '\0'; --- 106,117 ---- const struct tm *t; { char *p; + struct lc_time localebuf; ! if (!format) ! format = "%c"; ! localebuf.mon[0] = 0; ! p = _fmt(format, t, s, s + maxsize, &localebuf); if (p == s + maxsize) return 0; *p = '\0'; *************** *** 77,87 **** } static char * ! _fmt(format, t, pt, ptlim) const char *format; const struct tm *t; char *pt; const char *ptlim; { for (; *format; ++format) { if (*format == '%') { --- 119,130 ---- } static char * ! _fmt(format, t, pt, ptlim, locale) const char *format; const struct tm *t; char *pt; const char *ptlim; + struct lc_time *locale; { for (; *format; ++format) { if (*format == '%') { *************** *** 90,114 **** case '\0': --format; break; case 'A': pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ? ! "?" : Afmt[t->tm_wday], pt, ptlim); continue; case 'a': pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ? ! "?" : afmt[t->tm_wday], pt, ptlim); continue; case 'B': pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? ! "?" : Bfmt[t->tm_mon], pt, ptlim); continue; case 'b': case 'h': pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? ! "?" : bfmt[t->tm_mon], pt, ptlim); continue; case 'c': ! pt = _fmt("%D %X", t, pt, ptlim); continue; case 'C': /* --- 133,166 ---- case '\0': --format; break; + case '+': + pt = _fmt(_loc(locale)->date_fmt, t, + pt, ptlim, locale); + continue; case 'A': pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ? ! "?" : _loc(locale)->weekday[t->tm_wday], ! pt, ptlim); continue; case 'a': pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ? ! "?" : _loc(locale)->wday[t->tm_wday], ! pt, ptlim); continue; case 'B': pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? ! "?" : _loc(locale)->month[t->tm_mon], ! pt, ptlim); continue; case 'b': case 'h': pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? ! "?" : _loc(locale)->mon[t->tm_mon], ! pt, ptlim); continue; case 'c': ! pt = _fmt(_loc(locale)->c_fmt, t, ! pt, ptlim, locale); continue; case 'C': /* *************** *** 122,148 **** "%02d", pt, ptlim); continue; case 'D': ! pt = _fmt("%m/%d/%y", t, pt, ptlim); ! continue; ! case 'x': ! /* ! ** Version 3.0 of strftime from Arnold Robbins ! ** (arnold@skeeve.atl.ga.us) does the ! ** equivalent of... ! ** _fmt("%a %b %e %Y"); ! ** ...for %x; since the X3J11 C language ! ** standard calls for "date, using locale's ! ** date format," anything goes. Using just ! ** numbers (as here) makes Quakers happier. ! ** Word from Paul Eggert (eggert@twinsun.com) ! ** is that %Y-%m-%d is the ISO standard date ! ** format, specified in ISO 2014 and later ! ** ISO 8601:1988, with a summary available in ! ** pub/doc/ISO/english/ISO8601.ps.Z on ! ** ftp.uni-erlangen.de. ! ** (ado, 5/30/93) ! */ ! pt = _fmt("%m/%d/%y", t, pt, ptlim); continue; case 'd': pt = _conv(t->tm_mday, "%02d", pt, ptlim); --- 174,180 ---- "%02d", pt, ptlim); continue; case 'D': ! pt = _fmt("%m/%d/%y", t, pt, ptlim, locale); continue; case 'd': pt = _conv(t->tm_mday, "%02d", pt, ptlim); *************** *** 224,240 **** pt, ptlim); continue; case 'R': ! pt = _fmt("%H:%M", t, pt, ptlim); continue; case 'r': ! pt = _fmt("%I:%M:%S %p", t, pt, ptlim); continue; case 'S': pt = _conv(t->tm_sec, "%02d", pt, ptlim); continue; case 'T': ! case 'X': ! pt = _fmt("%H:%M:%S", t, pt, ptlim); continue; case 't': pt = _add("\t", pt, ptlim); --- 256,271 ---- pt, ptlim); continue; case 'R': ! pt = _fmt("%H:%M", t, pt, ptlim, locale); continue; case 'r': ! pt = _fmt("%I:%M:%S %p", t, pt, ptlim, locale); continue; case 'S': pt = _conv(t->tm_sec, "%02d", pt, ptlim); continue; case 'T': ! pt = _fmt("%H:%M:%S", t, pt, ptlim, locale); continue; case 't': pt = _add("\t", pt, ptlim); *************** *** 321,327 **** ** "date as dd-bbb-YYYY" ** (ado, 5/24/93) */ ! pt = _fmt("%e-%b-%Y", t, pt, ptlim); continue; case 'W': pt = _conv((t->tm_yday + 7 - --- 352,358 ---- ** "date as dd-bbb-YYYY" ** (ado, 5/24/93) */ ! pt = _fmt("%e-%b-%Y", t, pt, ptlim, locale); continue; case 'W': pt = _conv((t->tm_yday + 7 - *************** *** 332,337 **** --- 363,376 ---- case 'w': pt = _conv(t->tm_wday, "%d", pt, ptlim); continue; + case 'X': + pt = _fmt(_loc(locale)->X_fmt, t, + pt, ptlim, locale); + continue; + case 'x': + pt = _fmt(_loc(locale)->x_fmt, t, + pt, ptlim, locale); + continue; case 'y': pt = _conv((t->tm_year + TM_YEAR_BASE) % 100, "%02d", pt, ptlim); *************** *** 390,393 **** --- 429,526 ---- while (pt < ptlim && (*pt = *str++) != '\0') ++pt; return pt; + } + + static const struct lc_time * + _loc(locale) + struct lc_time *locale; + { + static const char locale_home[] = LOCALE_HOME; + static const char lc_time[] = "LC_TIME"; + static char *locale_buf; + static char locale_buf_C[] = "C"; + + int fd; + char *lbuf, *name, *p; + const char **ap, *plim; + char filename[FILENAME_MAX]; + struct stat st; + size_t namesize, bufsize; + + /* Use locale->mon[0] to signal whether locale is already set up. */ + if (locale->mon[0]) + return locale; + + #if HAVE_SETLOCALE + name = setlocale(LC_TIME, (char *)0); + #else + if (!(name = getenv("LC_ALL")) || !*name) + if (!(name = getenv(lc_time)) || !*name) + name = getenv("LANG"); + #endif + if (!name || !*name) + goto no_locale; + + /* If the locale name is the same as our cache, use the cache. */ + lbuf = locale_buf; + if (lbuf && strcmp(name, lbuf) == 0) { + p = lbuf; + for (ap=(const char **)locale; ap<(const char **)(locale + 1); ap++) + *ap = p += strlen(p) + 1; + return locale; + } + + /* Slurp the locale file into the cache. */ + namesize = strlen(name) + 1; + if (sizeof(filename) < sizeof(locale_home) + namesize + sizeof(lc_time)) + goto no_locale; + sprintf(filename, "%s/%s/%s", locale_home, name, lc_time); + fd = open(filename, O_RDONLY, 0); + if (fd < 0) + goto no_locale; + if (fstat(fd, &st) != 0) + goto bad_locale; + if (st.st_size <= 0) + goto bad_locale; + bufsize = namesize + st.st_size; + locale_buf = 0; + lbuf = + !lbuf || lbuf==locale_buf_C + ? malloc(bufsize) + : realloc(lbuf, bufsize); + if (!lbuf) + goto bad_locale; + strcpy(lbuf, name); + p = lbuf + namesize; + plim = p + st.st_size; + if (read(fd, p, (size_t)st.st_size) != st.st_size) + goto bad_lbuf; + if (close(fd) != 0) + goto bad_lbuf; + + /* Parse the locale file into *locale. */ + if (plim[-1] != '\n') + goto bad_lbuf; + for (ap=(const char **)locale; ap<(const char **)(locale + 1); ap++) { + if (p == plim) + goto bad_lbuf; + *ap = p; + while (*p != '\n') + p++; + *p++ = 0; + } + + /* Record the successful parse in the cache. */ + locale_buf = lbuf; + + return locale; + + bad_lbuf: + free(lbuf); + bad_locale: + (void) close(fd); + no_locale: + *locale = C_time_locale; + locale_buf = locale_buf_C; + return locale; }
participants (1)
-
yata!eggert@twinsun.com