1/*
2 * strftime.c --
3 *
4 *	This file contains a modified version of the BSD 4.4 strftime
5 *	function.
6 *
7 * This file is a modified version of the strftime.c file from the BSD 4.4
8 * source.  See the copyright notice below for details on redistribution
9 * restrictions.  The "license.terms" file does not apply to this file.
10 *
11 * Changes 2002 Copyright (c) 2002 ActiveState Corporation.
12 *
13 * RCS: @(#) $Id: strftime.c,v 1.10.2.3 2005/11/04 18:18:04 kennykb Exp $
14 */
15
16/*
17 * Copyright (c) 1989 The Regents of the University of California.
18 * All rights reserved.
19 *
20 * Redistribution and use in source and binary forms, with or without
21 * modification, are permitted provided that the following conditions
22 * are met:
23 * 1. Redistributions of source code must retain the above copyright
24 *    notice, this list of conditions and the following disclaimer.
25 * 2. Redistributions in binary form must reproduce the above copyright
26 *    notice, this list of conditions and the following disclaimer in the
27 *    documentation and/or other materials provided with the distribution.
28 * 3. All advertising materials mentioning features or use of this software
29 *    must display the following acknowledgement:
30 *	This product includes software developed by the University of
31 *	California, Berkeley and its contributors.
32 * 4. Neither the name of the University nor the names of its contributors
33 *    may be used to endorse or promote products derived from this software
34 *    without specific prior written permission.
35 *
36 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
37 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
38 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
39 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
40 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
41 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
42 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
44 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
45 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46 * SUCH DAMAGE.
47 */
48
49#if defined(LIBC_SCCS)
50static char *rcsid = "$Id: strftime.c,v 1.10.2.3 2005/11/04 18:18:04 kennykb Exp $";
51#endif /* LIBC_SCCS */
52
53#include <time.h>
54#include <string.h>
55#include <locale.h>
56#include "tclInt.h"
57#include "tclPort.h"
58
59#define TM_YEAR_BASE   1900
60#define IsLeapYear(x)   ((x % 4 == 0) && (x % 100 != 0 || x % 400 == 0))
61
62typedef struct {
63    const char *abday[7];
64    const char *day[7];
65    const char *abmon[12];
66    const char *mon[12];
67    const char *am_pm[2];
68    const char *d_t_fmt;
69    const char *d_fmt;
70    const char *t_fmt;
71    const char *t_fmt_ampm;
72} _TimeLocale;
73
74/*
75 * This is the C locale default.  On Windows, if we wanted to make this
76 * localized, we would use GetLocaleInfo to get the correct values.
77 * It may be acceptable to do localization of month/day names, as the
78 * numerical values would be considered the locale-independent versions.
79 */
80static const _TimeLocale _DefaultTimeLocale =
81{
82    {
83	"Sun","Mon","Tue","Wed","Thu","Fri","Sat",
84    },
85    {
86	"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
87	"Friday", "Saturday"
88    },
89    {
90	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
91	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
92    },
93    {
94	"January", "February", "March", "April", "May", "June", "July",
95	"August", "September", "October", "November", "December"
96    },
97    {
98	"AM", "PM"
99    },
100    "%a %b %d %H:%M:%S %Y",
101    "%m/%d/%y",
102    "%H:%M:%S",
103    "%I:%M:%S %p"
104};
105
106static const _TimeLocale *_CurrentTimeLocale = &_DefaultTimeLocale;
107
108static int isGMT;
109static size_t gsize;
110static char *pt;
111static int		 _add _ANSI_ARGS_((const char* str));
112static int		_conv _ANSI_ARGS_((int n, int digits, int pad));
113static int		_secs _ANSI_ARGS_((const struct tm *t));
114static size_t		_fmt _ANSI_ARGS_((const char *format,
115			    const struct tm *t));
116static int ISO8601Week _ANSI_ARGS_((CONST struct tm* t, int *year ));
117
118size_t
119TclpStrftime(s, maxsize, format, t, useGMT)
120    char *s;
121    size_t maxsize;
122    const char *format;
123    const struct tm *t;
124    int useGMT;
125{
126    if (format[0] == '%' && format[1] == 'Q') {
127	/* Format as a stardate */
128	sprintf(s, "Stardate %2d%03d.%01d",
129		(((t->tm_year + TM_YEAR_BASE) + 377) - 2323),
130		(((t->tm_yday + 1) * 1000) /
131			(365 + IsLeapYear((t->tm_year + TM_YEAR_BASE)))),
132		(((t->tm_hour * 60) + t->tm_min)/144));
133	return(strlen(s));
134    }
135
136    isGMT = useGMT;
137    /*
138     * We may be able to skip this for useGMT, but it should be harmless.
139     * -- hobbs
140     */
141    tzset();
142
143    pt = s;
144    if ((gsize = maxsize) < 1)
145	return(0);
146    if (_fmt(format, t)) {
147	*pt = '\0';
148	return(maxsize - gsize);
149    }
150    return(0);
151}
152
153#define SUN_WEEK(t)	(((t)->tm_yday + 7 - \
154				((t)->tm_wday)) / 7)
155#define MON_WEEK(t)	(((t)->tm_yday + 7 - \
156				((t)->tm_wday ? (t)->tm_wday - 1 : 6)) / 7)
157
158static size_t
159_fmt(format, t)
160    const char *format;
161    const struct tm *t;
162{
163#ifdef WIN32
164#define BUF_SIZ 256
165    TCHAR buf[BUF_SIZ];
166    SYSTEMTIME syst = {
167	t->tm_year + 1900,
168	t->tm_mon + 1,
169	t->tm_wday,
170	t->tm_mday,
171	t->tm_hour,
172	t->tm_min,
173	t->tm_sec,
174	0,
175    };
176#endif
177    for (; *format; ++format) {
178	if (*format == '%') {
179	    ++format;
180	    if (*format == 'E') {
181				/* Alternate Era */
182		++format;
183	    } else if (*format == 'O') {
184				/* Alternate numeric symbols */
185		++format;
186	    }
187	    switch(*format) {
188		case '\0':
189		    --format;
190		    break;
191		case 'A':
192		    if (t->tm_wday < 0 || t->tm_wday > 6)
193			return(0);
194		    if (!_add(_CurrentTimeLocale->day[t->tm_wday]))
195			return(0);
196		    continue;
197		case 'a':
198		    if (t->tm_wday < 0 || t->tm_wday > 6)
199			return(0);
200		    if (!_add(_CurrentTimeLocale->abday[t->tm_wday]))
201			return(0);
202		    continue;
203		case 'B':
204		    if (t->tm_mon < 0 || t->tm_mon > 11)
205			return(0);
206		    if (!_add(_CurrentTimeLocale->mon[t->tm_mon]))
207			return(0);
208		    continue;
209		case 'b':
210		case 'h':
211		    if (t->tm_mon < 0 || t->tm_mon > 11)
212			return(0);
213		    if (!_add(_CurrentTimeLocale->abmon[t->tm_mon]))
214			return(0);
215		    continue;
216		case 'C':
217		    if (!_conv((t->tm_year + TM_YEAR_BASE) / 100,
218			    2, '0'))
219			return(0);
220		    continue;
221		case 'D':
222		    if (!_fmt("%m/%d/%y", t))
223			return(0);
224		    continue;
225		case 'd':
226		    if (!_conv(t->tm_mday, 2, '0'))
227			return(0);
228		    continue;
229		case 'e':
230		    if (!_conv(t->tm_mday, 2, ' '))
231			return(0);
232		    continue;
233	        case 'g':
234		    {
235			int year;
236			ISO8601Week( t, &year );
237			if ( !_conv( year%100, 2, '0' ) ) {
238			    return( 0 );
239			}
240			continue;
241		    }
242	        case 'G':
243		    {
244			int year;
245			ISO8601Week( t, &year );
246			if ( !_conv( year, 4, '0' ) ) {
247			    return( 0 );
248			}
249			continue;
250		    }
251		case 'H':
252		    if (!_conv(t->tm_hour, 2, '0'))
253			return(0);
254		    continue;
255		case 'I':
256		    if (!_conv(t->tm_hour % 12 ?
257			    t->tm_hour % 12 : 12, 2, '0'))
258			return(0);
259		    continue;
260		case 'j':
261		    if (!_conv(t->tm_yday + 1, 3, '0'))
262			return(0);
263		    continue;
264		case 'k':
265		    if (!_conv(t->tm_hour, 2, ' '))
266			return(0);
267		    continue;
268		case 'l':
269		    if (!_conv(t->tm_hour % 12 ?
270			    t->tm_hour % 12: 12, 2, ' '))
271			return(0);
272		    continue;
273		case 'M':
274		    if (!_conv(t->tm_min, 2, '0'))
275			return(0);
276		    continue;
277		case 'm':
278		    if (!_conv(t->tm_mon + 1, 2, '0'))
279			return(0);
280		    continue;
281		case 'n':
282		    if (!_add("\n"))
283			return(0);
284		    continue;
285		case 'p':
286		    if (!_add(_CurrentTimeLocale->am_pm[t->tm_hour >= 12]))
287			return(0);
288		    continue;
289		case 'R':
290		    if (!_fmt("%H:%M", t))
291			return(0);
292		    continue;
293		case 'r':
294		    if (!_fmt(_CurrentTimeLocale->t_fmt_ampm, t))
295			return(0);
296		    continue;
297		case 'S':
298		    if (!_conv(t->tm_sec, 2, '0'))
299			return(0);
300		    continue;
301		case 's':
302		    if (!_secs(t))
303			return(0);
304		    continue;
305		case 'T':
306		    if (!_fmt("%H:%M:%S", t))
307			return(0);
308		    continue;
309		case 't':
310		    if (!_add("\t"))
311			return(0);
312		    continue;
313		case 'U':
314		    if (!_conv(SUN_WEEK(t), 2, '0'))
315			return(0);
316		    continue;
317		case 'u':
318		    if (!_conv(t->tm_wday ? t->tm_wday : 7, 1, '0'))
319			return(0);
320		    continue;
321		case 'V':
322		{
323		    int week = ISO8601Week( t, NULL );
324		    if (!_conv(week, 2, '0'))
325			return(0);
326		    continue;
327		}
328		case 'W':
329		    if (!_conv(MON_WEEK(t), 2, '0'))
330			return(0);
331		    continue;
332		case 'w':
333		    if (!_conv(t->tm_wday, 1, '0'))
334			return(0);
335		    continue;
336#ifdef WIN32
337		/*
338		 * To properly handle the localized time routines on Windows,
339		 * we must make use of the special localized calls.
340		 */
341		case 'c':
342		    if (!GetDateFormat(LOCALE_USER_DEFAULT,
343				       DATE_LONGDATE | LOCALE_USE_CP_ACP,
344				       &syst, NULL, buf, BUF_SIZ)
345			|| !_add(buf)
346			|| !_add(" ")) {
347			return(0);
348		    }
349		    /*
350		     * %c is created with LONGDATE + " " + TIME on Windows,
351		     * so continue to %X case here.
352		     */
353		case 'X':
354		    if (!GetTimeFormat(LOCALE_USER_DEFAULT,
355				       LOCALE_USE_CP_ACP,
356				       &syst, NULL, buf, BUF_SIZ)
357			|| !_add(buf)) {
358			return(0);
359		    }
360		    continue;
361		case 'x':
362		    if (!GetDateFormat(LOCALE_USER_DEFAULT,
363				       DATE_SHORTDATE | LOCALE_USE_CP_ACP,
364				       &syst, NULL, buf, BUF_SIZ)
365			|| !_add(buf)) {
366			return(0);
367		    }
368		    continue;
369#else
370		case 'c':
371		    if (!_fmt(_CurrentTimeLocale->d_t_fmt, t))
372			return(0);
373		    continue;
374		case 'x':
375		    if (!_fmt(_CurrentTimeLocale->d_fmt, t))
376			return(0);
377		    continue;
378		case 'X':
379		    if (!_fmt(_CurrentTimeLocale->t_fmt, t))
380			return(0);
381		    continue;
382#endif
383		case 'y':
384		    if (!_conv((t->tm_year + TM_YEAR_BASE) % 100,
385			    2, '0'))
386			return(0);
387		    continue;
388		case 'Y':
389		    if (!_conv((t->tm_year + TM_YEAR_BASE), 4, '0'))
390			return(0);
391		    continue;
392		case 'Z': {
393		    char *name = (isGMT ? "GMT" : TclpGetTZName(t->tm_isdst));
394		    int wrote;
395		    Tcl_UtfToExternal(NULL, NULL, name, -1, 0, NULL,
396				      pt, gsize, NULL, &wrote, NULL);
397		    pt += wrote;
398		    gsize -= wrote;
399		    continue;
400		}
401		case '%':
402		    /*
403		     * X311J/88-090 (4.12.3.5): if conversion char is
404		     * undefined, behavior is undefined.  Print out the
405		     * character itself as printf(3) does.
406		     */
407		default:
408		    break;
409	    }
410	}
411	if (!gsize--)
412	    return(0);
413	*pt++ = *format;
414    }
415    return(gsize);
416}
417
418static int
419_secs(t)
420    const struct tm *t;
421{
422    static char buf[15];
423    register time_t s;
424    register char *p;
425    struct tm tmp;
426
427    /* Make a copy, mktime(3) modifies the tm struct. */
428    tmp = *t;
429    s = mktime(&tmp);
430    for (p = buf + sizeof(buf) - 2; s > 0 && p > buf; s /= 10)
431	*p-- = (char)(s % 10 + '0');
432    return(_add(++p));
433}
434
435static int
436_conv(n, digits, pad)
437    int n, digits;
438    int pad;
439{
440    static char buf[10];
441    register char *p;
442
443    p = buf + sizeof( buf ) - 1;
444    *p-- = '\0';
445    if ( n == 0 ) {
446	*p-- = '0'; --digits;
447    } else {
448	for (; n > 0 && p > buf; n /= 10, --digits)
449	    *p-- = (char)(n % 10 + '0');
450    }
451    while (p > buf && digits-- > 0)
452	*p-- = (char) pad;
453    return(_add(++p));
454}
455
456static int
457_add(str)
458    const char *str;
459{
460    for (;; ++pt, --gsize) {
461	if (!gsize)
462	    return(0);
463	if (!(*pt = *str++))
464	    return(1);
465    }
466}
467
468static int
469ISO8601Week( t, year )
470    CONST struct tm* t;
471    int* year;
472{
473    /* Find the day-of-year of the Thursday in
474     * the week in question. */
475
476    int ydayThursday;
477    int week;
478    if ( t->tm_wday == 0 ) {
479	ydayThursday = t->tm_yday - 3;
480    } else {
481	ydayThursday = t->tm_yday - t->tm_wday + 4;
482    }
483
484    if ( ydayThursday < 0 ) {
485
486	/* This is the last week of the previous year. */
487	if ( IsLeapYear(( t->tm_year + TM_YEAR_BASE - 1 )) ) {
488	    ydayThursday += 366;
489	} else {
490	    ydayThursday += 365;
491	}
492	week = ydayThursday / 7 + 1;
493	if ( year != NULL ) {
494	    *year = t->tm_year + 1899;
495	}
496
497    } else if ( ( IsLeapYear(( t -> tm_year + TM_YEAR_BASE ))
498		  && ydayThursday >= 366 )
499		|| ( !IsLeapYear(( t -> tm_year
500				   + TM_YEAR_BASE ))
501		     && ydayThursday >= 365 ) ) {
502
503	/* This is week 1 of the following year */
504
505	week = 1;
506	if ( year != NULL ) {
507	    *year = t->tm_year + 1901;
508	}
509
510    } else {
511
512	week = ydayThursday / 7 + 1;
513	if ( year != NULL ) {
514	    *year = t->tm_year + 1900;
515	}
516
517    }
518
519    return week;
520
521}
522