1 /* strftime - custom formatting of date and/or time
2 Copyright (C) 1989, 1991, 1992 Free Software Foundation, Inc.
4 This file is part of SXEmacs
6 SXEmacs is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 SXEmacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>. */
20 /* Synched up with: FSF 19.30. */
22 /* Note: this version of strftime lacks locale support,
25 Performs `%' substitutions similar to those in printf. Except
26 where noted, substituted fields have a fixed size; numeric fields are
27 padded if necessary. Padding is with zeros by default; for fields
28 that display a single number, padding can be changed or inhibited by
29 following the `%' with one of the modifiers described below. Unknown
30 field specifiers are copied as normal characters. All other
31 characters are copied to the output without change.
33 Supports a superset of the ANSI C field specifiers.
35 Literal character fields:
40 Numeric modifiers (a nonstandard extension):
41 - do not pad the field
42 _ pad the field with spaces
51 %r time, 12-hour (hh:mm:ss [AP]M)
52 %R time, 24-hour (hh:mm)
53 %s time in seconds since 00:00:00, Jan 1, 1970 (a nonstandard extension)
55 %T time, 24-hour (hh:mm:ss)
56 %X locale's time representation (%H:%M:%S)
57 %Z time zone (EDT), or nothing if no time zone is determinable
60 %a locale's abbreviated weekday name (Sun..Sat)
61 %A locale's full weekday name, variable length (Sunday..Saturday)
62 %b locale's abbreviated month name (Jan..Dec)
63 %B locale's full month name, variable length (January..December)
64 %c locale's date and time (Sat Nov 04 12:02:33 EST 1989)
66 %d day of month (01..31)
67 %e day of month ( 1..31)
70 %j day of year (001..366)
72 %U week number of year with Sunday as first day of week (00..53)
74 %W week number of year with Monday as first day of week (00..53)
75 %x locale's date representation (mm/dd/yy)
76 %y last two digits of year (00..99)
79 David MacKenzie <djm@gnu.ai.mit.edu> */
87 #include <sys/types.h>
88 #if defined(TM_IN_SYS_TIME) || (!defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME))
98 #if defined(HAVE_TZNAME)
99 extern char *tzname[2];
103 #define strftime emacs_strftime
106 /* Types of padding for numbers in date and time. */
111 static char const *const days[] = {
112 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday",
116 static char const *const months[] = {
117 "January", "February", "March", "April", "May", "June",
118 "July", "August", "September", "October", "November", "December"
121 /* Add character C to STRING and increment LENGTH,
122 unless LENGTH would exceed MAX. */
124 #define add_char(c) do \
126 if (length + 1 <= max) \
127 string[length++] = (c); \
130 /* Add a 2 digit number to STRING, padding if specified.
131 Return the number of characters added, up to MAX. */
133 static int add_num2(char *string, int num, int max, enum padding pad)
138 if (top == 0 && pad == blank)
140 else if (top != 0 || pad == zero)
142 add_char(num % 10 + '0');
146 /* Add a 3 digit number to STRING, padding if specified.
147 Return the number of characters added, up to MAX. */
149 static int add_num3(char *string, int num, int max, enum padding pad)
152 int mid = (num - top * 100) / 10;
155 if (top == 0 && pad == blank)
157 else if (top != 0 || pad == zero)
159 if (mid == 0 && top == 0 && pad == blank)
161 else if (mid != 0 || top != 0 || pad == zero)
163 add_char(num % 10 + '0');
167 /* Like strncpy except return the number of characters copied. */
169 static int add_str(char *to, const char *from, int max)
173 for (i = 0; from[i] && i <= max; ++i)
178 static int add_num_time_t(char *string, int max, time_t num)
180 /* This buffer is large enough to hold the character representation
181 (including the trailing NUL) of any unsigned decimal quantity
182 whose binary representation fits in 128 bits. */
186 if (sizeof(num) > 16)
188 length = snprintf(buf, sizeof(buf), "%lu", (unsigned long)num);
189 assert(length >= 0 && (size_t)length<sizeof(buf));
190 length = add_str(string, buf, max);
194 /* Return the week in the year of the time in TM, with the weeks
195 starting on Sundays. */
197 static int sun_week(const struct tm *tm)
201 /* Set `dl' to the day in the year of the last day of the week previous
202 to the one containing the day specified in TM. If the day specified
203 in TM is in the first week of the year, `dl' will be negative or 0.
204 Otherwise, calculate the number of complete weeks before our week
205 (dl / 7) and add any partial week at the start of the year (dl % 7). */
206 dl = tm->tm_yday - tm->tm_wday;
207 return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
210 /* Return the week in the year of the time in TM, with the weeks
211 starting on Mondays. */
213 static int mon_week(const struct tm *tm)
217 if (tm->tm_wday == 0)
220 wday = tm->tm_wday - 1;
221 dl = tm->tm_yday - wday;
222 return dl <= 0 ? 0 : dl / 7 + (dl % 7 != 0);
225 #if !defined(HAVE_TM_ZONE) && !defined(HAVE_TZNAME)
226 char *zone_name(const struct tm *tp);
227 char *zone_name(const struct tm *tp)
233 gettimeofday(&tv, &tz);
234 return timezone(tz.tz_minuteswest, tp->tm_isdst);
238 /* Format the time given in TM according to FORMAT, and put the
240 Return the number of characters (not including terminating null)
241 that were put into STRING, or 0 if the length would have
244 size_t strftime(char *string, size_t max, const char *format,
245 const struct tm * tm);
248 strftime(char *string, size_t max, const char *format, const struct tm *tm)
250 enum padding pad; /* Type of padding to apply. */
251 size_t length = 0; /* Characters put in STRING so far. */
253 for (; *format && length < max; ++format) {
259 if (*format == '-') {
262 } else if (*format == '_') {
269 /* Literal character fields: */
288 add_num2(&string[length], tm->tm_hour,
290 *format == 'H' ? pad : blank);
297 if (tm->tm_hour == 0)
299 else if (tm->tm_hour > 12)
300 hour12 = tm->tm_hour - 12;
302 hour12 = tm->tm_hour;
304 add_num2(&string[length], hour12,
312 add_num2(&string[length], tm->tm_min,
316 if (tm->tm_hour < 12)
324 strftime(&string[length], max - length,
329 strftime(&string[length], max - length,
335 struct tm writable_tm;
338 add_num_time_t(&string[length],
347 add_num2(&string[length], tm->tm_sec,
352 strftime(&string[length], max - length,
357 strftime(&string[length], max - length,
363 add_str(&string[length], tm->tm_zone,
367 if (tm->tm_isdst && tzname[1] && *tzname[1])
369 add_str(&string[length], tzname[1],
373 add_str(&string[length], tzname[0],
377 add_str(&string[length], zone_name(tm),
385 add_char(days[tm->tm_wday][0]);
386 add_char(days[tm->tm_wday][1]);
387 add_char(days[tm->tm_wday][2]);
391 add_str(&string[length], days[tm->tm_wday],
396 add_char(months[tm->tm_mon][0]);
397 add_char(months[tm->tm_mon][1]);
398 add_char(months[tm->tm_mon][2]);
402 add_str(&string[length], months[tm->tm_mon],
407 strftime(&string[length], max - length,
408 "%a %b %d %H:%M:%S %Z %Y", tm);
412 add_num2(&string[length],
413 (tm->tm_year + 1900) / 100,
418 add_num2(&string[length], tm->tm_mday,
423 add_num2(&string[length], tm->tm_mday,
424 max - length, blank);
428 strftime(&string[length], max - length,
433 add_num3(&string[length], tm->tm_yday + 1,
438 add_num2(&string[length], tm->tm_mon + 1,
443 add_num2(&string[length], sun_week(tm),
447 add_char(tm->tm_wday + '0');
451 add_num2(&string[length], mon_week(tm),
456 strftime(&string[length], max - length,
461 add_num2(&string[length], tm->tm_year % 100,
465 add_char((tm->tm_year + 1900) / 1000 + '0');
467 add_num3(&string[length],
468 (1900 + tm->tm_year) % 1000,