Menu

#1201 clock format - use Control Panel %X, %x, %c

obsolete: 8.4a2
closed-fixed
5
2004-08-19
2000-10-26
Anonymous
No

OriginalBugID: 1192 Bug
Version: 8.1a2
SubmitDate: '1999-02-11'
LastModified: '2000-04-03'
Severity: MED
Status: Assigned
Submitter: pat
ChangedBy: hobbs
RelatedBugIDs: 1612 740 784 954 847 1245 1363
OS: Windows 98
Machine: X86
FixedDate: '2000-10-25'
ClosedDate: '2000-10-25'

Name:
Christopher Nelson

ReproducibleScript:
clock format [clock seconds] -format "c:%c x:%x X:%X"

ObservedBehavior:
Produced the output:
c:Sat Feb 06 11:54:27 1999 x:02/06/99 X:11:54:27
which is inconsistent with the Regional Settings for time and date format on my W98 system.

DesiredBehavior:
I'd expect %c, %x and %X to use the formats configured by the user in the Control Panel.

Verified with 8.2.3
-- 04/03/2000 hobbs

Discussion

  • Donal K. Fellows

    This seems to be because (in 8.4a2 at least) the code in .../compat/strftime.c uses a single hard-coded locale definition and does not provide any way for this to be updated to anything else. Fixing this is going to be a bit awkward, but anyone willing to take it on should start by copying the above file into .../win/tclWinTime.c and then modifying the locale code to pull the info from the right spot...

    A more complete solution would allow changing locales on the fly, but that's a bit more complex!

     
  • Donal K. Fellows

    • milestone: 102420 --> obsolete: 8.4a2
     
  • Don Porter

    Don Porter - 2001-02-08
    • assigned_to: nobody --> kennykb
    • labels: 104236 --> 06. Time Measurement
     
  • Jonathan E. Guyer

    Same problem on Macintosh. The patch below fixes this problem. E.g., with U.S. settings:

    % clock format [clock seconds] -format "c:%c x:%x X:%X"

    yields

    c:Thu, Feb 15, 2001 12:16:47 AM x:2/15/01 X:12:16:47 AM

    and with Swedish settings it yields

    c:tor 15 feb 2001 00.16.16 x:01-02-15 X:00.16.16

    In addition to the listed changes to tclMacTime.c, remove strftime.c from TclLibraries.\pi

    clock.test has 10 failures on Mac, but it had them before this patch. They all look like epoch problems.

    RCS file: /cvsroot/tcl/tcl/mac/tclMacTime.c,v
    retrieving revision 1.3
    diff -u -2 -r1.3 tclMacTime.c
    --- tcl/mac/tclMacTime.c 1999/03/10 05:52:51 1.3
    +++ tcl/mac/tclMacTime.c 2001/02/15 05:10:27
    @@ -18,4 +18,7 @@
    #include <Timer.h>
    #include <time.h>
    +#include <string.h>
    +#include <locale.h>
    +#include <TextUtils.h>

    /*
    @@ -312,2 +315,500 @@
    }
    #endif
    +
    +/*
    + * Copyright (c) 1989 The Regents of the University of California.
    + * All rights reserved.
    + *
    + * Redistribution and use in source and binary forms, with or without
    + * modification, are permitted provided that the following conditions
    + * are met:
    + * 1. Redistributions of source code must retain the above copyright
    + * notice, this list of conditions and the following disclaimer.
    + * 2. Redistributions in binary form must reproduce the above copyright
    + * notice, this list of conditions and the following disclaimer in the
    + * documentation and/or other materials provided with the distribution.
    + * 3. All advertising materials mentioning features or use of this software
    + * must display the following acknowledgement:
    + * This product includes software developed by the University of
    + * California, Berkeley and its contributors.
    + * 4. Neither the name of the University nor the names of its contributors
    + * may be used to endorse or promote products derived from this software
    + * without specific prior written permission.
    + *
    + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
    + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
    + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
    + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
    + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    + * SUCH DAMAGE.
    + */
    +
    +#define TM_YEAR_BASE 1900
    +#define IsLeapYear(x) ((x % 4 == 0) && (x % 100 != 0 || x % 400 == 0))
    +
    +typedef struct {
    + const char *abday[7];
    + const char *day[7];
    + const char *abmon[12];
    + const char *mon[12];
    + const char *am_pm[2];
    + const char *d_t_fmt;
    + const char *d_fmt;
    + const char *t_fmt;
    + const char *t_fmt_ampm;
    +} _TimeLocale;
    +
    +static const _TimeLocale _DefaultTimeLocale =
    +{
    + {
    + "Sun","Mon","Tue","Wed","Thu","Fri","Sat",
    + },
    + {
    + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
    + "Friday", "Saturday"
    + },
    + {
    + "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    + },
    + {
    + "January", "February", "March", "April", "May", "June", "July",
    + "August", "September", "October", "November", "December"
    + },
    + {
    + "AM", "PM"
    + },
    + "%a %b %d %H:%M:%S %Y",
    + "%m/%d/%y",
    + "%H:%M:%S",
    + "%I:%M:%S %p"
    +};
    +
    +static const _TimeLocale *_CurrentTimeLocale = &_DefaultTimeLocale;
    +
    +static size_t gsize;
    +static char *pt;
    +static int _add _ANSI_ARGS_((const char* str));
    +static int _conv _ANSI_ARGS_((int n, int digits, int pad));
    +static int _secs _ANSI_ARGS_((const struct tm *t));
    +static size_t _fmt _ANSI_ARGS_((const char *format,
    + const struct tm *t));
    +static time_t _dateToSecs _ANSI_ARGS_((const struct tm *t, int useGMT));
    +static int _localDate _ANSI_ARGS_((const struct tm *t, DateForm longFlag));
    +static int _localTime _ANSI_ARGS_((const struct tm *t));
    +
    +/*
    + *-----------------------------------------------------------------------------
    + *
    + * TclpStrftime --
    + *
    + * This is a modified version of the BSD 4.4 strftime function. It supports
    + * Macintosh localized date and time formats.
    + *
    + * Results:
    + * Number of characters formatted.
    + *
    + * Side effects:
    + * s contains the formatted time string.
    + *
    + *-----------------------------------------------------------------------------
    + */
    +size_t
    +TclpStrftime(s, maxsize, format, t)
    + char *s;
    + size_t maxsize;
    + const char *format;
    + const struct tm *t;
    +{
    + if (format[0] == '%' && format[1] == 'Q') {
    + /* Format as a stardate */
    + sprintf(s, "Stardate %2d%03d.%01d",
    + (((t->tm_year + TM_YEAR_BASE) + 377) - 2323),
    + (((t->tm_yday + 1) * 1000) /
    + (365 + IsLeapYear((t->tm_year + TM_YEAR_BASE)))),
    + (((t->tm_hour * 60) + t->tm_min)/144));
    + return(strlen(s));
    + }
    +
    + tzset();
    +
    + pt = s;
    + if ((gsize = maxsize) < 1)
    + return(0);
    + if (_fmt(format, t)) {
    + *pt = '\0';
    + return(maxsize - gsize);
    + }
    + return(0);
    +}
    +
    +#define SUN_WEEK(t) (((t)->tm_yday + 7 - \ + ((t)->tm_wday)) / 7)
    +#define MON_WEEK(t) (((t)->tm_yday + 7 - \ + ((t)->tm_wday ? (t)->tm_wday - 1 : 6)) / 7)
    +
    +static size_t
    +_fmt(format, t)
    + const char *format;
    + const struct tm *t;
    +{
    + for (; *format; ++format) {
    + if (*format == '%') {
    + ++format;
    + if (*format == 'E') {
    + /* Alternate Era */
    + ++format;
    + } else if (*format == 'O') {
    + /* Alternate numeric symbols */
    + ++format;
    + }
    + switch(*format) {
    + case '\0':
    + --format;
    + break;
    + case 'A':
    + if (t->tm_wday < 0 || t->tm_wday > 6)
    + return(0);
    + if (!_add(_CurrentTimeLocale->day[t->tm_wday]))
    + return(0);
    + continue;
    + case 'a':
    + if (t->tm_wday < 0 || t->tm_wday > 6)
    + return(0);
    + if (!_add(_CurrentTimeLocale->abday[t->tm_wday]))
    + return(0);
    + continue;
    + case 'B':
    + if (t->tm_mon < 0 || t->tm_mon > 11)
    + return(0);
    + if (!_add(_CurrentTimeLocale->mon[t->tm_mon]))
    + return(0);
    + continue;
    + case 'b':
    + case 'h':
    + if (t->tm_mon < 0 || t->tm_mon > 11)
    + return(0);
    + if (!_add(_CurrentTimeLocale->abmon[t->tm_mon]))
    + return(0);
    + continue;
    + case 'C':
    + if (!_conv((t->tm_year + TM_YEAR_BASE) / 100,
    + 2, '0'))
    + return(0);
    + continue;
    + case 'c':
    + if (!_localDate(t, abbrevDate)
    + || !_add(" ")
    + || !_localTime(t))
    + return(0);
    + continue;
    + case 'D':
    + if (!_fmt("%m/%d/%y", t))
    + return(0);
    + continue;
    + case 'd':
    + if (!_conv(t->tm_mday, 2, '0'))
    + return(0);
    + continue;
    + case 'e':
    + if (!_conv(t->tm_mday, 2, ' '))
    + return(0);
    + continue;
    + case 'H':
    + if (!_conv(t->tm_hour, 2, '0'))
    + return(0);
    + continue;
    + case 'I':
    + if (!_conv(t->tm_hour % 12 ?
    + t->tm_hour % 12 : 12, 2, '0'))
    + return(0);
    + continue;
    + case 'j':
    + if (!_conv(t->tm_yday + 1, 3, '0'))
    + return(0);
    + continue;
    + case 'k':
    + if (!_conv(t->tm_hour, 2, ' '))
    + return(0);
    + continue;
    + case 'l':
    + if (!_conv(t->tm_hour % 12 ?
    + t->tm_hour % 12: 12, 2, ' '))
    + return(0);
    + continue;
    + case 'M':
    + if (!_conv(t->tm_min, 2, '0'))
    + return(0);
    + continue;
    + case 'm':
    + if (!_conv(t->tm_mon + 1, 2, '0'))
    + return(0);
    + continue;
    + case 'n':
    + if (!_add("\n"))
    + return(0);
    + continue;
    + case 'p':
    + if (!_add(_CurrentTimeLocale->am_pm[t->tm_hour >= 12]))
    + return(0);
    + continue;
    + case 'R':
    + if (!_fmt("%H:%M", t))
    + return(0);
    + continue;
    + case 'r':
    + if (!_fmt(_CurrentTimeLocale->t_fmt_ampm, t))
    + return(0);
    + continue;
    + case 'S':
    + if (!_conv(t->tm_sec, 2, '0'))
    + return(0);
    + continue;
    + case 's':
    + if (!_secs(t))
    + return(0);
    + continue;
    + case 'T':
    + if (!_fmt("%H:%M:%S", t))
    + return(0);
    + continue;
    + case 't':
    + if (!_add("\t"))
    + return(0);
    + continue;
    + case 'U':
    + if (!_conv(SUN_WEEK(t), 2, '0'))
    + return(0);
    + continue;
    + case 'u':
    + if (!_conv(t->tm_wday ? t->tm_wday : 7, 1, '0'))
    + return(0);
    + continue;
    + case 'V':
    + {
    + /* ISO 8601 Week Of Year:
    + If the week (Monday - Sunday) containing
    + January 1 has four or more days in the new
    + year, then it is week 1; otherwise it is
    + week 53 of the previous year and the next
    + week is week one. */
    +
    + int week = MON_WEEK(t);
    +
    + int days = (((t)->tm_yday + 7 - \ + ((t)->tm_wday ? (t)->tm_wday - 1 : 6)) % 7);
    +
    +
    + if (days >= 4) {
    + week++;
    + } else if (week == 0) {
    + week = 53;
    + }
    +
    + if (!_conv(week, 2, '0'))
    + return(0);
    + continue;
    + }
    + case 'W':
    + if (!_conv(MON_WEEK(t), 2, '0'))
    + return(0);
    + continue;
    + case 'w':
    + if (!_conv(t->tm_wday, 1, '0'))
    + return(0);
    + continue;
    + case 'x':
    + if (!_localDate(t, shortDate))
    + return(0);
    + continue;
    + case 'X':
    + if (!_localTime(t))
    + return(0);
    + continue;
    + case 'y':
    + if (!_conv((t->tm_year + TM_YEAR_BASE) % 100,
    + 2, '0'))
    + return(0);
    + continue;
    + case 'Y':
    + if (!_conv((t->tm_year + TM_YEAR_BASE), 4, '0'))
    + return(0);
    + continue;
    +#ifndef MAC_TCL
    + case 'Z': {
    + char *name = TclpGetTZName(t->tm_isdst);
    + if (name && !_add(name)) {
    + return 0;
    + }
    + continue;
    + }
    +#endif
    + case '%':
    + /*
    + * X311J/88-090 (4.12.3.5): if conversion char is
    + * undefined, behavior is undefined. Print out the
    + * character itself as printf(3) does.
    + */
    + default:
    + break;
    + }
    + }
    + if (!gsize--)
    + return(0);
    + *pt++ = *format;
    + }
    + return(gsize);
    +}
    +
    +static int
    +_secs(t)
    + const struct tm *t;
    +{
    + static char buf[15];
    + register time_t s;
    + register char *p;
    + struct tm tmp;
    +
    + /* Make a copy, mktime(3) modifies the tm struct. */
    + tmp = *t;
    + s = mktime(&tmp);
    + for (p = buf + sizeof(buf) - 2; s > 0 && p > buf; s /= 10)
    + *p-- = (char)(s % 10 + '0');
    + return(_add(++p));
    +}
    +
    +static int
    +_conv(n, digits, pad)
    + int n, digits;
    + int pad;
    +{
    + static char buf[10];
    + register char *p;
    +
    + for (p = buf + sizeof(buf) - 2; n > 0 && p > buf; n /= 10, --digits)
    + *p-- = (char)(n % 10 + '0');
    + while (p > buf && digits-- > 0)
    + *p-- = (char) pad;
    + return(_add(++p));
    +}
    +
    +static int
    +_add(str)
    + const char *str;
    +{
    + for (;; ++pt, --gsize) {
    + if (!gsize)
    + return(0);
    + if (!(*pt = *str++))
    + return(1);
    + }
    +}
    +
    +/*
    + *----------------------------------------------------------------------
    + *
    + * _dateToSecs --
    + *
    + * Converts a struct tm data structure to raw seconds. The
    + * returned time will be for Greenwich Mean Time. This function
    + * is meant to be used as a replacement
    + * for mktime which is broken(?) on most ANSI libs
    + * on the Macintosh.
    + *
    + * Results:
    + * Time in seconds since epoch.
    + *
    + * Side effects:
    + * None.
    + *
    + *----------------------------------------------------------------------
    + */
    +static time_t
    +_dateToSecs(
    + const struct tm *t) /* Time struct to read. */
    +{
    + time_t s;
    + DateTimeRec dtr;
    +
    + dtr.second = t->tm_sec;
    + dtr.minute = t->tm_min;
    + dtr.hour = t->tm_hour;
    + dtr.day = t->tm_mday;
    + dtr.month = t->tm_mon + 1;
    + dtr.year = t->tm_year + 1900;
    + dtr.dayOfWeek = t->tm_wday + 1;
    +
    + DateToSeconds(&dtr, (unsigned long *) &s);
    +
    + return(s);
    +}
    +
    +/*
    + *----------------------------------------------------------------------
    + *
    + * _localDate --
    + *
    + * Format tm data structure in localized Macintosh date form.
    + *
    + * Results:
    + * Success or failure.
    + *
    + * Side effects:
    + * Formatted string is appended to buffer.
    + *
    + *----------------------------------------------------------------------
    + */
    +static int
    +_localDate(const struct tm *t, DateForm longFlag)
    +{
    + Str255 dateString;
    + time_t s;
    +
    + s = _dateToSecs(t);
    +
    + DateString(s, longFlag, dateString, nil);
    + if (dateString[0] < 255) {
    + dateString[dateString[0]+1] = '\0';
    + } else {
    + dateString[255] = '\0';
    + }
    + return _add((char *) &dateString[1]);
    +}
    +
    +/*
    + *----------------------------------------------------------------------
    + *
    + * _localTime --
    + *
    + * Format tm data structure in localized Macintosh time form.
    + *
    + * Results:
    + * Success or failure.
    + *
    + * Side effects:
    + * Formatted string is appended to buffer.
    + *
    + *----------------------------------------------------------------------
    + */
    +static int
    +_localTime(t)
    + const struct tm *t;
    +{
    + Str255 timeString;
    + time_t s;
    +
    + s = _dateToSecs(t);
    +
    + TimeString(s, true, timeString, nil);
    + if (timeString[0] < 255) {
    + timeString[timeString[0]+1] = '\0';
    + } else {
    + timeString[255] = '\0';
    + }
    + return _add((char *) &timeString[1]);
    +}

     
  • Kevin B KENNY

    Kevin B KENNY - 2001-02-15

    Thanks for the patch. I'm looking into the localization issues
    now. I probably won't use the patch verbatim, because I'm reluctant
    to copy all that logic from strftime.c into separate Mac, Windows and
    Unix versions. I'm hoping instead, to have a single strftime, and
    a single getdate, with system-specific stubs inserted only where
    necessary. Otherwise, keeping the documentation for [clock] in sync
    with the code on the various platforms is going to be tricky.
    (It's badly out of step already. My first task is to bring it up
    to date with what the existing code actually does.)

    I'm also concerned that strftime and getdate stay in sync.
    It should ALWAYS be possible to do [clock scan [clock format [clock seconds]]]
    and get the same answer back. Ideally, the default [clock format] should
    even be portable among locales. Some of this will mean that the
    English short-names for months will have to be recognized in every
    locale. (That's a good thing anyway, for parsing e-mail time stamps.)

    I'll stay in touch. Can I prevail upon you to do Mac testing
    with what I come up with? (I currently work in a "Mac-free environment.")

     
  • Jonathan E. Guyer

    The stubby way definitely sounds better. I didn't want to trample on other platforms and was just following Donal's guidance on where to put it.

    I thought about the scan-format reversibility thing, but set it aside (for hopefully obvious reasons). When it comes to it, StringToDate() and StringToTime() should handle most of the dirty work on the Mac. I'm happy to do whatever testing is needed on Mac.

    >(I currently work in a "Mac-free environment.")

    You poor bastard. They shoot horses when they're just going to live out their lives in misery. 8^)

     
  • Nobody/Anonymous

    Logged In: NO

    Hello

    I have also such a problem on linux red had.
    when doing:
    [clock format [clock seconds] -format %b]
    I am getting local month name.

     
  • Kevin B KENNY

    Kevin B KENNY - 2004-03-05
    • summary: clock format not sufficiently localized --> clock format - use Control Panel %X, %x, %c
    • status: open --> closed
     
  • Kevin B KENNY

    Kevin B KENNY - 2004-03-05

    Logged In: YES
    user_id=99768

    [clock format] actually should NOT use the system's
    date/time format *by default*, since as often as not,
    it's formatting a time for a program, rather than a human,
    to read. Instead, it should accept a -locale parameter
    that in turn designates the correct locale-dependent
    %X, %x and %c strings (also the %E and %O behavior!)

    I have a sandbox implementation that works this way,
    but of course it will take a TIP (that I'm not yet ready
    to submit) to fix it.

     
  • Jeffrey Hobbs

    Jeffrey Hobbs - 2004-03-05

    Logged In: YES
    user_id=72656

    The %[cxX] are defined to already be locale specific, so I
    disagree that any extra param is necessary. A -locale that
    does even more would be nice, but this is a bug as it is.
    FWIW, this works correctly on Windows now, so I'm not sure
    this needs to be open regardless.

     
  • Kevin B KENNY

    Kevin B KENNY - 2004-05-18
    • status: closed --> open
     
  • Kevin B KENNY

    Kevin B KENNY - 2004-08-19
    • status: open --> closed
     
  • Kevin B KENNY

    Kevin B KENNY - 2004-08-19
    • status: closed --> closed-fixed
     
  • Kevin B KENNY

    Kevin B KENNY - 2004-08-19

    Logged In: YES
    user_id=99768

    Implemented that way in 8.5 provided that the
    [clock format] command specifies -locale system.
    Without '-locale system', presumes the Tcl
    root locale.