1 /* strftime - custom formatting of date and/or time
2 Copyright (C) 1989, 1991, 1992 Free Software Foundation, Inc.
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)
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.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
18 /* Note: this version of strftime lacks locale support,
21 Performs `%' substitutions similar to those in printf. Except
22 where noted, substituted fields have a fixed size; numeric fields are
23 padded if necessary. Padding is with zeros by default; for fields
24 that display a single number, padding can be changed or inhibited by
25 following the `%' with one of the modifiers described below. Unknown
26 field specifiers are copied as normal characters. All other
27 characters are copied to the output without change.
29 Supports a superset of the ANSI C field specifiers.
31 Literal character fields:
36 Numeric modifiers (a nonstandard extension):
37 - do not pad the field
38 _ pad the field with spaces
47 %r time, 12-hour (hh:mm:ss [AP]M)
48 %R time, 24-hour (hh:mm)
49 %s time in seconds since 00:00:00, Jan 1, 1970 (a nonstandard extension)
51 %T time, 24-hour (hh:mm:ss)
52 %X locale's time representation (%H:%M:%S)
53 %z RFC-822 style numeric timezone (-0500) (a nonstandard extension)
54 %Z time zone (EDT), or nothing if no time zone is determinable
57 %a locale's abbreviated weekday name (Sun..Sat)
58 %A locale's full weekday name, variable length (Sunday..Saturday)
59 %b locale's abbreviated month name (Jan..Dec)
60 %B locale's full month name, variable length (January..December)
61 %c locale's date and time (Sat Nov 04 12:02:33 EST 1989)
63 %d day of month (01..31)
64 %e day of month ( 1..31)
67 %j day of year (001..366)
69 %U week number of year with Sunday as first day of week (00..53)
71 %W week number of year with Monday as first day of week (00..53)
72 %x locale's date representation (mm/dd/yy)
73 %y last two digits of year (00..99)
76 David MacKenzie <djm@gnu.ai.mit.edu> */
83 #include <sys/types.h>
84 #if defined(TM_IN_SYS_TIME) || (!defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME))
94 #if defined(HAVE_TZNAME)
95 extern char *tzname[2];
98 /* Types of padding for numbers in date and time. */
104 static char const* const days[] =
106 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
109 static char const * const months[] =
111 "January", "February", "March", "April", "May", "June",
112 "July", "August", "September", "October", "November", "December"
115 /* Add character C to STRING and increment LENGTH,
116 unless LENGTH would exceed MAX. */
118 #define add_char(c) \
121 if (length + 1 <= max) \
122 string[length++] = (c); \
126 /* Add a 2 digit number to STRING, padding if specified.
127 Return the number of characters added, up to MAX. */
130 add_num2 (string, num, max, pad)
139 if (top == 0 && pad == blank)
141 else if (top != 0 || pad == zero)
142 add_char (top + '0');
143 add_char (num % 10 + '0');
147 /* Add a 3 digit number to STRING, padding if specified.
148 Return the number of characters added, up to MAX. */
151 add_num3 (string, num, max, pad)
158 int mid = (num - top * 100) / 10;
161 if (top == 0 && pad == blank)
163 else if (top != 0 || pad == zero)
164 add_char (top + '0');
165 if (mid == 0 && top == 0 && pad == blank)
167 else if (mid != 0 || top != 0 || pad == zero)
168 add_char (mid + '0');
169 add_char (num % 10 + '0');
173 /* Like strncpy except return the number of characters copied. */
176 add_str (to, from, max)
183 for (i = 0; from[i] && i <= max; ++i)
189 add_num_time_t (string, max, num)
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. */
200 if (sizeof (num) > 16)
202 sprintf (buf, "%lu", (unsigned long) num);
203 length = add_str (string, buf, max);
207 /* Convert MINUTES_EAST into a string suitable for use as the RFC-822
208 timezone indicator. Write no more than MAX bytes into STRING.
209 Return the number of bytes written into STRING. */
212 add_num_tz (string, max, minutes_east)
222 if (minutes_east < 0)
225 minutes_east = -minutes_east;
230 length = 1 + add_num2 (&string[1], (minutes_east / 60) % 24, max - 1, zero);
231 length += add_num2 (&string[length], minutes_east % 60, max - length, zero);
236 /* Return the week in the year of the time in TM, with the weeks
237 starting on Sundays. */
245 /* Set `dl' to the day in the year of the last day of the week previous
246 to the one containing the day specified in TM. If the day specified
247 in TM is in the first week of the year, `dl' will be negative or 0.
248 Otherwise, calculate the number of complete weeks before our week
249 (dl / 7) and add any partial week at the start of the year (dl % 7). */
250 dl = tm->tm_yday - tm->tm_wday;
251 return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
254 /* Return the week in the year of the time in TM, with the weeks
255 starting on Mondays. */
263 if (tm->tm_wday == 0)
266 wday = tm->tm_wday - 1;
267 dl = tm->tm_yday - wday;
268 return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
271 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
280 gettimeofday (&tv, &tz);
281 return timezone (tz.tz_minuteswest, tp->tm_isdst);
285 /* Format the time given in TM according to FORMAT, and put the
287 Return the number of characters (not including terminating null)
288 that were put into STRING, or 0 if the length would have
292 strftime (string, max, format, tm)
298 enum padding pad; /* Type of padding to apply. */
299 size_t length = 0; /* Characters put in STRING so far. */
301 for (; *format && length < max; ++format)
314 else if (*format == '_')
324 /* Literal character fields: */
343 add_num2 (&string[length], tm->tm_hour, max - length,
344 *format == 'H' ? pad : blank);
351 if (tm->tm_hour == 0)
353 else if (tm->tm_hour > 12)
354 hour12 = tm->tm_hour - 12;
356 hour12 = tm->tm_hour;
358 add_num2 (&string[length], hour12, max - length,
359 *format == 'I' ? pad : blank);
364 add_num2 (&string[length], tm->tm_min, max - length, pad);
367 if (tm->tm_hour < 12)
375 strftime (&string[length], max - length, "%I:%M:%S %p", tm);
379 strftime (&string[length], max - length, "%H:%M", tm);
384 struct tm writable_tm;
386 length += add_num_time_t (&string[length], max - length,
387 mktime (&writable_tm));
393 add_num2 (&string[length], tm->tm_sec, max - length, pad);
397 strftime (&string[length], max - length, "%H:%M:%S", tm);
401 strftime (&string[length], max - length, "%H:%M:%S", tm);
411 tml = *localtime (&t); /* Canonicalize the local time */
414 /* Compute the difference */
416 diff = tml.tm_min - tmg.tm_min;
417 diff += 60 * (tml.tm_hour - tmg.tm_hour);
419 if (tml.tm_mon != tmg.tm_mon)
421 /* We assume no timezone differs from UTC by more than
422 +- 23 hours. This should be safe. */
423 if (tmg.tm_mday == 1)
425 else /* tml.tm_mday == 1 */
429 diff += 1440 * (tml.tm_mday - tmg.tm_mday);
431 length += add_num_tz (&string[length], max - length, diff);
436 length += add_str (&string[length], tm->tm_zone, max - length);
439 if (tm->tm_isdst && tzname[1] && *tzname[1])
440 length += add_str (&string[length], tzname[1], max - length);
442 length += add_str (&string[length], tzname[0], max - length);
444 length += add_str (&string[length], zone_name (tm), max - length);
451 add_char (days[tm->tm_wday][0]);
452 add_char (days[tm->tm_wday][1]);
453 add_char (days[tm->tm_wday][2]);
457 add_str (&string[length], days[tm->tm_wday], max - length);
461 add_char (months[tm->tm_mon][0]);
462 add_char (months[tm->tm_mon][1]);
463 add_char (months[tm->tm_mon][2]);
467 add_str (&string[length], months[tm->tm_mon], max - length);
471 strftime (&string[length], max - length,
472 "%a %b %d %H:%M:%S %Z %Y", tm);
476 add_num2 (&string[length], (tm->tm_year + 1900) / 100,
481 add_num2 (&string[length], tm->tm_mday, max - length, pad);
485 add_num2 (&string[length], tm->tm_mday, max - length, blank);
489 strftime (&string[length], max - length, "%m/%d/%y", tm);
493 add_num3 (&string[length], tm->tm_yday + 1, max - length, pad);
497 add_num2 (&string[length], tm->tm_mon + 1, max - length, pad);
501 add_num2 (&string[length], sun_week (tm), max - length, pad);
504 add_char (tm->tm_wday + '0');
508 add_num2 (&string[length], mon_week (tm), max - length, pad);
512 strftime (&string[length], max - length, "%m/%d/%y", tm);
516 add_num2 (&string[length], tm->tm_year % 100,
520 add_char ((tm->tm_year + 1900) / 1000 + '0');
522 add_num3 (&string[length],
523 (1900 + tm->tm_year) % 1000, max - length, zero);