1 /* 2 * Copyright (c) 1989 The Regents of the University of California. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms are permitted 6 * provided that the above copyright notice and this paragraph are 7 * duplicated in all such forms and that any documentation, 8 * advertising materials, and other materials related to such 9 * distribution and use acknowledge that the software was developed 10 * by the University of California, Berkeley. The name of the 11 * University may not be used to endorse or promote products derived 12 * from this software without specific prior written permission. 13 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 14 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 16 */ 17 18 #ifndef lint 19 #ifndef NOID 20 static const char elsieid[] = "@(#)strftime.c 7.38"; 21 /* 22 ** Based on the UCB version with the ID appearing below. 23 ** This is ANSIish only when "multibyte character == plain character". 24 */ 25 #endif /* !defined NOID */ 26 #endif /* !defined lint */ 27 28 #include "namespace.h" 29 #include "private.h" 30 31 #if defined(LIBC_SCCS) && !defined(lint) 32 static const char sccsid[] = "@(#)strftime.c 5.4 (Berkeley) 3/14/89"; 33 #endif /* LIBC_SCCS and not lint */ 34 #include <sys/cdefs.h> 35 __FBSDID("$FreeBSD$"); 36 37 #include "tzfile.h" 38 #include <fcntl.h> 39 #include <sys/stat.h> 40 #include "un-namespace.h" 41 #include "timelocal.h" 42 43 static char * _add(const char *, char *, const char *); 44 static char * _conv(int, const char *, char *, const char *); 45 static char * _fmt(const char *, const struct tm *, char *, const char *); 46 47 size_t strftime(char * __restrict, size_t, const char * __restrict, 48 const struct tm * __restrict); 49 50 extern char * tzname[]; 51 52 size_t 53 strftime(char * __restrict s, size_t maxsize, const char * __restrict format, 54 const struct tm * __restrict t) 55 { 56 char *p; 57 58 tzset(); 59 p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize); 60 if (p == s + maxsize) 61 return 0; 62 *p = '\0'; 63 return p - s; 64 } 65 66 static char * 67 _fmt(format, t, pt, ptlim) 68 const char *format; 69 const struct tm *const t; 70 char *pt; 71 const char *const ptlim; 72 { 73 int Ealternative, Oalternative; 74 struct lc_time_T *tptr = __get_current_time_locale(); 75 76 for ( ; *format; ++format) { 77 if (*format == '%') { 78 Ealternative = 0; 79 Oalternative = 0; 80 label: 81 switch (*++format) { 82 case '\0': 83 --format; 84 break; 85 case 'A': 86 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ? 87 "?" : tptr->weekday[t->tm_wday], 88 pt, ptlim); 89 continue; 90 case 'a': 91 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ? 92 "?" : tptr->wday[t->tm_wday], 93 pt, ptlim); 94 continue; 95 case 'B': 96 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? 97 "?" : (Oalternative ? tptr->alt_month : 98 tptr->month)[t->tm_mon], 99 pt, ptlim); 100 continue; 101 case 'b': 102 case 'h': 103 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? 104 "?" : tptr->mon[t->tm_mon], 105 pt, ptlim); 106 continue; 107 case 'C': 108 /* 109 ** %C used to do a... 110 ** _fmt("%a %b %e %X %Y", t); 111 ** ...whereas now POSIX 1003.2 calls for 112 ** something completely different. 113 ** (ado, 5/24/93) 114 */ 115 pt = _conv((t->tm_year + TM_YEAR_BASE) / 100, 116 "%02d", pt, ptlim); 117 continue; 118 case 'c': 119 pt = _fmt(tptr->c_fmt, t, pt, ptlim); 120 continue; 121 case 'D': 122 pt = _fmt("%m/%d/%y", t, pt, ptlim); 123 continue; 124 case 'd': 125 pt = _conv(t->tm_mday, "%02d", pt, ptlim); 126 continue; 127 case 'E': 128 if (Ealternative || Oalternative) 129 break; 130 Ealternative++; 131 goto label; 132 case 'O': 133 /* 134 ** POSIX locale extensions, a la 135 ** Arnold Robbins' strftime version 3.0. 136 ** The sequences 137 ** %Ec %EC %Ex %EX %Ey %EY 138 ** %Od %oe %OH %OI %Om %OM 139 ** %OS %Ou %OU %OV %Ow %OW %Oy 140 ** are supposed to provide alternate 141 ** representations. 142 ** (ado, 5/24/93) 143 ** 144 ** FreeBSD extensions 145 ** %OB %Ef %EF 146 */ 147 if (Ealternative || Oalternative) 148 break; 149 Oalternative++; 150 goto label; 151 case 'e': 152 pt = _conv(t->tm_mday, "%2d", pt, ptlim); 153 continue; 154 case 'F': 155 pt = _fmt("%Y-%m-%d", t, pt, ptlim); 156 continue; 157 case 'H': 158 pt = _conv(t->tm_hour, "%02d", pt, ptlim); 159 continue; 160 case 'I': 161 pt = _conv((t->tm_hour % 12) ? 162 (t->tm_hour % 12) : 12, 163 "%02d", pt, ptlim); 164 continue; 165 case 'j': 166 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); 167 continue; 168 case 'k': 169 /* 170 ** This used to be... 171 ** _conv(t->tm_hour % 12 ? 172 ** t->tm_hour % 12 : 12, 2, ' '); 173 ** ...and has been changed to the below to 174 ** match SunOS 4.1.1 and Arnold Robbins' 175 ** strftime version 3.0. That is, "%k" and 176 ** "%l" have been swapped. 177 ** (ado, 5/24/93) 178 */ 179 pt = _conv(t->tm_hour, "%2d", pt, ptlim); 180 continue; 181 #ifdef KITCHEN_SINK 182 case 'K': 183 /* 184 ** After all this time, still unclaimed! 185 */ 186 pt = _add("kitchen sink", pt, ptlim); 187 continue; 188 #endif /* defined KITCHEN_SINK */ 189 case 'l': 190 /* 191 ** This used to be... 192 ** _conv(t->tm_hour, 2, ' '); 193 ** ...and has been changed to the below to 194 ** match SunOS 4.1.1 and Arnold Robbin's 195 ** strftime version 3.0. That is, "%k" and 196 ** "%l" have been swapped. 197 ** (ado, 5/24/93) 198 */ 199 pt = _conv((t->tm_hour % 12) ? 200 (t->tm_hour % 12) : 12, 201 "%2d", pt, ptlim); 202 continue; 203 case 'M': 204 pt = _conv(t->tm_min, "%02d", pt, ptlim); 205 continue; 206 case 'm': 207 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); 208 continue; 209 case 'n': 210 pt = _add("\n", pt, ptlim); 211 continue; 212 case 'p': 213 pt = _add((t->tm_hour >= 12) ? 214 tptr->pm : 215 tptr->am, 216 pt, ptlim); 217 continue; 218 case 'R': 219 pt = _fmt("%H:%M", t, pt, ptlim); 220 continue; 221 case 'r': 222 pt = _fmt(tptr->ampm_fmt, t, pt, ptlim); 223 continue; 224 case 'S': 225 pt = _conv(t->tm_sec, "%02d", pt, ptlim); 226 continue; 227 case 's': 228 { 229 struct tm tm; 230 char buf[INT_STRLEN_MAXIMUM( 231 time_t) + 1]; 232 time_t mkt; 233 234 tm = *t; 235 mkt = mktime(&tm); 236 if (TYPE_SIGNED(time_t)) 237 (void) sprintf(buf, "%ld", 238 (long) mkt); 239 else (void) sprintf(buf, "%lu", 240 (unsigned long) mkt); 241 pt = _add(buf, pt, ptlim); 242 } 243 continue; 244 case 'T': 245 pt = _fmt("%H:%M:%S", t, pt, ptlim); 246 continue; 247 case 't': 248 pt = _add("\t", pt, ptlim); 249 continue; 250 case 'U': 251 pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7, 252 "%02d", pt, ptlim); 253 continue; 254 case 'u': 255 /* 256 ** From Arnold Robbins' strftime version 3.0: 257 ** "ISO 8601: Weekday as a decimal number 258 ** [1 (Monday) - 7]" 259 ** (ado, 5/24/93) 260 */ 261 pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday, 262 "%d", pt, ptlim); 263 continue; 264 case 'V': /* ISO 8601 week number */ 265 case 'G': /* ISO 8601 year (four digits) */ 266 case 'g': /* ISO 8601 year (two digits) */ 267 /* 268 ** From Arnold Robbins' strftime version 3.0: "the week number of the 269 ** year (the first Monday as the first day of week 1) as a decimal number 270 ** (01-53)." 271 ** (ado, 1993-05-24) 272 ** 273 ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: 274 ** "Week 01 of a year is per definition the first week which has the 275 ** Thursday in this year, which is equivalent to the week which contains 276 ** the fourth day of January. In other words, the first week of a new year 277 ** is the week which has the majority of its days in the new year. Week 01 278 ** might also contain days from the previous year and the week before week 279 ** 01 of a year is the last week (52 or 53) of the previous year even if 280 ** it contains days from the new year. A week starts with Monday (day 1) 281 ** and ends with Sunday (day 7). For example, the first week of the year 282 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 283 ** (ado, 1996-01-02) 284 */ 285 { 286 int year; 287 int yday; 288 int wday; 289 int w; 290 291 year = t->tm_year + TM_YEAR_BASE; 292 yday = t->tm_yday; 293 wday = t->tm_wday; 294 for ( ; ; ) { 295 int len; 296 int bot; 297 int top; 298 299 len = isleap(year) ? 300 DAYSPERLYEAR : 301 DAYSPERNYEAR; 302 /* 303 ** What yday (-3 ... 3) does 304 ** the ISO year begin on? 305 */ 306 bot = ((yday + 11 - wday) % 307 DAYSPERWEEK) - 3; 308 /* 309 ** What yday does the NEXT 310 ** ISO year begin on? 311 */ 312 top = bot - 313 (len % DAYSPERWEEK); 314 if (top < -3) 315 top += DAYSPERWEEK; 316 top += len; 317 if (yday >= top) { 318 ++year; 319 w = 1; 320 break; 321 } 322 if (yday >= bot) { 323 w = 1 + ((yday - bot) / 324 DAYSPERWEEK); 325 break; 326 } 327 --year; 328 yday += isleap(year) ? 329 DAYSPERLYEAR : 330 DAYSPERNYEAR; 331 } 332 #ifdef XPG4_1994_04_09 333 if ((w == 52 334 && t->tm_mon == TM_JANUARY) 335 || (w == 1 336 && t->tm_mon == TM_DECEMBER)) 337 w = 53; 338 #endif /* defined XPG4_1994_04_09 */ 339 if (*format == 'V') 340 pt = _conv(w, "%02d", 341 pt, ptlim); 342 else if (*format == 'g') { 343 pt = _conv(year % 100, "%02d", 344 pt, ptlim); 345 } else pt = _conv(year, "%04d", 346 pt, ptlim); 347 } 348 continue; 349 case 'v': 350 /* 351 ** From Arnold Robbins' strftime version 3.0: 352 ** "date as dd-bbb-YYYY" 353 ** (ado, 5/24/93) 354 */ 355 pt = _fmt("%e-%b-%Y", t, pt, ptlim); 356 continue; 357 case 'W': 358 pt = _conv((t->tm_yday + 7 - 359 (t->tm_wday ? 360 (t->tm_wday - 1) : 6)) / 7, 361 "%02d", pt, ptlim); 362 continue; 363 case 'w': 364 pt = _conv(t->tm_wday, "%d", pt, ptlim); 365 continue; 366 case 'X': 367 pt = _fmt(tptr->X_fmt, t, pt, ptlim); 368 continue; 369 case 'x': 370 pt = _fmt(tptr->x_fmt, t, pt, ptlim); 371 continue; 372 case 'y': 373 pt = _conv((t->tm_year + TM_YEAR_BASE) % 100, 374 "%02d", pt, ptlim); 375 continue; 376 case 'Y': 377 pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d", 378 pt, ptlim); 379 continue; 380 case 'Z': 381 if (t->tm_zone != NULL) 382 pt = _add(t->tm_zone, pt, ptlim); 383 else 384 if (t->tm_isdst == 0 || t->tm_isdst == 1) { 385 pt = _add(tzname[t->tm_isdst], 386 pt, ptlim); 387 } else pt = _add("?", pt, ptlim); 388 continue; 389 case 'z': 390 { 391 long absoff; 392 if (t->tm_gmtoff >= 0) { 393 absoff = t->tm_gmtoff; 394 pt = _add("+", pt, ptlim); 395 } else { 396 absoff = -t->tm_gmtoff; 397 pt = _add("-", pt, ptlim); 398 } 399 pt = _conv(absoff / 3600, "%02d", 400 pt, ptlim); 401 pt = _conv((absoff % 3600) / 60, "%02d", 402 pt, ptlim); 403 }; 404 continue; 405 case '+': 406 pt = _fmt(tptr->date_fmt, t, pt, ptlim); 407 continue; 408 case '%': 409 /* 410 * X311J/88-090 (4.12.3.5): if conversion char is 411 * undefined, behavior is undefined. Print out the 412 * character itself as printf(3) also does. 413 */ 414 default: 415 break; 416 } 417 } 418 if (pt == ptlim) 419 break; 420 *pt++ = *format; 421 } 422 return pt; 423 } 424 425 static char * 426 _conv(n, format, pt, ptlim) 427 const int n; 428 const char *const format; 429 char *const pt; 430 const char *const ptlim; 431 { 432 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 433 434 (void) sprintf(buf, format, n); 435 return _add(buf, pt, ptlim); 436 } 437 438 static char * 439 _add(str, pt, ptlim) 440 const char *str; 441 char *pt; 442 const char *const ptlim; 443 { 444 while (pt < ptlim && (*pt = *str++) != '\0') 445 ++pt; 446 return pt; 447 } 448