1 /* asctime a la ISO C. */
2
3 /*
4 ** This file is in the public domain, so clarified as of
5 ** 1996-06-05 by Arthur David Olson.
6 */
7
8 /*
9 ** Avoid the temptation to punt entirely to strftime;
10 ** strftime can behave badly when tm components are out of range, and
11 ** the output of strftime is supposed to be locale specific
12 ** whereas the output of asctime is supposed to be constant.
13 */
14
15 /*LINTLIBRARY*/
16
17 #include "private.h"
18 #include <stdio.h>
19
20 enum { STD_ASCTIME_BUF_SIZE = 26 };
21 /*
22 ** Big enough for something such as
23 ** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n
24 ** (two three-character abbreviations, five strings denoting integers,
25 ** seven explicit spaces, two explicit colons, a newline,
26 ** and a trailing NUL byte).
27 ** The values above are for systems where an int is 32 bits and are provided
28 ** as an example; the size expression below is a bound for the system at
29 ** hand.
30 */
31 static char buf_asctime[2*3 + 5*INT_STRLEN_MAXIMUM(int) + 7 + 2 + 1 + 1];
32
33 /* On pre-C99 platforms, a snprintf substitute good enough for us. */
34 #if !HAVE_SNPRINTF
35 # include <stdarg.h>
36 ATTRIBUTE_FORMAT((printf, 3, 4)) static int
my_snprintf(char * s,size_t size,char const * format,...)37 my_snprintf(char *s, size_t size, char const *format, ...)
38 {
39 int n;
40 va_list args;
41 char stackbuf[sizeof buf_asctime];
42 va_start(args, format);
43 n = vsprintf(stackbuf, format, args);
44 va_end (args);
45 if (0 <= n && n < size)
46 memcpy (s, stackbuf, n + 1);
47 return n;
48 }
49 # undef snprintf
50 # define snprintf my_snprintf
51 #endif
52
53 /* Publish asctime_r and ctime_r only when supporting older POSIX. */
54 #if SUPPORT_POSIX2008
55 # define asctime_static
56 #else
57 # define asctime_static static
58 # undef asctime_r
59 # undef ctime_r
60 # define asctime_r static_asctime_r
61 # define ctime_r static_ctime_r
62 #endif
63
64 asctime_static
65 char *
asctime_r(struct tm const * restrict timeptr,char * restrict buf)66 asctime_r(struct tm const *restrict timeptr, char *restrict buf)
67 {
68 static const char wday_name[][4] = {
69 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
70 };
71 static const char mon_name[][4] = {
72 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
73 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
74 };
75 register const char * wn;
76 register const char * mn;
77 int year, mday, hour, min, sec;
78 long long_TM_YEAR_BASE = TM_YEAR_BASE;
79 size_t bufsize = (buf == buf_asctime
80 ? sizeof buf_asctime : STD_ASCTIME_BUF_SIZE);
81
82 if (timeptr == NULL) {
83 strcpy(buf, "??? ??? ?? ??:??:?? ????\n");
84 /* Set errno now, since strcpy might change it in
85 POSIX.1-2017 and earlier. */
86 errno = EINVAL;
87 return buf;
88 }
89 if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK)
90 wn = "???";
91 else wn = wday_name[timeptr->tm_wday];
92 if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR)
93 mn = "???";
94 else mn = mon_name[timeptr->tm_mon];
95
96 year = timeptr->tm_year;
97 mday = timeptr->tm_mday;
98 hour = timeptr->tm_hour;
99 min = timeptr->tm_min;
100 sec = timeptr->tm_sec;
101
102 /* Vintage programs are coded for years that are always four bytes long
103 and may assume that the newline always lands in the same place.
104 For years that are less than four bytes, pad the output with
105 leading zeroes to get the newline in the traditional place.
106 For years longer than four bytes, put extra spaces before the year
107 so that vintage code trying to overwrite the newline
108 won't overwrite a digit within a year and truncate the year,
109 using the principle that no output is better than wrong output.
110 This conforms to ISO C and POSIX standards, which say behavior
111 is undefined when the year is less than 1000 or greater than 9999.
112
113 Also, avoid overflow when formatting tm_year + TM_YEAR_BASE. */
114
115 if ((year <= LONG_MAX - TM_YEAR_BASE
116 ? snprintf (buf, bufsize,
117 ((-999 - TM_YEAR_BASE <= year
118 && year <= 9999 - TM_YEAR_BASE)
119 ? "%s %s%3d %.2d:%.2d:%.2d %04ld\n"
120 : "%s %s%3d %.2d:%.2d:%.2d %ld\n"),
121 wn, mn, mday, hour, min, sec,
122 year + long_TM_YEAR_BASE)
123 : snprintf (buf, bufsize,
124 "%s %s%3d %.2d:%.2d:%.2d %d%d\n",
125 wn, mn, mday, hour, min, sec,
126 year / 10 + TM_YEAR_BASE / 10,
127 year % 10))
128 < bufsize)
129 return buf;
130 else {
131 errno = EOVERFLOW;
132 return NULL;
133 }
134 }
135
136 char *
asctime(register const struct tm * timeptr)137 asctime(register const struct tm *timeptr)
138 {
139 return asctime_r(timeptr, buf_asctime);
140 }
141
142 asctime_static
143 char *
ctime_r(const time_t * timep,char * buf)144 ctime_r(const time_t *timep, char *buf)
145 {
146 struct tm mytm;
147 struct tm *tmp = localtime_r(timep, &mytm);
148 return tmp ? asctime_r(tmp, buf) : NULL;
149 }
150
151 char *
ctime(const time_t * timep)152 ctime(const time_t *timep)
153 {
154 /* Do not call localtime_r, as C23 requires ctime to initialize the
155 static storage that localtime updates. */
156 struct tm *tmp = localtime(timep);
157 return tmp ? asctime(tmp) : NULL;
158 }
159