428
+ − 1 /* strftime - custom formatting of date and/or time
+ − 2 Copyright (C) 1989, 1991, 1992 Free Software Foundation, Inc.
+ − 3
+ − 4 This program is free software; you can redistribute it and/or modify
+ − 5 it under the terms of the GNU General Public License as published by
+ − 6 the Free Software Foundation; either version 2, or (at your option)
+ − 7 any later version.
+ − 8
+ − 9 This program is distributed in the hope that it will be useful,
+ − 10 but WITHOUT ANY WARRANTY; without even the implied warranty of
+ − 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ − 12 GNU General Public License for more details.
+ − 13
+ − 14 You should have received a copy of the GNU General Public License
+ − 15 along with this program; see the file COPYING. If not, write to
+ − 16 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ − 17 Boston, MA 02111-1307, USA. */
+ − 18
+ − 19 /* Synched up with: FSF 19.30. */
+ − 20
771
+ − 21 /* This file has been ... uhhhhh ... Mule-ized. Yeah.
+ − 22
+ − 23 (Everything here is external format. This is DANGEROUS and
+ − 24 data-lossy, but fixing it is too much of a bother now.) --ben */
+ − 25
428
+ − 26 /* Note: this version of strftime lacks locale support,
+ − 27 but it is standalone.
+ − 28
+ − 29 Performs `%' substitutions similar to those in printf. Except
+ − 30 where noted, substituted fields have a fixed size; numeric fields are
+ − 31 padded if necessary. Padding is with zeros by default; for fields
+ − 32 that display a single number, padding can be changed or inhibited by
+ − 33 following the `%' with one of the modifiers described below. Unknown
+ − 34 field specifiers are copied as normal characters. All other
+ − 35 characters are copied to the output without change.
+ − 36
+ − 37 Supports a superset of the ANSI C field specifiers.
+ − 38
+ − 39 Literal character fields:
+ − 40 % %
+ − 41 n newline
+ − 42 t tab
+ − 43
+ − 44 Numeric modifiers (a nonstandard extension):
+ − 45 - do not pad the field
+ − 46 _ pad the field with spaces
+ − 47
+ − 48 Time fields:
+ − 49 %H hour (00..23)
+ − 50 %I hour (01..12)
+ − 51 %k hour ( 0..23)
+ − 52 %l hour ( 1..12)
+ − 53 %M minute (00..59)
+ − 54 %p locale's AM or PM
+ − 55 %r time, 12-hour (hh:mm:ss [AP]M)
+ − 56 %R time, 24-hour (hh:mm)
+ − 57 %s time in seconds since 00:00:00, Jan 1, 1970 (a nonstandard extension)
+ − 58 %S second (00..61)
+ − 59 %T time, 24-hour (hh:mm:ss)
+ − 60 %X locale's time representation (%H:%M:%S)
+ − 61 %Z time zone (EDT), or nothing if no time zone is determinable
+ − 62
+ − 63 Date fields:
+ − 64 %a locale's abbreviated weekday name (Sun..Sat)
+ − 65 %A locale's full weekday name, variable length (Sunday..Saturday)
+ − 66 %b locale's abbreviated month name (Jan..Dec)
+ − 67 %B locale's full month name, variable length (January..December)
+ − 68 %c locale's date and time (Sat Nov 04 12:02:33 EST 1989)
+ − 69 %C century (00..99)
+ − 70 %d day of month (01..31)
+ − 71 %e day of month ( 1..31)
+ − 72 %D date (mm/dd/yy)
+ − 73 %h same as %b
+ − 74 %j day of year (001..366)
+ − 75 %m month (01..12)
+ − 76 %U week number of year with Sunday as first day of week (00..53)
+ − 77 %w day of week (0..6)
+ − 78 %W week number of year with Monday as first day of week (00..53)
+ − 79 %x locale's date representation (mm/dd/yy)
+ − 80 %y last two digits of year (00..99)
+ − 81 %Y year (1970...)
+ − 82
+ − 83 David MacKenzie <djm@gnu.ai.mit.edu> */
+ − 84
+ − 85 #ifdef HAVE_CONFIG_H
+ − 86 #include <config.h>
+ − 87 #include "lisp.h"
+ − 88 #endif
+ − 89
+ − 90 #include <stdio.h>
+ − 91 #include <sys/types.h>
+ − 92 #if defined(TM_IN_SYS_TIME) || (!defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME))
+ − 93 #include <sys/time.h>
+ − 94 #else
+ − 95 #include <time.h>
+ − 96 #endif
+ − 97
+ − 98 #ifndef STDC_HEADERS
+ − 99 time_t mktime ();
+ − 100 #endif
+ − 101
442
+ − 102 #if defined(WIN32_NATIVE) || defined(CYGWIN)
428
+ − 103 #include <time.h>
+ − 104 #else
+ − 105 #if defined(HAVE_TZNAME)
+ − 106 extern char *tzname[2];
+ − 107 #endif
442
+ − 108 #endif /* WIN32_NATIVE */
428
+ − 109
+ − 110 #ifdef emacs
+ − 111 #define strftime emacs_strftime
+ − 112 #endif
+ − 113
+ − 114 /* Types of padding for numbers in date and time. */
+ − 115 enum padding
+ − 116 {
+ − 117 none, blank, zero
+ − 118 };
+ − 119
442
+ − 120 static char const* const days[] =
428
+ − 121 {
+ − 122 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
+ − 123 };
+ − 124
442
+ − 125 static char const * const months[] =
428
+ − 126 {
+ − 127 "January", "February", "March", "April", "May", "June",
+ − 128 "July", "August", "September", "October", "November", "December"
+ − 129 };
+ − 130
+ − 131 /* Add character C to STRING and increment LENGTH,
+ − 132 unless LENGTH would exceed MAX. */
+ − 133
+ − 134 #define add_char(c) do \
+ − 135 { \
+ − 136 if (length + 1 <= max) \
+ − 137 string[length++] = (c); \
+ − 138 } while (0)
+ − 139
+ − 140 /* Add a 2 digit number to STRING, padding if specified.
+ − 141 Return the number of characters added, up to MAX. */
+ − 142
+ − 143 static int
+ − 144 add_num2 (char *string, int num, int max, enum padding pad)
+ − 145 {
+ − 146 int top = num / 10;
+ − 147 int length = 0;
+ − 148
+ − 149 if (top == 0 && pad == blank)
+ − 150 add_char (' ');
+ − 151 else if (top != 0 || pad == zero)
+ − 152 add_char (top + '0');
+ − 153 add_char (num % 10 + '0');
+ − 154 return length;
+ − 155 }
+ − 156
+ − 157 /* Add a 3 digit number to STRING, padding if specified.
+ − 158 Return the number of characters added, up to MAX. */
+ − 159
+ − 160 static int
+ − 161 add_num3 (char *string, int num, int max, enum padding pad)
+ − 162 {
+ − 163 int top = num / 100;
+ − 164 int mid = (num - top * 100) / 10;
+ − 165 int length = 0;
+ − 166
+ − 167 if (top == 0 && pad == blank)
+ − 168 add_char (' ');
+ − 169 else if (top != 0 || pad == zero)
+ − 170 add_char (top + '0');
+ − 171 if (mid == 0 && top == 0 && pad == blank)
+ − 172 add_char (' ');
+ − 173 else if (mid != 0 || top != 0 || pad == zero)
+ − 174 add_char (mid + '0');
+ − 175 add_char (num % 10 + '0');
+ − 176 return length;
+ − 177 }
+ − 178
+ − 179 /* Like strncpy except return the number of characters copied. */
+ − 180
+ − 181 static int
442
+ − 182 add_str (char *to, const char *from, int max)
428
+ − 183 {
+ − 184 int i;
+ − 185
+ − 186 for (i = 0; from[i] && i <= max; ++i)
+ − 187 to[i] = from[i];
+ − 188 return i;
+ − 189 }
+ − 190
+ − 191 static int
+ − 192 add_num_time_t (char *string, int max, time_t num)
+ − 193 {
+ − 194 /* This buffer is large enough to hold the character representation
+ − 195 (including the trailing NUL) of any unsigned decimal quantity
+ − 196 whose binary representation fits in 128 bits. */
+ − 197 char buf[40];
+ − 198
+ − 199 if (sizeof (num) > 16)
+ − 200 abort ();
+ − 201 sprintf (buf, "%lu", (unsigned long) num);
793
+ − 202 return add_str (string, buf, max);
428
+ − 203 }
+ − 204
+ − 205 /* Return the week in the year of the time in TM, with the weeks
+ − 206 starting on Sundays. */
+ − 207
+ − 208 static int
442
+ − 209 sun_week (const struct tm *tm)
428
+ − 210 {
+ − 211 int dl;
+ − 212
+ − 213 /* Set `dl' to the day in the year of the last day of the week previous
+ − 214 to the one containing the day specified in TM. If the day specified
+ − 215 in TM is in the first week of the year, `dl' will be negative or 0.
+ − 216 Otherwise, calculate the number of complete weeks before our week
+ − 217 (dl / 7) and add any partial week at the start of the year (dl % 7). */
+ − 218 dl = tm->tm_yday - tm->tm_wday;
+ − 219 return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
+ − 220 }
+ − 221
+ − 222 /* Return the week in the year of the time in TM, with the weeks
+ − 223 starting on Mondays. */
+ − 224
+ − 225 static int
442
+ − 226 mon_week (const struct tm *tm)
428
+ − 227 {
+ − 228 int dl, wday;
+ − 229
+ − 230 if (tm->tm_wday == 0)
+ − 231 wday = 6;
+ − 232 else
+ − 233 wday = tm->tm_wday - 1;
+ − 234 dl = tm->tm_yday - wday;
+ − 235 return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
+ − 236 }
+ − 237
+ − 238 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
442
+ − 239 char *zone_name (const struct tm *tp);
428
+ − 240 char *
442
+ − 241 zone_name (const struct tm *tp)
428
+ − 242 {
+ − 243 char *timezone ();
+ − 244 struct timeval tv;
+ − 245 struct timezone tz;
+ − 246
+ − 247 gettimeofday (&tv, &tz);
+ − 248 return timezone (tz.tz_minuteswest, tp->tm_isdst);
+ − 249 }
+ − 250 #endif
+ − 251
+ − 252 /* Format the time given in TM according to FORMAT, and put the
+ − 253 results in STRING.
+ − 254 Return the number of characters (not including terminating null)
+ − 255 that were put into STRING, or 0 if the length would have
+ − 256 exceeded MAX. */
+ − 257
442
+ − 258 size_t strftime (char *string, size_t max, const char *format,
+ − 259 const struct tm *tm);
428
+ − 260
+ − 261 size_t
442
+ − 262 strftime (char *string, size_t max, const char *format, const struct tm *tm)
428
+ − 263 {
+ − 264 enum padding pad; /* Type of padding to apply. */
+ − 265 size_t length = 0; /* Characters put in STRING so far. */
+ − 266
+ − 267 for (; *format && length < max; ++format)
+ − 268 {
+ − 269 if (*format != '%')
+ − 270 add_char (*format);
+ − 271 else
+ − 272 {
+ − 273 ++format;
+ − 274 /* Modifiers: */
+ − 275 if (*format == '-')
+ − 276 {
+ − 277 pad = none;
+ − 278 ++format;
+ − 279 }
+ − 280 else if (*format == '_')
+ − 281 {
+ − 282 pad = blank;
+ − 283 ++format;
+ − 284 }
+ − 285 else
+ − 286 pad = zero;
+ − 287
+ − 288 switch (*format)
+ − 289 {
+ − 290 /* Literal character fields: */
+ − 291 case 0:
+ − 292 case '%':
+ − 293 add_char ('%');
+ − 294 break;
+ − 295 case 'n':
+ − 296 add_char ('\n');
+ − 297 break;
+ − 298 case 't':
+ − 299 add_char ('\t');
+ − 300 break;
+ − 301 default:
+ − 302 add_char (*format);
+ − 303 break;
+ − 304
+ − 305 /* Time fields: */
+ − 306 case 'H':
+ − 307 case 'k':
+ − 308 length +=
+ − 309 add_num2 (&string[length], tm->tm_hour, max - length,
+ − 310 *format == 'H' ? pad : blank);
+ − 311 break;
+ − 312 case 'I':
+ − 313 case 'l':
+ − 314 {
+ − 315 int hour12;
+ − 316
+ − 317 if (tm->tm_hour == 0)
+ − 318 hour12 = 12;
+ − 319 else if (tm->tm_hour > 12)
+ − 320 hour12 = tm->tm_hour - 12;
+ − 321 else
+ − 322 hour12 = tm->tm_hour;
+ − 323 length +=
+ − 324 add_num2 (&string[length], hour12, max - length,
+ − 325 *format == 'I' ? pad : blank);
+ − 326 }
+ − 327 break;
+ − 328 case 'M':
+ − 329 length +=
+ − 330 add_num2 (&string[length], tm->tm_min, max - length, pad);
+ − 331 break;
+ − 332 case 'p':
+ − 333 if (tm->tm_hour < 12)
+ − 334 add_char ('A');
+ − 335 else
+ − 336 add_char ('P');
+ − 337 add_char ('M');
+ − 338 break;
+ − 339 case 'r':
+ − 340 length +=
+ − 341 strftime (&string[length], max - length, "%I:%M:%S %p", tm);
+ − 342 break;
+ − 343 case 'R':
+ − 344 length +=
+ − 345 strftime (&string[length], max - length, "%H:%M", tm);
+ − 346 break;
+ − 347
+ − 348 case 's':
+ − 349 {
+ − 350 struct tm writable_tm;
+ − 351 writable_tm = *tm;
+ − 352 length += add_num_time_t (&string[length], max - length,
+ − 353 mktime (&writable_tm));
+ − 354 }
+ − 355 break;
+ − 356
+ − 357 case 'S':
+ − 358 length +=
+ − 359 add_num2 (&string[length], tm->tm_sec, max - length, pad);
+ − 360 break;
+ − 361 case 'T':
+ − 362 length +=
+ − 363 strftime (&string[length], max - length, "%H:%M:%S", tm);
+ − 364 break;
+ − 365 case 'X':
+ − 366 length +=
+ − 367 strftime (&string[length], max - length, "%H:%M:%S", tm);
+ − 368 break;
+ − 369 case 'Z':
+ − 370 #ifdef HAVE_TM_ZONE
+ − 371 length += add_str (&string[length], tm->tm_zone, max - length);
+ − 372 #else
+ − 373 #ifdef HAVE_TZNAME
+ − 374 if (tm->tm_isdst && tzname[1] && *tzname[1])
+ − 375 length += add_str (&string[length], tzname[1], max - length);
+ − 376 else
+ − 377 length += add_str (&string[length], tzname[0], max - length);
+ − 378 #else
+ − 379 length += add_str (&string[length], zone_name (tm), max - length);
+ − 380 #endif
+ − 381 #endif
+ − 382 break;
+ − 383
+ − 384 /* Date fields: */
+ − 385 case 'a':
+ − 386 add_char (days[tm->tm_wday][0]);
+ − 387 add_char (days[tm->tm_wday][1]);
+ − 388 add_char (days[tm->tm_wday][2]);
+ − 389 break;
+ − 390 case 'A':
+ − 391 length +=
+ − 392 add_str (&string[length], days[tm->tm_wday], max - length);
+ − 393 break;
+ − 394 case 'b':
+ − 395 case 'h':
+ − 396 add_char (months[tm->tm_mon][0]);
+ − 397 add_char (months[tm->tm_mon][1]);
+ − 398 add_char (months[tm->tm_mon][2]);
+ − 399 break;
+ − 400 case 'B':
+ − 401 length +=
+ − 402 add_str (&string[length], months[tm->tm_mon], max - length);
+ − 403 break;
+ − 404 case 'c':
+ − 405 length +=
+ − 406 strftime (&string[length], max - length,
+ − 407 "%a %b %d %H:%M:%S %Z %Y", tm);
+ − 408 break;
+ − 409 case 'C':
+ − 410 length +=
+ − 411 add_num2 (&string[length], (tm->tm_year + 1900) / 100,
+ − 412 max - length, pad);
+ − 413 break;
+ − 414 case 'd':
+ − 415 length +=
+ − 416 add_num2 (&string[length], tm->tm_mday, max - length, pad);
+ − 417 break;
+ − 418 case 'e':
+ − 419 length +=
+ − 420 add_num2 (&string[length], tm->tm_mday, max - length, blank);
+ − 421 break;
+ − 422 case 'D':
+ − 423 length +=
+ − 424 strftime (&string[length], max - length, "%m/%d/%y", tm);
+ − 425 break;
+ − 426 case 'j':
+ − 427 length +=
+ − 428 add_num3 (&string[length], tm->tm_yday + 1, max - length, pad);
+ − 429 break;
+ − 430 case 'm':
+ − 431 length +=
+ − 432 add_num2 (&string[length], tm->tm_mon + 1, max - length, pad);
+ − 433 break;
+ − 434 case 'U':
+ − 435 length +=
+ − 436 add_num2 (&string[length], sun_week (tm), max - length, pad);
+ − 437 break;
+ − 438 case 'w':
+ − 439 add_char (tm->tm_wday + '0');
+ − 440 break;
+ − 441 case 'W':
+ − 442 length +=
+ − 443 add_num2 (&string[length], mon_week (tm), max - length, pad);
+ − 444 break;
+ − 445 case 'x':
+ − 446 length +=
+ − 447 strftime (&string[length], max - length, "%m/%d/%y", tm);
+ − 448 break;
+ − 449 case 'y':
+ − 450 length +=
+ − 451 add_num2 (&string[length], tm->tm_year % 100,
+ − 452 max - length, pad);
+ − 453 break;
+ − 454 case 'Y':
+ − 455 add_char ((tm->tm_year + 1900) / 1000 + '0');
+ − 456 length +=
+ − 457 add_num3 (&string[length],
+ − 458 (1900 + tm->tm_year) % 1000, max - length, zero);
+ − 459 break;
+ − 460 }
+ − 461 }
+ − 462 }
+ − 463 add_char (0);
+ − 464 return length - 1;
+ − 465 }