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