1 #ifndef lint 2 #ifndef NOID 3 static char elsieid[] = "@(#)strftime.c 7.19"; 4 /* 5 ** Based on the UCB version with the ID appearing below. 6 ** This is ANSIish only when time is treated identically in all locales and 7 ** when "multibyte character == plain character". 8 */ 9 #endif /* !defined NOID */ 10 #endif /* !defined lint */ 11 12 #include "private.h" 13 14 /* 15 * Copyright (c) 1989 The Regents of the University of California. 16 * All rights reserved. 17 * 18 * Redistribution and use in source and binary forms are permitted 19 * provided that the above copyright notice and this paragraph are 20 * duplicated in all such forms and that any documentation, 21 * advertising materials, and other materials related to such 22 * distribution and use acknowledge that the software was developed 23 * by the University of California, Berkeley. The name of the 24 * University may not be used to endorse or promote products derived 25 * from this software without specific prior written permission. 26 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 27 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 28 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 29 */ 30 31 #ifndef LIBC_SCCS 32 #ifndef lint 33 static const char sccsid[] = "@(#)strftime.c 5.4 (Berkeley) 3/14/89"; 34 #endif /* !defined lint */ 35 #endif /* !defined LIBC_SCCS */ 36 37 #include "tzfile.h" 38 39 static const char afmt[][4] = { 40 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" 41 }; 42 static const char Afmt[][10] = { 43 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", 44 "Saturday" 45 }; 46 static const char bfmt[][4] = { 47 "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", 48 "Oct", "Nov", "Dec" 49 }; 50 static const char Bfmt[][10] = { 51 "January", "February", "March", "April", "May", "June", "July", 52 "August", "September", "October", "November", "December" 53 }; 54 55 static char *_add P((const char *, char *, const char *)); 56 static char *_conv P((int, const char *, char *, const char *)); 57 static char *_fmt P((const char *, const struct tm *, char *, const char *)); 58 59 size_t strftime P((char *, size_t, const char *, const struct tm *)); 60 61 extern char *tzname[]; 62 63 size_t 64 strftime(s, maxsize, format, t) 65 char *s; 66 size_t maxsize; 67 const char *format; 68 const struct tm *t; 69 { 70 char *p; 71 72 p = _fmt(format, t, s, s + maxsize); 73 if (p == s + maxsize) 74 return 0; 75 *p = '\0'; 76 return p - s; 77 } 78 79 static char * 80 _fmt(format, t, pt, ptlim) 81 const char *format; 82 const struct tm *t; 83 char *pt; 84 const char *ptlim; 85 { 86 for (; *format; ++format) { 87 if (*format == '%') { 88 label: 89 switch(*++format) { 90 case '\0': 91 --format; 92 break; 93 case 'A': 94 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ? 95 "?" : Afmt[t->tm_wday], pt, ptlim); 96 continue; 97 case 'a': 98 pt = _add((t->tm_wday < 0 || t->tm_wday > 6) ? 99 "?" : afmt[t->tm_wday], pt, ptlim); 100 continue; 101 case 'B': 102 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? 103 "?" : Bfmt[t->tm_mon], pt, ptlim); 104 continue; 105 case 'b': 106 case 'h': 107 pt = _add((t->tm_mon < 0 || t->tm_mon > 11) ? 108 "?" : bfmt[t->tm_mon], pt, ptlim); 109 continue; 110 case 'c': 111 pt = _fmt("%D %X", t, pt, ptlim); 112 continue; 113 case 'C': 114 /* 115 ** %C used to do a... 116 ** _fmt("%a %b %e %X %Y", t); 117 ** ...whereas now POSIX 1003.2 calls for 118 ** something completely different. 119 ** (ado, 5/24/93) 120 */ 121 pt = _conv((t->tm_year + TM_YEAR_BASE) / 100, 122 "%02d", pt, ptlim); 123 continue; 124 case 'D': 125 pt = _fmt("%m/%d/%y", t, pt, ptlim); 126 continue; 127 case 'x': 128 /* 129 ** Version 3.0 of strftime from Arnold Robbins 130 ** (arnold@skeeve.atl.ga.us) does the 131 ** equivalent of... 132 ** _fmt("%a %b %e %Y"); 133 ** ...for %x; since the X3J11 C language 134 ** standard calls for "date, using locale's 135 ** date format," anything goes. Using just 136 ** numbers (as here) makes Quakers happier. 137 ** Word from Paul Eggert (eggert@twinsun.com) 138 ** is that %Y-%m-%d is the ISO standard date 139 ** format, specified in ISO 2014 and later 140 ** ISO 8601:1988, with a summary available in 141 ** pub/doc/ISO/english/ISO8601.ps.Z on 142 ** ftp.uni-erlangen.de. 143 ** (ado, 5/30/93) 144 */ 145 pt = _fmt("%m/%d/%y", t, pt, ptlim); 146 continue; 147 case 'd': 148 pt = _conv(t->tm_mday, "%02d", pt, ptlim); 149 continue; 150 case 'E': 151 case 'O': 152 /* 153 ** POSIX locale extensions, a la 154 ** Arnold Robbins' strftime version 3.0. 155 ** The sequences 156 ** %Ec %EC %Ex %Ey %EY 157 ** %Od %oe %OH %OI %Om %OM 158 ** %OS %Ou %OU %OV %Ow %OW %Oy 159 ** are supposed to provide alternate 160 ** representations. 161 ** (ado, 5/24/93) 162 */ 163 goto label; 164 case 'e': 165 pt = _conv(t->tm_mday, "%2d", pt, ptlim); 166 continue; 167 case 'H': 168 pt = _conv(t->tm_hour, "%02d", pt, ptlim); 169 continue; 170 case 'I': 171 pt = _conv((t->tm_hour % 12) ? 172 (t->tm_hour % 12) : 12, 173 "%02d", pt, ptlim); 174 continue; 175 case 'j': 176 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); 177 continue; 178 case 'k': 179 /* 180 ** This used to be... 181 ** _conv(t->tm_hour % 12 ? 182 ** t->tm_hour % 12 : 12, 2, ' '); 183 ** ...and has been changed to the below to 184 ** match SunOS 4.1.1 and Arnold Robbins' 185 ** strftime version 3.0. That is, "%k" and 186 ** "%l" have been swapped. 187 ** (ado, 5/24/93) 188 */ 189 pt = _conv(t->tm_hour, "%2d", pt, ptlim); 190 continue; 191 #ifdef KITCHEN_SINK 192 case 'K': 193 /* 194 ** After all this time, still unclaimed! 195 */ 196 pt = _add("kitchen sink", pt, ptlim); 197 continue; 198 #endif /* defined KITCHEN_SINK */ 199 case 'l': 200 /* 201 ** This used to be... 202 ** _conv(t->tm_hour, 2, ' '); 203 ** ...and has been changed to the below to 204 ** match SunOS 4.1.1 and Arnold Robbin's 205 ** strftime version 3.0. That is, "%k" and 206 ** "%l" have been swapped. 207 ** (ado, 5/24/93) 208 */ 209 pt = _conv((t->tm_hour % 12) ? 210 (t->tm_hour % 12) : 12, 211 "%2d", pt, ptlim); 212 continue; 213 case 'M': 214 pt = _conv(t->tm_min, "%02d", pt, ptlim); 215 continue; 216 case 'm': 217 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); 218 continue; 219 case 'n': 220 pt = _add("\n", pt, ptlim); 221 continue; 222 case 'p': 223 pt = _add(t->tm_hour >= 12 ? "PM" : "AM", 224 pt, ptlim); 225 continue; 226 case 'R': 227 pt = _fmt("%H:%M", t, pt, ptlim); 228 continue; 229 case 'r': 230 pt = _fmt("%I:%M:%S %p", t, pt, ptlim); 231 continue; 232 case 'S': 233 pt = _conv(t->tm_sec, "%02d", pt, ptlim); 234 continue; 235 case 'T': 236 case 'X': 237 pt = _fmt("%H:%M:%S", t, pt, ptlim); 238 continue; 239 case 't': 240 pt = _add("\t", pt, ptlim); 241 continue; 242 case 'U': 243 pt = _conv((t->tm_yday + 7 - t->tm_wday) / 7, 244 "%02d", pt, ptlim); 245 continue; 246 case 'u': 247 /* 248 ** From Arnold Robbins' strftime version 3.0: 249 ** "ISO 8601: Weekday as a decimal number 250 ** [1 (Monday) - 7]" 251 ** (ado, 5/24/93) 252 */ 253 pt = _conv((t->tm_wday == 0) ? 7 : t->tm_wday, 254 "%d", pt, ptlim); 255 continue; 256 case 'V': 257 /* 258 ** From Arnold Robbins' strftime version 3.0: 259 ** "the week number of the year (the first 260 ** Monday as the first day of week 1) as a 261 ** decimal number (01-53). The method for 262 ** determining the week number is as specified 263 ** by ISO 8601 (to wit: if the week containing 264 ** January 1 has four or more days in the new 265 ** year, then it is week 1, otherwise it is 266 ** week 53 of the previous year and the next 267 ** week is week 1)." 268 ** (ado, 5/24/93) 269 */ 270 /* 271 ** XXX--If January 1 falls on a Friday, 272 ** January 1-3 are part of week 53 of the 273 ** previous year. By analogy, if January 274 ** 1 falls on a Thursday, are December 29-31 275 ** of the PREVIOUS year part of week 1??? 276 ** (ado 5/24/93) 277 ** 278 ** You are understood not to expect this. 279 */ 280 { 281 int i; 282 283 i = (t->tm_yday + 10 - (t->tm_wday ? 284 (t->tm_wday - 1) : 6)) / 7; 285 if (i == 0) { 286 /* 287 ** What day of the week does 288 ** January 1 fall on? 289 */ 290 i = t->tm_wday - 291 (t->tm_yday - 1); 292 /* 293 ** Fri Jan 1: 53 294 ** Sun Jan 1: 52 295 ** Sat Jan 1: 53 if previous 296 ** year a leap 297 ** year, else 52 298 */ 299 if (i == TM_FRIDAY) 300 i = 53; 301 else if (i == TM_SUNDAY) 302 i = 52; 303 else i = isleap(t->tm_year + 304 TM_YEAR_BASE) ? 305 53 : 52; 306 #ifdef XPG4_1994_04_09 307 /* 308 ** As of 4/9/94, though, 309 ** XPG4 calls for 53 310 ** unconditionally. 311 */ 312 i = 53; 313 #endif /* defined XPG4_1994_04_09 */ 314 } 315 pt = _conv(i, "%02d", pt, ptlim); 316 } 317 continue; 318 case 'v': 319 /* 320 ** From Arnold Robbins' strftime version 3.0: 321 ** "date as dd-bbb-YYYY" 322 ** (ado, 5/24/93) 323 */ 324 pt = _fmt("%e-%b-%Y", t, pt, ptlim); 325 continue; 326 case 'W': 327 pt = _conv((t->tm_yday + 7 - 328 (t->tm_wday ? 329 (t->tm_wday - 1) : 6)) / 7, 330 "%02d", pt, ptlim); 331 continue; 332 case 'w': 333 pt = _conv(t->tm_wday, "%d", pt, ptlim); 334 continue; 335 case 'y': 336 pt = _conv((t->tm_year + TM_YEAR_BASE) % 100, 337 "%02d", pt, ptlim); 338 continue; 339 case 'Y': 340 pt = _conv(t->tm_year + TM_YEAR_BASE, "%04d", 341 pt, ptlim); 342 continue; 343 case 'Z': 344 #ifdef TM_ZONE 345 if (t->TM_ZONE) 346 pt = _add(t->TM_ZONE, pt, ptlim); 347 else 348 #endif /* defined TM_ZONE */ 349 if (t->tm_isdst == 0 || t->tm_isdst == 1) { 350 pt = _add(tzname[t->tm_isdst], 351 pt, ptlim); 352 } else pt = _add("?", pt, ptlim); 353 continue; 354 case '%': 355 /* 356 * X311J/88-090 (4.12.3.5): if conversion char is 357 * undefined, behavior is undefined. Print out the 358 * character itself as printf(3) also does. 359 */ 360 default: 361 break; 362 } 363 } 364 if (pt == ptlim) 365 break; 366 *pt++ = *format; 367 } 368 return pt; 369 } 370 371 static char * 372 _conv(n, format, pt, ptlim) 373 int n; 374 const char *format; 375 char *pt; 376 const char *ptlim; 377 { 378 char buf[INT_STRLEN_MAXIMUM(int) + 1]; 379 380 (void) sprintf(buf, format, n); 381 return _add(buf, pt, ptlim); 382 } 383 384 static char * 385 _add(str, pt, ptlim) 386 const char *str; 387 char *pt; 388 const char *ptlim; 389 { 390 while (pt < ptlim && (*pt = *str++) != '\0') 391 ++pt; 392 return pt; 393 } 394