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(a)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(a)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;
}