1/* $OpenBSD: wcsftime.c,v 1.3 2014/05/06 15:49:45 tedu Exp $ */ 2#include "private.h" 3 4/* 5** Based on the UCB version with the ID appearing below. 6** This is ANSIish only when "multibyte character == plain character". 7** 8** Copyright (c) 1989, 1993 9** The Regents of the University of California. All rights reserved. 10** 11** Redistribution and use in source and binary forms, with or without 12** modification, are permitted provided that the following conditions 13** are met: 14** 1. Redistributions of source code must retain the above copyright 15** notice, this list of conditions and the following disclaimer. 16** 2. Redistributions in binary form must reproduce the above copyright 17** notice, this list of conditions and the following disclaimer in the 18** documentation and/or other materials provided with the distribution. 19** 3. 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 "tzfile.h" 37#include "fcntl.h" 38#include <locale.h> 39#include <wchar.h> 40 41struct 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 56static 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"?" 104static wchar_t * _add(const wchar_t *, wchar_t *, const wchar_t *); 105static wchar_t * _sadd(const char *, wchar_t *, const wchar_t *); 106static wchar_t * _conv(int, const wchar_t *, wchar_t *, const wchar_t *); 107static wchar_t * _fmt(const wchar_t *, const struct tm *, wchar_t *, const wchar_t *, 108 int *); 109static wchar_t * _yconv(int, int, int, int, wchar_t *, const wchar_t *); 110 111extern char * tzname[]; 112 113#define IN_NONE 0 114#define IN_SOME 1 115#define IN_THIS 2 116#define IN_ALL 3 117 118size_t 119wcsftime(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 137static 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 } 148label: 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 if (TYPE_SIGNED(time_t)) 296 (void) swprintf(buf, 297 sizeof buf/sizeof buf[0], 298 L"%ld", (long) mkt); 299 else 300 (void) swprintf(buf, 301 sizeof buf/sizeof buf[0], 302 L"%lu", (unsigned long) mkt); 303 pt = _add(buf, pt, ptlim); 304 } 305 continue; 306 case 'T': 307 pt = _fmt(L"%H:%M:%S", t, pt, ptlim, warnp); 308 continue; 309 case 't': 310 pt = _add(L"\t", pt, ptlim); 311 continue; 312 case 'U': 313 pt = _conv((t->tm_yday + DAYSPERWEEK - 314 t->tm_wday) / DAYSPERWEEK, 315 L"%02d", pt, ptlim); 316 continue; 317 case 'u': 318 /* 319 ** From Arnold Robbins' strftime version 3.0: 320 ** "ISO 8601: Weekday as a decimal number 321 ** [1 (Monday) - 7]" 322 ** (ado, 1993-05-24) 323 */ 324 pt = _conv((t->tm_wday == 0) ? 325 DAYSPERWEEK : t->tm_wday, 326 L"%d", pt, ptlim); 327 continue; 328 case 'V': /* ISO 8601 week number */ 329 case 'G': /* ISO 8601 year (four digits) */ 330 case 'g': /* ISO 8601 year (two digits) */ 331/* 332** From Arnold Robbins' strftime version 3.0: "the week number of the 333** year (the first Monday as the first day of week 1) as a decimal number 334** (01-53)." 335** (ado, 1993-05-24) 336** 337** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: 338** "Week 01 of a year is per definition the first week which has the 339** Thursday in this year, which is equivalent to the week which contains 340** the fourth day of January. In other words, the first week of a new year 341** is the week which has the majority of its days in the new year. Week 01 342** might also contain days from the previous year and the week before week 343** 01 of a year is the last week (52 or 53) of the previous year even if 344** it contains days from the new year. A week starts with Monday (day 1) 345** and ends with Sunday (day 7). For example, the first week of the year 346** 1997 lasts from 1996-12-30 to 1997-01-05..." 347** (ado, 1996-01-02) 348*/ 349 { 350 int year; 351 int base; 352 int yday; 353 int wday; 354 int w; 355 356 year = t->tm_year; 357 base = TM_YEAR_BASE; 358 yday = t->tm_yday; 359 wday = t->tm_wday; 360 for ( ; ; ) { 361 int len; 362 int bot; 363 int top; 364 365 len = isleap_sum(year, base) ? 366 DAYSPERLYEAR : 367 DAYSPERNYEAR; 368 /* 369 ** What yday (-3 ... 3) does the ISO year 370 ** begin on? 371 */ 372 bot = ((yday + 11 - wday) % DAYSPERWEEK) - 3; 373 /* 374 ** What yday does the NEXT ISO year begin on? 375 */ 376 top = bot - (len % DAYSPERWEEK); 377 if (top < -3) 378 top += DAYSPERWEEK; 379 top += len; 380 if (yday >= top) { 381 ++base; 382 w = 1; 383 break; 384 } 385 if (yday >= bot) { 386 w = 1 + ((yday - bot) / DAYSPERWEEK); 387 break; 388 } 389 --base; 390 yday += isleap_sum(year, base) ? 391 DAYSPERLYEAR : 392 DAYSPERNYEAR; 393 } 394 if ((w == 52 && t->tm_mon == TM_JANUARY) || 395 (w == 1 && t->tm_mon == TM_DECEMBER)) 396 w = 53; 397 if (*format == 'V') 398 pt = _conv(w, L"%02d", pt, ptlim); 399 else if (*format == 'g') { 400 *warnp = IN_ALL; 401 pt = _yconv(year, base, 0, 1, pt, ptlim); 402 } else 403 pt = _yconv(year, base, 1, 1, pt, ptlim); 404 } 405 continue; 406 case 'v': 407 /* 408 ** From Arnold Robbins' strftime version 3.0: 409 ** "date as dd-bbb-YYYY" 410 ** (ado, 1993-05-24) 411 */ 412 pt = _fmt(L"%e-%b-%Y", t, pt, ptlim, warnp); 413 continue; 414 case 'W': 415 pt = _conv((t->tm_yday + DAYSPERWEEK - 416 (t->tm_wday ? 417 (t->tm_wday - 1) : 418 (DAYSPERWEEK - 1))) / DAYSPERWEEK, 419 L"%02d", pt, ptlim); 420 continue; 421 case 'w': 422 pt = _conv(t->tm_wday, L"%d", pt, ptlim); 423 continue; 424 case 'X': 425 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); 426 continue; 427 case 'x': 428 { 429 int warn2 = IN_SOME; 430 431 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); 432 if (warn2 == IN_ALL) 433 warn2 = IN_THIS; 434 if (warn2 > *warnp) 435 *warnp = warn2; 436 } 437 continue; 438 case 'y': 439 *warnp = IN_ALL; 440 pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1, pt, ptlim); 441 continue; 442 case 'Y': 443 pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1, pt, ptlim); 444 continue; 445 case 'Z': 446 if (t->tm_zone != NULL) 447 pt = _sadd(t->TM_ZONE, pt, ptlim); 448 else 449 if (t->tm_isdst >= 0) 450 pt = _sadd(tzname[t->tm_isdst != 0], 451 pt, ptlim); 452 /* 453 ** C99 says that %Z must be replaced by the 454 ** empty string if the time zone is not 455 ** determinable. 456 */ 457 continue; 458 case 'z': 459 { 460 int diff; 461 wchar_t const * sign; 462 463 if (t->tm_isdst < 0) 464 continue; 465 diff = t->tm_gmtoff; 466 if (diff < 0) { 467 sign = L"-"; 468 diff = -diff; 469 } else 470 sign = L"+"; 471 pt = _add(sign, pt, ptlim); 472 diff /= SECSPERMIN; 473 diff = (diff / MINSPERHOUR) * 100 + 474 (diff % MINSPERHOUR); 475 pt = _conv(diff, L"%04d", pt, ptlim); 476 } 477 continue; 478 case '+': 479 pt = _fmt(Locale->date_fmt, t, pt, ptlim, warnp); 480 continue; 481 case '%': 482 /* 483 ** X311J/88-090 (4.12.3.5): if conversion wchar_t is 484 ** undefined, behavior is undefined. Print out the 485 ** character itself as printf(3) also does. 486 */ 487 default: 488 if (pt != ptlim) 489 *pt++ = *format; 490 break; 491 } 492 } 493 return pt; 494} 495 496static wchar_t * 497_conv(int n, const wchar_t *format, wchar_t *pt, const wchar_t *ptlim) 498{ 499 wchar_t buf[INT_STRLEN_MAXIMUM(int) + 1]; 500 501 (void) swprintf(buf, sizeof buf/sizeof buf[0], format, n); 502 return _add(buf, pt, ptlim); 503} 504 505static wchar_t * 506_add(const wchar_t *str, wchar_t *pt, const wchar_t *ptlim) 507{ 508 while (pt < ptlim && (*pt = *str++) != L'\0') 509 ++pt; 510 return pt; 511} 512 513static wchar_t * 514_sadd(const char *str, wchar_t *pt, const wchar_t *ptlim) 515{ 516 while (pt < ptlim && (*pt = btowc(*str++)) != L'\0') 517 ++pt; 518 return pt; 519} 520/* 521** POSIX and the C Standard are unclear or inconsistent about 522** what %C and %y do if the year is negative or exceeds 9999. 523** Use the convention that %C concatenated with %y yields the 524** same output as %Y, and that %Y contains at least 4 bytes, 525** with more only if necessary. 526*/ 527 528static wchar_t * 529_yconv(int a, int b, int convert_top, int convert_yy, wchar_t *pt, 530 const wchar_t *ptlim) 531{ 532 register int lead; 533 register int trail; 534 535#define DIVISOR 100 536 trail = a % DIVISOR + b % DIVISOR; 537 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; 538 trail %= DIVISOR; 539 if (trail < 0 && lead > 0) { 540 trail += DIVISOR; 541 --lead; 542 } else if (lead < 0 && trail > 0) { 543 trail -= DIVISOR; 544 ++lead; 545 } 546 if (convert_top) { 547 if (lead == 0 && trail < 0) 548 pt = _add(L"-0", pt, ptlim); 549 else pt = _conv(lead, L"%02d", pt, ptlim); 550 } 551 if (convert_yy) 552 pt = _conv(((trail < 0) ? -trail : trail), L"%02d", pt, ptlim); 553 return pt; 554} 555 556