1 /* $NetBSD: strftime.c,v 1.10 2000/01/15 16:59:05 kleink Exp $ */ 2 3 /* 4 * Copyright (c) 1989 The Regents of the University of California. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 #if defined(LIBC_SCCS) && !defined(lint) 38 #if 0 39 static char *sccsid = "@(#)strftime.c 5.11 (Berkeley) 2/24/91"; 40 #else 41 __RCSID("$NetBSD: strftime.c,v 1.10 2000/01/15 16:59:05 kleink Exp $"); 42 #endif 43 #endif /* LIBC_SCCS and not lint */ 44 45 #include "namespace.h" 46 #include <sys/localedef.h> 47 #include <locale.h> 48 #include <string.h> 49 #include <tzfile.h> 50 #include <time.h> 51 52 static int _add __P((const char *, char **, const char *)); 53 static int _conv __P((int, int, int, char **, const char *)); 54 static int _secs __P((const struct tm *, char **, const char *)); 55 static size_t _fmt __P((const char *, const struct tm *, char **, 56 const char *)); 57 58 size_t 59 strftime(s, maxsize, format, t) 60 char *s; 61 size_t maxsize; 62 const char *format; 63 const struct tm *t; 64 { 65 char *pt; 66 67 tzset(); 68 if (maxsize < 1) 69 return (0); 70 71 pt = s; 72 if (_fmt(format, t, &pt, s + maxsize)) { 73 *pt = '\0'; 74 return (pt - s); 75 } else 76 return (0); 77 } 78 79 #define SUN_WEEK(t) (((t)->tm_yday + 7 - \ 80 ((t)->tm_wday)) / 7) 81 #define MON_WEEK(t) (((t)->tm_yday + 7 - \ 82 ((t)->tm_wday ? (t)->tm_wday - 1 : 6)) / 7) 83 84 static size_t 85 _fmt(format, t, pt, ptlim) 86 const char *format; 87 const struct tm *t; 88 char **pt; 89 const char * const ptlim; 90 { 91 for (; *format; ++format) { 92 if (*format == '%') { 93 ++format; 94 if (*format == 'E') { 95 /* Alternate Era */ 96 ++format; 97 } else if (*format == 'O') { 98 /* Alternate numeric symbols */ 99 ++format; 100 } 101 switch (*format) { 102 case '\0': 103 --format; 104 break; 105 case 'A': 106 if (t->tm_wday < 0 || t->tm_wday > 6) 107 return (0); 108 if (!_add(_CurrentTimeLocale->day[t->tm_wday], 109 pt, ptlim)) 110 return (0); 111 continue; 112 113 case 'a': 114 if (t->tm_wday < 0 || t->tm_wday > 6) 115 return (0); 116 if (!_add(_CurrentTimeLocale->abday[t->tm_wday], 117 pt, ptlim)) 118 return (0); 119 continue; 120 case 'B': 121 if (t->tm_mon < 0 || t->tm_mon > 11) 122 return (0); 123 if (!_add(_CurrentTimeLocale->mon[t->tm_mon], 124 pt, ptlim)) 125 return (0); 126 continue; 127 case 'b': 128 case 'h': 129 if (t->tm_mon < 0 || t->tm_mon > 11) 130 return (0); 131 if (!_add(_CurrentTimeLocale->abmon[t->tm_mon], 132 pt, ptlim)) 133 return (0); 134 continue; 135 case 'C': 136 if (!_conv((t->tm_year + TM_YEAR_BASE) / 100, 137 2, '0', pt, ptlim)) 138 return (0); 139 continue; 140 case 'c': 141 if (!_fmt(_CurrentTimeLocale->d_t_fmt, t, pt, 142 ptlim)) 143 return (0); 144 continue; 145 case 'D': 146 if (!_fmt("%m/%d/%y", t, pt, ptlim)) 147 return (0); 148 continue; 149 case 'd': 150 if (!_conv(t->tm_mday, 2, '0', pt, ptlim)) 151 return (0); 152 continue; 153 case 'e': 154 if (!_conv(t->tm_mday, 2, ' ', pt, ptlim)) 155 return (0); 156 continue; 157 case 'F': 158 if (!_fmt("%Y-%m-%d", t, pt, ptlim)) 159 return (0); 160 continue; 161 case 'H': 162 if (!_conv(t->tm_hour, 2, '0', pt, ptlim)) 163 return (0); 164 continue; 165 case 'I': 166 if (!_conv(t->tm_hour % 12 ? 167 t->tm_hour % 12 : 12, 2, '0', pt, ptlim)) 168 return (0); 169 continue; 170 case 'j': 171 if (!_conv(t->tm_yday + 1, 3, '0', pt, ptlim)) 172 return (0); 173 continue; 174 case 'k': 175 if (!_conv(t->tm_hour, 2, ' ', pt, ptlim)) 176 return (0); 177 continue; 178 case 'l': 179 if (!_conv(t->tm_hour % 12 ? 180 t->tm_hour % 12: 12, 2, ' ', pt, ptlim)) 181 return (0); 182 continue; 183 case 'M': 184 if (!_conv(t->tm_min, 2, '0', pt, ptlim)) 185 return (0); 186 continue; 187 case 'm': 188 if (!_conv(t->tm_mon + 1, 2, '0', pt, ptlim)) 189 return (0); 190 continue; 191 case 'n': 192 if (!_add("\n", pt, ptlim)) 193 return (0); 194 continue; 195 case 'p': 196 if (!_add(_CurrentTimeLocale->am_pm[t->tm_hour 197 >= 12], pt, ptlim)) 198 return (0); 199 continue; 200 case 'R': 201 if (!_fmt("%H:%M", t, pt, ptlim)) 202 return (0); 203 continue; 204 case 'r': 205 if (!_fmt(_CurrentTimeLocale->t_fmt_ampm, t, pt, 206 ptlim)) 207 return (0); 208 continue; 209 case 'S': 210 if (!_conv(t->tm_sec, 2, '0', pt, ptlim)) 211 return (0); 212 continue; 213 case 's': 214 if (!_secs(t, pt, ptlim)) 215 return (0); 216 continue; 217 case 'T': 218 if (!_fmt("%H:%M:%S", t, pt, ptlim)) 219 return (0); 220 continue; 221 case 't': 222 if (!_add("\t", pt, ptlim)) 223 return (0); 224 continue; 225 case 'U': 226 if (!_conv(SUN_WEEK(t), 2, '0', pt, ptlim)) 227 return (0); 228 continue; 229 case 'u': 230 if (!_conv(t->tm_wday ? t->tm_wday : 7, 1, '0', 231 pt, ptlim)) 232 return (0); 233 continue; 234 case 'V': /* ISO 8601 week number */ 235 case 'G': /* ISO 8601 year (four digits) */ 236 case 'g': /* ISO 8601 year (two digits) */ 237 /* 238 ** From Arnold Robbins' strftime version 3.0: "the week number of the 239 ** year (the first Monday as the first day of week 1) as a decimal number 240 ** (01-53)." 241 ** (ado, 1993-05-24) 242 ** 243 ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: 244 ** "Week 01 of a year is per definition the first week which has the 245 ** Thursday in this year, which is equivalent to the week which contains 246 ** the fourth day of January. In other words, the first week of a new year 247 ** is the week which has the majority of its days in the new year. Week 01 248 ** might also contain days from the previous year and the week before week 249 ** 01 of a year is the last week (52 or 53) of the previous year even if 250 ** it contains days from the new year. A week starts with Monday (day 1) 251 ** and ends with Sunday (day 7). For example, the first week of the year 252 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 253 ** (ado, 1996-01-02) 254 */ 255 { 256 int year; 257 int yday; 258 int wday; 259 int w; 260 261 year = t->tm_year + TM_YEAR_BASE; 262 yday = t->tm_yday; 263 wday = t->tm_wday; 264 for ( ; ; ) { 265 int len; 266 int bot; 267 int top; 268 269 len = isleap(year) ? 270 DAYSPERLYEAR : 271 DAYSPERNYEAR; 272 /* 273 ** What yday (-3 ... 3) does 274 ** the ISO year begin on? 275 */ 276 bot = ((yday + 11 - wday) % 277 DAYSPERWEEK) - 3; 278 /* 279 ** What yday does the NEXT 280 ** ISO year begin on? 281 */ 282 top = bot - 283 (len % DAYSPERWEEK); 284 if (top < -3) 285 top += DAYSPERWEEK; 286 top += len; 287 if (yday >= top) { 288 ++year; 289 w = 1; 290 break; 291 } 292 if (yday >= bot) { 293 w = 1 + ((yday - bot) / 294 DAYSPERWEEK); 295 break; 296 } 297 --year; 298 yday += isleap(year) ? 299 DAYSPERLYEAR : 300 DAYSPERNYEAR; 301 } 302 #ifdef XPG4_1994_04_09 303 if ((w == 52 304 && t->tm_mon == TM_JANUARY) 305 || (w == 1 306 && t->tm_mon == TM_DECEMBER)) 307 w = 53; 308 #endif /* defined XPG4_1994_04_09 */ 309 if (*format == 'V') { 310 if (!_conv(w, 2, '0', 311 pt, ptlim)) 312 return (0); 313 } else if (*format == 'g') { 314 if (!_conv(year % 100, 2, '0', 315 pt, ptlim)) 316 return (0); 317 } else if (!_conv(year, 4, '0', 318 pt, ptlim)) 319 return (0); 320 } 321 continue; 322 case 'W': 323 if (!_conv(MON_WEEK(t), 2, '0', pt, ptlim)) 324 return (0); 325 continue; 326 case 'w': 327 if (!_conv(t->tm_wday, 1, '0', pt, ptlim)) 328 return (0); 329 continue; 330 case 'x': 331 if (!_fmt(_CurrentTimeLocale->d_fmt, t, pt, 332 ptlim)) 333 return (0); 334 continue; 335 case 'X': 336 if (!_fmt(_CurrentTimeLocale->t_fmt, t, pt, 337 ptlim)) 338 return (0); 339 continue; 340 case 'y': 341 if (!_conv((t->tm_year + TM_YEAR_BASE) % 100, 342 2, '0', pt, ptlim)) 343 return (0); 344 continue; 345 case 'Y': 346 if (!_conv((t->tm_year + TM_YEAR_BASE), 4, '0', 347 pt, ptlim)) 348 return (0); 349 continue; 350 case 'Z': 351 if (tzname[t->tm_isdst ? 1 : 0] && 352 !_add(tzname[t->tm_isdst ? 1 : 0], pt, 353 ptlim)) 354 return (0); 355 continue; 356 case '%': 357 /* 358 * X311J/88-090 (4.12.3.5): if conversion char is 359 * undefined, behavior is undefined. Print out the 360 * character itself as printf(3) does. 361 */ 362 default: 363 break; 364 } 365 } 366 if (*pt == ptlim) 367 return (0); 368 *(*pt)++ = *format; 369 } 370 return (ptlim - *pt); 371 } 372 373 static int 374 _secs(t, pt, ptlim) 375 const struct tm *t; 376 char **pt; 377 const char * const ptlim; 378 { 379 char buf[15]; 380 time_t s; 381 char *p; 382 struct tm tmp; 383 384 buf[sizeof (buf) - 1] = '\0'; 385 /* Make a copy, mktime(3) modifies the tm struct. */ 386 tmp = *t; 387 s = mktime(&tmp); 388 for (p = buf + sizeof(buf) - 2; s > 0 && p > buf; s /= 10) 389 *p-- = (char)(s % 10 + '0'); 390 return (_add(++p, pt, ptlim)); 391 } 392 393 static int 394 _conv(n, digits, pad, pt, ptlim) 395 int n, digits; 396 int pad; 397 char **pt; 398 const char * const ptlim; 399 { 400 char buf[10]; 401 char *p; 402 403 buf[sizeof (buf) - 1] = '\0'; 404 p = buf + sizeof(buf) - 2; 405 do { 406 *p-- = n % 10 + '0'; 407 n /= 10; 408 --digits; 409 } while (n > 0 && p > buf); 410 while (p > buf && digits-- > 0) 411 *p-- = pad; 412 return (_add(++p, pt, ptlim)); 413 } 414 415 static int 416 _add(str, pt, ptlim) 417 const char *str; 418 char **pt; 419 const char * const ptlim; 420 { 421 422 for (;; ++(*pt)) { 423 if (*pt == ptlim) 424 return (0); 425 if ((**pt = *str++) == '\0') 426 return (1); 427 } 428 } 429