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