1 /* $OpenBSD: wcsftime.c,v 1.7 2019/05/12 12:49:52 schwarze Exp $ */ 2 /* 3 ** Based on the UCB version with the ID appearing below. 4 ** This is ANSIish only when "multibyte character == plain character". 5 ** 6 ** Copyright (c) 1989, 1993 7 ** The Regents of the University of California. All rights reserved. 8 ** 9 ** Redistribution and use in source and binary forms, with or without 10 ** modification, are permitted provided that the following conditions 11 ** are met: 12 ** 1. Redistributions of source code must retain the above copyright 13 ** notice, this list of conditions and the following disclaimer. 14 ** 2. Redistributions in binary form must reproduce the above copyright 15 ** notice, this list of conditions and the following disclaimer in the 16 ** documentation and/or other materials provided with the distribution. 17 ** 3. Neither the name of the University nor the names of its contributors 18 ** may be used to endorse or promote products derived from this software 19 ** without specific prior written permission. 20 ** 21 ** THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 22 ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 ** ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 25 ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 ** SUCH DAMAGE. 32 */ 33 34 #include <fcntl.h> 35 #include <locale.h> 36 #include <wchar.h> 37 38 #include "private.h" 39 #include "tzfile.h" 40 41 struct lc_time_T { 42 const wchar_t * mon[MONSPERYEAR]; 43 const wchar_t * month[MONSPERYEAR]; 44 const wchar_t * wday[DAYSPERWEEK]; 45 const wchar_t * weekday[DAYSPERWEEK]; 46 const wchar_t * X_fmt; 47 const wchar_t * x_fmt; 48 const wchar_t * c_fmt; 49 const wchar_t * am; 50 const wchar_t * pm; 51 const wchar_t * date_fmt; 52 }; 53 54 #define Locale (&C_time_locale) 55 56 static const struct lc_time_T C_time_locale = { 57 { 58 L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", 59 L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" 60 }, { 61 L"January", L"February", L"March", L"April", L"May", L"June", 62 L"July", L"August", L"September", L"October", L"November", 63 L"December" 64 }, { 65 L"Sun", L"Mon", L"Tue", L"Wed", 66 L"Thu", L"Fri", L"Sat" 67 }, { 68 L"Sunday", L"Monday", L"Tuesday", L"Wednesday", 69 L"Thursday", L"Friday", L"Saturday" 70 }, 71 72 /* X_fmt */ 73 L"%H:%M:%S", 74 75 /* 76 ** x_fmt 77 ** C99 requires this format. 78 ** Using just numbers (as here) makes Quakers happier; 79 ** it's also compatible with SVR4. 80 */ 81 L"%m/%d/%y", 82 83 /* 84 ** c_fmt 85 ** C99 requires this format. 86 ** Previously this code used "%D %X", but we now conform to C99. 87 ** Note that 88 ** "%a %b %d %H:%M:%S %Y" 89 ** is used by Solaris 2.3. 90 */ 91 L"%a %b %e %T %Y", 92 93 /* am */ 94 L"AM", 95 96 /* pm */ 97 L"PM", 98 99 /* date_fmt */ 100 L"%a %b %e %H:%M:%S %Z %Y" 101 }; 102 103 #define UNKNOWN L"?" 104 static wchar_t * _add(const wchar_t *, wchar_t *, const wchar_t *); 105 static wchar_t * _sadd(const char *, wchar_t *, const wchar_t *); 106 static wchar_t * _conv(int, const wchar_t *, wchar_t *, const wchar_t *); 107 static wchar_t * _fmt(const wchar_t *, const struct tm *, wchar_t *, const wchar_t *, 108 int *); 109 static wchar_t * _yconv(int, int, int, int, wchar_t *, const wchar_t *); 110 111 extern char * tzname[]; 112 113 #define IN_NONE 0 114 #define IN_SOME 1 115 #define IN_THIS 2 116 #define IN_ALL 3 117 118 size_t 119 wcsftime(wchar_t *__restrict s, size_t maxsize, 120 const wchar_t *__restrict format, const struct tm *__restrict t) 121 { 122 wchar_t *p; 123 int warn; 124 125 tzset(); 126 warn = IN_NONE; 127 p = _fmt(((format == NULL) ? L"%c" : format), t, s, s + maxsize, &warn); 128 if (p == s + maxsize) { 129 if (maxsize > 0) 130 s[maxsize - 1] = '\0'; 131 return 0; 132 } 133 *p = L'\0'; 134 return p - s; 135 } 136 137 static wchar_t * 138 _fmt(const wchar_t *format, const struct tm *t, wchar_t *pt, 139 const wchar_t *ptlim, int *warnp) 140 { 141 for ( ; *format; ++format) { 142 if (*format != L'%') { 143 if (pt == ptlim) 144 break; 145 *pt++ = *format; 146 continue; 147 } 148 label: 149 switch (*++format) { 150 case '\0': 151 --format; 152 break; 153 case 'A': 154 pt = _add((t->tm_wday < 0 || 155 t->tm_wday >= DAYSPERWEEK) ? 156 UNKNOWN : Locale->weekday[t->tm_wday], 157 pt, ptlim); 158 continue; 159 case 'a': 160 pt = _add((t->tm_wday < 0 || 161 t->tm_wday >= DAYSPERWEEK) ? 162 UNKNOWN : Locale->wday[t->tm_wday], 163 pt, ptlim); 164 continue; 165 case 'B': 166 pt = _add((t->tm_mon < 0 || 167 t->tm_mon >= MONSPERYEAR) ? 168 UNKNOWN : Locale->month[t->tm_mon], 169 pt, ptlim); 170 continue; 171 case 'b': 172 case 'h': 173 pt = _add((t->tm_mon < 0 || 174 t->tm_mon >= MONSPERYEAR) ? 175 UNKNOWN : Locale->mon[t->tm_mon], 176 pt, ptlim); 177 continue; 178 case 'C': 179 /* 180 ** %C used to do a... 181 ** _fmt("%a %b %e %X %Y", t); 182 ** ...whereas now POSIX 1003.2 calls for 183 ** something completely different. 184 ** (ado, 1993-05-24) 185 */ 186 pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0, 187 pt, ptlim); 188 continue; 189 case 'c': 190 { 191 int warn2 = IN_SOME; 192 193 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); 194 if (warn2 == IN_ALL) 195 warn2 = IN_THIS; 196 if (warn2 > *warnp) 197 *warnp = warn2; 198 } 199 continue; 200 case 'D': 201 pt = _fmt(L"%m/%d/%y", t, pt, ptlim, warnp); 202 continue; 203 case 'd': 204 pt = _conv(t->tm_mday, L"%02d", pt, ptlim); 205 continue; 206 case 'E': 207 case 'O': 208 /* 209 ** C99 locale modifiers. 210 ** The sequences 211 ** %Ec %EC %Ex %EX %Ey %EY 212 ** %Od %oe %OH %OI %Om %OM 213 ** %OS %Ou %OU %OV %Ow %OW %Oy 214 ** are supposed to provide alternate 215 ** representations. 216 */ 217 goto label; 218 case 'e': 219 pt = _conv(t->tm_mday, L"%2d", pt, ptlim); 220 continue; 221 case 'F': 222 pt = _fmt(L"%Y-%m-%d", t, pt, ptlim, warnp); 223 continue; 224 case 'H': 225 pt = _conv(t->tm_hour, L"%02d", pt, ptlim); 226 continue; 227 case 'I': 228 pt = _conv((t->tm_hour % 12) ? 229 (t->tm_hour % 12) : 12, 230 L"%02d", pt, ptlim); 231 continue; 232 case 'j': 233 pt = _conv(t->tm_yday + 1, L"%03d", pt, ptlim); 234 continue; 235 case 'k': 236 /* 237 ** This used to be... 238 ** _conv(t->tm_hour % 12 ? 239 ** t->tm_hour % 12 : 12, 2, ' '); 240 ** ...and has been changed to the below to 241 ** match SunOS 4.1.1 and Arnold Robbins' 242 ** strftime version 3.0. That is, "%k" and 243 ** "%l" have been swapped. 244 ** (ado, 1993-05-24) 245 */ 246 pt = _conv(t->tm_hour, L"%2d", pt, ptlim); 247 continue; 248 case 'l': 249 /* 250 ** This used to be... 251 ** _conv(t->tm_hour, 2, ' '); 252 ** ...and has been changed to the below to 253 ** match SunOS 4.1.1 and Arnold Robbin's 254 ** strftime version 3.0. That is, "%k" and 255 ** "%l" have been swapped. 256 ** (ado, 1993-05-24) 257 */ 258 pt = _conv((t->tm_hour % 12) ? 259 (t->tm_hour % 12) : 12, 260 L"%2d", pt, ptlim); 261 continue; 262 case 'M': 263 pt = _conv(t->tm_min, L"%02d", pt, ptlim); 264 continue; 265 case 'm': 266 pt = _conv(t->tm_mon + 1, L"%02d", pt, ptlim); 267 continue; 268 case 'n': 269 pt = _add(L"\n", pt, ptlim); 270 continue; 271 case 'p': 272 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? 273 Locale->pm : 274 Locale->am, 275 pt, ptlim); 276 continue; 277 case 'R': 278 pt = _fmt(L"%H:%M", t, pt, ptlim, warnp); 279 continue; 280 case 'r': 281 pt = _fmt(L"%I:%M:%S %p", t, pt, ptlim, warnp); 282 continue; 283 case 'S': 284 pt = _conv(t->tm_sec, L"%02d", pt, ptlim); 285 continue; 286 case 's': 287 { 288 struct tm tm; 289 wchar_t buf[INT_STRLEN_MAXIMUM( 290 time_t) + 1]; 291 time_t mkt; 292 293 tm = *t; 294 mkt = mktime(&tm); 295 (void) swprintf(buf, 296 sizeof buf/sizeof buf[0], 297 L"%ld", (long) mkt); 298 pt = _add(buf, pt, ptlim); 299 } 300 continue; 301 case 'T': 302 pt = _fmt(L"%H:%M:%S", t, pt, ptlim, warnp); 303 continue; 304 case 't': 305 pt = _add(L"\t", pt, ptlim); 306 continue; 307 case 'U': 308 pt = _conv((t->tm_yday + DAYSPERWEEK - 309 t->tm_wday) / DAYSPERWEEK, 310 L"%02d", pt, ptlim); 311 continue; 312 case 'u': 313 /* 314 ** From Arnold Robbins' strftime version 3.0: 315 ** "ISO 8601: Weekday as a decimal number 316 ** [1 (Monday) - 7]" 317 ** (ado, 1993-05-24) 318 */ 319 pt = _conv((t->tm_wday == 0) ? 320 DAYSPERWEEK : t->tm_wday, 321 L"%d", pt, ptlim); 322 continue; 323 case 'V': /* ISO 8601 week number */ 324 case 'G': /* ISO 8601 year (four digits) */ 325 case 'g': /* ISO 8601 year (two digits) */ 326 /* 327 ** From Arnold Robbins' strftime version 3.0: "the week number of the 328 ** year (the first Monday as the first day of week 1) as a decimal number 329 ** (01-53)." 330 ** (ado, 1993-05-24) 331 ** 332 ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: 333 ** "Week 01 of a year is per definition the first week which has the 334 ** Thursday in this year, which is equivalent to the week which contains 335 ** the fourth day of January. In other words, the first week of a new year 336 ** is the week which has the majority of its days in the new year. Week 01 337 ** might also contain days from the previous year and the week before week 338 ** 01 of a year is the last week (52 or 53) of the previous year even if 339 ** it contains days from the new year. A week starts with Monday (day 1) 340 ** and ends with Sunday (day 7). For example, the first week of the year 341 ** 1997 lasts from 1996-12-30 to 1997-01-05..." 342 ** (ado, 1996-01-02) 343 */ 344 { 345 int year; 346 int base; 347 int yday; 348 int wday; 349 int w; 350 351 year = t->tm_year; 352 base = TM_YEAR_BASE; 353 yday = t->tm_yday; 354 wday = t->tm_wday; 355 for ( ; ; ) { 356 int len; 357 int bot; 358 int top; 359 360 len = isleap_sum(year, base) ? 361 DAYSPERLYEAR : 362 DAYSPERNYEAR; 363 /* 364 ** What yday (-3 ... 3) does the ISO year 365 ** begin on? 366 */ 367 bot = ((yday + 11 - wday) % DAYSPERWEEK) - 3; 368 /* 369 ** What yday does the NEXT ISO year begin on? 370 */ 371 top = bot - (len % DAYSPERWEEK); 372 if (top < -3) 373 top += DAYSPERWEEK; 374 top += len; 375 if (yday >= top) { 376 ++base; 377 w = 1; 378 break; 379 } 380 if (yday >= bot) { 381 w = 1 + ((yday - bot) / DAYSPERWEEK); 382 break; 383 } 384 --base; 385 yday += isleap_sum(year, base) ? 386 DAYSPERLYEAR : 387 DAYSPERNYEAR; 388 } 389 if ((w == 52 && t->tm_mon == TM_JANUARY) || 390 (w == 1 && t->tm_mon == TM_DECEMBER)) 391 w = 53; 392 if (*format == 'V') 393 pt = _conv(w, L"%02d", pt, ptlim); 394 else if (*format == 'g') { 395 *warnp = IN_ALL; 396 pt = _yconv(year, base, 0, 1, pt, ptlim); 397 } else 398 pt = _yconv(year, base, 1, 1, pt, ptlim); 399 } 400 continue; 401 case 'v': 402 /* 403 ** From Arnold Robbins' strftime version 3.0: 404 ** "date as dd-bbb-YYYY" 405 ** (ado, 1993-05-24) 406 */ 407 pt = _fmt(L"%e-%b-%Y", t, pt, ptlim, warnp); 408 continue; 409 case 'W': 410 pt = _conv((t->tm_yday + DAYSPERWEEK - 411 (t->tm_wday ? 412 (t->tm_wday - 1) : 413 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 414 L"%02d", pt, ptlim); 415 continue; 416 case 'w': 417 pt = _conv(t->tm_wday, L"%d", pt, ptlim); 418 continue; 419 case 'X': 420 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); 421 continue; 422 case 'x': 423 { 424 int warn2 = IN_SOME; 425 426 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); 427 if (warn2 == IN_ALL) 428 warn2 = IN_THIS; 429 if (warn2 > *warnp) 430 *warnp = warn2; 431 } 432 continue; 433 case 'y': 434 *warnp = IN_ALL; 435 pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1, pt, ptlim); 436 continue; 437 case 'Y': 438 pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1, pt, ptlim); 439 continue; 440 case 'Z': 441 if (t->tm_zone != NULL) 442 pt = _sadd(t->tm_zone, pt, ptlim); 443 else 444 if (t->tm_isdst >= 0) 445 pt = _sadd(tzname[t->tm_isdst != 0], 446 pt, ptlim); 447 /* 448 ** C99 says that %Z must be replaced by the 449 ** empty string if the time zone is not 450 ** determinable. 451 */ 452 continue; 453 case 'z': 454 { 455 int diff; 456 wchar_t const * sign; 457 458 if (t->tm_isdst < 0) 459 continue; 460 diff = t->tm_gmtoff; 461 if (diff < 0) { 462 sign = L"-"; 463 diff = -diff; 464 } else 465 sign = L"+"; 466 pt = _add(sign, pt, ptlim); 467 diff /= SECSPERMIN; 468 diff = (diff / MINSPERHOUR) * 100 + 469 (diff % MINSPERHOUR); 470 pt = _conv(diff, L"%04d", pt, ptlim); 471 } 472 continue; 473 case '+': 474 pt = _fmt(Locale->date_fmt, t, pt, ptlim, warnp); 475 continue; 476 case '%': 477 /* 478 ** X311J/88-090 (4.12.3.5): if conversion wchar_t is 479 ** undefined, behavior is undefined. Print out the 480 ** character itself as printf(3) also does. 481 */ 482 default: 483 if (pt != ptlim) 484 *pt++ = *format; 485 break; 486 } 487 } 488 return pt; 489 } 490 491 static wchar_t * 492 _conv(int n, const wchar_t *format, wchar_t *pt, const wchar_t *ptlim) 493 { 494 wchar_t buf[INT_STRLEN_MAXIMUM(int) + 1]; 495 496 (void) swprintf(buf, sizeof buf/sizeof buf[0], format, n); 497 return _add(buf, pt, ptlim); 498 } 499 500 static wchar_t * 501 _add(const wchar_t *str, wchar_t *pt, const wchar_t *ptlim) 502 { 503 while (pt < ptlim && (*pt = *str++) != L'\0') 504 ++pt; 505 return pt; 506 } 507 508 static wchar_t * 509 _sadd(const char *str, wchar_t *pt, const wchar_t *ptlim) 510 { 511 while (pt < ptlim && (*pt = btowc(*str++)) != L'\0') 512 ++pt; 513 return pt; 514 } 515 /* 516 ** POSIX and the C Standard are unclear or inconsistent about 517 ** what %C and %y do if the year is negative or exceeds 9999. 518 ** Use the convention that %C concatenated with %y yields the 519 ** same output as %Y, and that %Y contains at least 4 bytes, 520 ** with more only if necessary. 521 */ 522 523 static wchar_t * 524 _yconv(int a, int b, int convert_top, int convert_yy, wchar_t *pt, 525 const wchar_t *ptlim) 526 { 527 int lead; 528 int trail; 529 530 #define DIVISOR 100 531 trail = a % DIVISOR + b % DIVISOR; 532 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 533 trail %= DIVISOR; 534 if (trail < 0 && lead > 0) { 535 trail += DIVISOR; 536 --lead; 537 } else if (lead < 0 && trail > 0) { 538 trail -= DIVISOR; 539 ++lead; 540 } 541 if (convert_top) { 542 if (lead == 0 && trail < 0) 543 pt = _add(L"-0", pt, ptlim); 544 else pt = _conv(lead, L"%02d", pt, ptlim); 545 } 546 if (convert_yy) 547 pt = _conv(((trail < 0) ? -trail : trail), L"%02d", pt, ptlim); 548 return pt; 549 } 550 551