sl@0: /* sl@0: * strftime.c -- sl@0: * sl@0: * This file contains a modified version of the BSD 4.4 strftime sl@0: * function. sl@0: * sl@0: * This file is a modified version of the strftime.c file from the BSD 4.4 sl@0: * source. See the copyright notice below for details on redistribution sl@0: * restrictions. The "license.terms" file does not apply to this file. sl@0: * sl@0: * Changes 2002 Copyright (c) 2002 ActiveState Corporation. sl@0: * sl@0: * RCS: @(#) $Id: strftime.c,v 1.10.2.3 2005/11/04 18:18:04 kennykb Exp $ sl@0: */ sl@0: sl@0: /* sl@0: * Copyright (c) 1989 The Regents of the University of California. sl@0: * All rights reserved. sl@0: * sl@0: * Redistribution and use in source and binary forms, with or without sl@0: * modification, are permitted provided that the following conditions sl@0: * are met: sl@0: * 1. Redistributions of source code must retain the above copyright sl@0: * notice, this list of conditions and the following disclaimer. sl@0: * 2. Redistributions in binary form must reproduce the above copyright sl@0: * notice, this list of conditions and the following disclaimer in the sl@0: * documentation and/or other materials provided with the distribution. sl@0: * 3. All advertising materials mentioning features or use of this software sl@0: * must display the following acknowledgement: sl@0: * This product includes software developed by the University of sl@0: * California, Berkeley and its contributors. sl@0: * 4. Neither the name of the University nor the names of its contributors sl@0: * may be used to endorse or promote products derived from this software sl@0: * without specific prior written permission. sl@0: * sl@0: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND sl@0: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE sl@0: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE sl@0: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE sl@0: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL sl@0: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS sl@0: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) sl@0: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT sl@0: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY sl@0: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF sl@0: * SUCH DAMAGE. sl@0: */ sl@0: sl@0: #if defined(LIBC_SCCS) sl@0: static char *rcsid = "$Id: strftime.c,v 1.10.2.3 2005/11/04 18:18:04 kennykb Exp $"; sl@0: #endif /* LIBC_SCCS */ sl@0: sl@0: #include sl@0: #include sl@0: #include sl@0: #include "tclInt.h" sl@0: #include "tclPort.h" sl@0: sl@0: #define TM_YEAR_BASE 1900 sl@0: #define IsLeapYear(x) ((x % 4 == 0) && (x % 100 != 0 || x % 400 == 0)) sl@0: sl@0: typedef struct { sl@0: const char *abday[7]; sl@0: const char *day[7]; sl@0: const char *abmon[12]; sl@0: const char *mon[12]; sl@0: const char *am_pm[2]; sl@0: const char *d_t_fmt; sl@0: const char *d_fmt; sl@0: const char *t_fmt; sl@0: const char *t_fmt_ampm; sl@0: } _TimeLocale; sl@0: sl@0: /* sl@0: * This is the C locale default. On Windows, if we wanted to make this sl@0: * localized, we would use GetLocaleInfo to get the correct values. sl@0: * It may be acceptable to do localization of month/day names, as the sl@0: * numerical values would be considered the locale-independent versions. sl@0: */ sl@0: static const _TimeLocale _DefaultTimeLocale = sl@0: { sl@0: { sl@0: "Sun","Mon","Tue","Wed","Thu","Fri","Sat", sl@0: }, sl@0: { sl@0: "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", sl@0: "Friday", "Saturday" sl@0: }, sl@0: { sl@0: "Jan", "Feb", "Mar", "Apr", "May", "Jun", sl@0: "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" sl@0: }, sl@0: { sl@0: "January", "February", "March", "April", "May", "June", "July", sl@0: "August", "September", "October", "November", "December" sl@0: }, sl@0: { sl@0: "AM", "PM" sl@0: }, sl@0: "%a %b %d %H:%M:%S %Y", sl@0: "%m/%d/%y", sl@0: "%H:%M:%S", sl@0: "%I:%M:%S %p" sl@0: }; sl@0: sl@0: static const _TimeLocale *_CurrentTimeLocale = &_DefaultTimeLocale; sl@0: sl@0: static int isGMT; sl@0: static size_t gsize; sl@0: static char *pt; sl@0: static int _add _ANSI_ARGS_((const char* str)); sl@0: static int _conv _ANSI_ARGS_((int n, int digits, int pad)); sl@0: static int _secs _ANSI_ARGS_((const struct tm *t)); sl@0: static size_t _fmt _ANSI_ARGS_((const char *format, sl@0: const struct tm *t)); sl@0: static int ISO8601Week _ANSI_ARGS_((CONST struct tm* t, int *year )); sl@0: sl@0: size_t sl@0: TclpStrftime(s, maxsize, format, t, useGMT) sl@0: char *s; sl@0: size_t maxsize; sl@0: const char *format; sl@0: const struct tm *t; sl@0: int useGMT; sl@0: { sl@0: if (format[0] == '%' && format[1] == 'Q') { sl@0: /* Format as a stardate */ sl@0: sprintf(s, "Stardate %2d%03d.%01d", sl@0: (((t->tm_year + TM_YEAR_BASE) + 377) - 2323), sl@0: (((t->tm_yday + 1) * 1000) / sl@0: (365 + IsLeapYear((t->tm_year + TM_YEAR_BASE)))), sl@0: (((t->tm_hour * 60) + t->tm_min)/144)); sl@0: return(strlen(s)); sl@0: } sl@0: sl@0: isGMT = useGMT; sl@0: /* sl@0: * We may be able to skip this for useGMT, but it should be harmless. sl@0: * -- hobbs sl@0: */ sl@0: tzset(); sl@0: sl@0: pt = s; sl@0: if ((gsize = maxsize) < 1) sl@0: return(0); sl@0: if (_fmt(format, t)) { sl@0: *pt = '\0'; sl@0: return(maxsize - gsize); sl@0: } sl@0: return(0); sl@0: } sl@0: sl@0: #define SUN_WEEK(t) (((t)->tm_yday + 7 - \ sl@0: ((t)->tm_wday)) / 7) sl@0: #define MON_WEEK(t) (((t)->tm_yday + 7 - \ sl@0: ((t)->tm_wday ? (t)->tm_wday - 1 : 6)) / 7) sl@0: sl@0: static size_t sl@0: _fmt(format, t) sl@0: const char *format; sl@0: const struct tm *t; sl@0: { sl@0: #ifdef WIN32 sl@0: #define BUF_SIZ 256 sl@0: TCHAR buf[BUF_SIZ]; sl@0: SYSTEMTIME syst = { sl@0: t->tm_year + 1900, sl@0: t->tm_mon + 1, sl@0: t->tm_wday, sl@0: t->tm_mday, sl@0: t->tm_hour, sl@0: t->tm_min, sl@0: t->tm_sec, sl@0: 0, sl@0: }; sl@0: #endif sl@0: for (; *format; ++format) { sl@0: if (*format == '%') { sl@0: ++format; sl@0: if (*format == 'E') { sl@0: /* Alternate Era */ sl@0: ++format; sl@0: } else if (*format == 'O') { sl@0: /* Alternate numeric symbols */ sl@0: ++format; sl@0: } sl@0: switch(*format) { sl@0: case '\0': sl@0: --format; sl@0: break; sl@0: case 'A': sl@0: if (t->tm_wday < 0 || t->tm_wday > 6) sl@0: return(0); sl@0: if (!_add(_CurrentTimeLocale->day[t->tm_wday])) sl@0: return(0); sl@0: continue; sl@0: case 'a': sl@0: if (t->tm_wday < 0 || t->tm_wday > 6) sl@0: return(0); sl@0: if (!_add(_CurrentTimeLocale->abday[t->tm_wday])) sl@0: return(0); sl@0: continue; sl@0: case 'B': sl@0: if (t->tm_mon < 0 || t->tm_mon > 11) sl@0: return(0); sl@0: if (!_add(_CurrentTimeLocale->mon[t->tm_mon])) sl@0: return(0); sl@0: continue; sl@0: case 'b': sl@0: case 'h': sl@0: if (t->tm_mon < 0 || t->tm_mon > 11) sl@0: return(0); sl@0: if (!_add(_CurrentTimeLocale->abmon[t->tm_mon])) sl@0: return(0); sl@0: continue; sl@0: case 'C': sl@0: if (!_conv((t->tm_year + TM_YEAR_BASE) / 100, sl@0: 2, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'D': sl@0: if (!_fmt("%m/%d/%y", t)) sl@0: return(0); sl@0: continue; sl@0: case 'd': sl@0: if (!_conv(t->tm_mday, 2, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'e': sl@0: if (!_conv(t->tm_mday, 2, ' ')) sl@0: return(0); sl@0: continue; sl@0: case 'g': sl@0: { sl@0: int year; sl@0: ISO8601Week( t, &year ); sl@0: if ( !_conv( year%100, 2, '0' ) ) { sl@0: return( 0 ); sl@0: } sl@0: continue; sl@0: } sl@0: case 'G': sl@0: { sl@0: int year; sl@0: ISO8601Week( t, &year ); sl@0: if ( !_conv( year, 4, '0' ) ) { sl@0: return( 0 ); sl@0: } sl@0: continue; sl@0: } sl@0: case 'H': sl@0: if (!_conv(t->tm_hour, 2, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'I': sl@0: if (!_conv(t->tm_hour % 12 ? sl@0: t->tm_hour % 12 : 12, 2, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'j': sl@0: if (!_conv(t->tm_yday + 1, 3, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'k': sl@0: if (!_conv(t->tm_hour, 2, ' ')) sl@0: return(0); sl@0: continue; sl@0: case 'l': sl@0: if (!_conv(t->tm_hour % 12 ? sl@0: t->tm_hour % 12: 12, 2, ' ')) sl@0: return(0); sl@0: continue; sl@0: case 'M': sl@0: if (!_conv(t->tm_min, 2, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'm': sl@0: if (!_conv(t->tm_mon + 1, 2, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'n': sl@0: if (!_add("\n")) sl@0: return(0); sl@0: continue; sl@0: case 'p': sl@0: if (!_add(_CurrentTimeLocale->am_pm[t->tm_hour >= 12])) sl@0: return(0); sl@0: continue; sl@0: case 'R': sl@0: if (!_fmt("%H:%M", t)) sl@0: return(0); sl@0: continue; sl@0: case 'r': sl@0: if (!_fmt(_CurrentTimeLocale->t_fmt_ampm, t)) sl@0: return(0); sl@0: continue; sl@0: case 'S': sl@0: if (!_conv(t->tm_sec, 2, '0')) sl@0: return(0); sl@0: continue; sl@0: case 's': sl@0: if (!_secs(t)) sl@0: return(0); sl@0: continue; sl@0: case 'T': sl@0: if (!_fmt("%H:%M:%S", t)) sl@0: return(0); sl@0: continue; sl@0: case 't': sl@0: if (!_add("\t")) sl@0: return(0); sl@0: continue; sl@0: case 'U': sl@0: if (!_conv(SUN_WEEK(t), 2, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'u': sl@0: if (!_conv(t->tm_wday ? t->tm_wday : 7, 1, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'V': sl@0: { sl@0: int week = ISO8601Week( t, NULL ); sl@0: if (!_conv(week, 2, '0')) sl@0: return(0); sl@0: continue; sl@0: } sl@0: case 'W': sl@0: if (!_conv(MON_WEEK(t), 2, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'w': sl@0: if (!_conv(t->tm_wday, 1, '0')) sl@0: return(0); sl@0: continue; sl@0: #ifdef WIN32 sl@0: /* sl@0: * To properly handle the localized time routines on Windows, sl@0: * we must make use of the special localized calls. sl@0: */ sl@0: case 'c': sl@0: if (!GetDateFormat(LOCALE_USER_DEFAULT, sl@0: DATE_LONGDATE | LOCALE_USE_CP_ACP, sl@0: &syst, NULL, buf, BUF_SIZ) sl@0: || !_add(buf) sl@0: || !_add(" ")) { sl@0: return(0); sl@0: } sl@0: /* sl@0: * %c is created with LONGDATE + " " + TIME on Windows, sl@0: * so continue to %X case here. sl@0: */ sl@0: case 'X': sl@0: if (!GetTimeFormat(LOCALE_USER_DEFAULT, sl@0: LOCALE_USE_CP_ACP, sl@0: &syst, NULL, buf, BUF_SIZ) sl@0: || !_add(buf)) { sl@0: return(0); sl@0: } sl@0: continue; sl@0: case 'x': sl@0: if (!GetDateFormat(LOCALE_USER_DEFAULT, sl@0: DATE_SHORTDATE | LOCALE_USE_CP_ACP, sl@0: &syst, NULL, buf, BUF_SIZ) sl@0: || !_add(buf)) { sl@0: return(0); sl@0: } sl@0: continue; sl@0: #else sl@0: case 'c': sl@0: if (!_fmt(_CurrentTimeLocale->d_t_fmt, t)) sl@0: return(0); sl@0: continue; sl@0: case 'x': sl@0: if (!_fmt(_CurrentTimeLocale->d_fmt, t)) sl@0: return(0); sl@0: continue; sl@0: case 'X': sl@0: if (!_fmt(_CurrentTimeLocale->t_fmt, t)) sl@0: return(0); sl@0: continue; sl@0: #endif sl@0: case 'y': sl@0: if (!_conv((t->tm_year + TM_YEAR_BASE) % 100, sl@0: 2, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'Y': sl@0: if (!_conv((t->tm_year + TM_YEAR_BASE), 4, '0')) sl@0: return(0); sl@0: continue; sl@0: case 'Z': { sl@0: char *name = (isGMT ? "GMT" : TclpGetTZName(t->tm_isdst)); sl@0: int wrote; sl@0: Tcl_UtfToExternal(NULL, NULL, name, -1, 0, NULL, sl@0: pt, gsize, NULL, &wrote, NULL); sl@0: pt += wrote; sl@0: gsize -= wrote; sl@0: continue; sl@0: } sl@0: case '%': sl@0: /* sl@0: * X311J/88-090 (4.12.3.5): if conversion char is sl@0: * undefined, behavior is undefined. Print out the sl@0: * character itself as printf(3) does. sl@0: */ sl@0: default: sl@0: break; sl@0: } sl@0: } sl@0: if (!gsize--) sl@0: return(0); sl@0: *pt++ = *format; sl@0: } sl@0: return(gsize); sl@0: } sl@0: sl@0: static int sl@0: _secs(t) sl@0: const struct tm *t; sl@0: { sl@0: static char buf[15]; sl@0: register time_t s; sl@0: register char *p; sl@0: struct tm tmp; sl@0: sl@0: /* Make a copy, mktime(3) modifies the tm struct. */ sl@0: tmp = *t; sl@0: s = mktime(&tmp); sl@0: for (p = buf + sizeof(buf) - 2; s > 0 && p > buf; s /= 10) sl@0: *p-- = (char)(s % 10 + '0'); sl@0: return(_add(++p)); sl@0: } sl@0: sl@0: static int sl@0: _conv(n, digits, pad) sl@0: int n, digits; sl@0: int pad; sl@0: { sl@0: static char buf[10]; sl@0: register char *p; sl@0: sl@0: p = buf + sizeof( buf ) - 1; sl@0: *p-- = '\0'; sl@0: if ( n == 0 ) { sl@0: *p-- = '0'; --digits; sl@0: } else { sl@0: for (; n > 0 && p > buf; n /= 10, --digits) sl@0: *p-- = (char)(n % 10 + '0'); sl@0: } sl@0: while (p > buf && digits-- > 0) sl@0: *p-- = (char) pad; sl@0: return(_add(++p)); sl@0: } sl@0: sl@0: static int sl@0: _add(str) sl@0: const char *str; sl@0: { sl@0: for (;; ++pt, --gsize) { sl@0: if (!gsize) sl@0: return(0); sl@0: if (!(*pt = *str++)) sl@0: return(1); sl@0: } sl@0: } sl@0: sl@0: static int sl@0: ISO8601Week( t, year ) sl@0: CONST struct tm* t; sl@0: int* year; sl@0: { sl@0: /* Find the day-of-year of the Thursday in sl@0: * the week in question. */ sl@0: sl@0: int ydayThursday; sl@0: int week; sl@0: if ( t->tm_wday == 0 ) { sl@0: ydayThursday = t->tm_yday - 3; sl@0: } else { sl@0: ydayThursday = t->tm_yday - t->tm_wday + 4; sl@0: } sl@0: sl@0: if ( ydayThursday < 0 ) { sl@0: sl@0: /* This is the last week of the previous year. */ sl@0: if ( IsLeapYear(( t->tm_year + TM_YEAR_BASE - 1 )) ) { sl@0: ydayThursday += 366; sl@0: } else { sl@0: ydayThursday += 365; sl@0: } sl@0: week = ydayThursday / 7 + 1; sl@0: if ( year != NULL ) { sl@0: *year = t->tm_year + 1899; sl@0: } sl@0: sl@0: } else if ( ( IsLeapYear(( t -> tm_year + TM_YEAR_BASE )) sl@0: && ydayThursday >= 366 ) sl@0: || ( !IsLeapYear(( t -> tm_year sl@0: + TM_YEAR_BASE )) sl@0: && ydayThursday >= 365 ) ) { sl@0: sl@0: /* This is week 1 of the following year */ sl@0: sl@0: week = 1; sl@0: if ( year != NULL ) { sl@0: *year = t->tm_year + 1901; sl@0: } sl@0: sl@0: } else { sl@0: sl@0: week = ydayThursday / 7 + 1; sl@0: if ( year != NULL ) { sl@0: *year = t->tm_year + 1900; sl@0: } sl@0: sl@0: } sl@0: sl@0: return week; sl@0: sl@0: }