xref: /netbsd-src/lib/libc/time/asctime.c (revision ccd9df534e375a4366c5b55f23782053c7a98d82)
1 /*	$NetBSD: asctime.c,v 1.30 2024/01/20 14:52:49 christos Exp $	*/
2 
3 /* asctime and asctime_r a la POSIX and ISO C, except pad years before 1000.  */
4 
5 /*
6 ** This file is in the public domain, so clarified as of
7 ** 1996-06-05 by Arthur David Olson.
8 */
9 
10 /*
11 ** Avoid the temptation to punt entirely to strftime;
12 ** the output of strftime is supposed to be locale specific
13 ** whereas the output of asctime is supposed to be constant.
14 */
15 
16 #include <sys/cdefs.h>
17 #if defined(LIBC_SCCS) && !defined(lint)
18 #if 0
19 static char	elsieid[] = "@(#)asctime.c	8.5";
20 #else
21 __RCSID("$NetBSD: asctime.c,v 1.30 2024/01/20 14:52:49 christos Exp $");
22 #endif
23 #endif /* LIBC_SCCS and not lint */
24 
25 /*LINTLIBRARY*/
26 
27 #include "namespace.h"
28 #include "private.h"
29 #include <stdio.h>
30 
31 #ifndef __LIBC12_SOURCE__
32 
33 #ifdef __weak_alias
34 __weak_alias(asctime_r,_asctime_r)
35 #endif
36 
37 /*
38 ** All years associated with 32-bit time_t values are exactly four digits long;
39 ** some years associated with 64-bit time_t values are not.
40 ** Vintage programs are coded for years that are always four digits long
41 ** and may assume that the newline always lands in the same place.
42 ** For years that are less than four digits, we pad the output with
43 ** leading zeroes to get the newline in the traditional place.
44 ** The -4 ensures that we get four characters of output even if
45 ** we call a strftime variant that produces fewer characters for some years.
46 ** The ISO C and POSIX standards prohibit padding the year,
47 ** but many implementations pad anyway; most likely the standards are buggy.
48 */
49 static char const ASCTIME_FMT[] = "%s %s%3d %.2d:%.2d:%.2d %-4s\n";
50 /*
51 ** For years that are more than four digits we put extra spaces before the year
52 ** so that code trying to overwrite the newline won't end up overwriting
53 ** a digit within a year and truncating the year (operating on the assumption
54 ** that no output is better than wrong output).
55 */
56 static char const ASCTIME_FMT_B[] = "%s %s%3d %.2d:%.2d:%.2d     %s\n";
57 
58 enum { STD_ASCTIME_BUF_SIZE = 26 };
59 #endif
60 
61 /*
62 ** Big enough for something such as
63 ** ??? ???-2147483648 -2147483648:-2147483648:-2147483648     -2147483648\n
64 ** (two three-character abbreviations, five strings denoting integers,
65 ** seven explicit spaces, two explicit colons, a newline,
66 ** and a trailing NUL byte).
67 ** The values above are for systems where an int is 32 bits and are provided
68 ** as an example; the size expression below is a bound for the system at
69 ** hand.
70 */
71 static char buf_ctime[2*3 + 5*INT_STRLEN_MAXIMUM(int) + 7 + 2 + 1 + 1];
72 
73 /* A similar buffer for ctime.
74    C89 requires that they be the same buffer.
75    This requirement was removed in C99, so support it only if requested,
76    as support is more likely to lead to bugs in badly written programs.  */
77 #ifndef __LIBC12_SOURCE__
78 #if SUPPORT_C89
79 # define buf_asctime buf_ctime
80 #else
81 static char buf_asctime[sizeof buf_ctime];
82 #endif
83 
84 char *
85 asctime_r(struct tm const *restrict timeptr, char *restrict buf)
86 {
87 	static const char	wday_name[][4] = {
88 		"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
89 	};
90 	static const char	mon_name[][4] = {
91 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
92 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
93 	};
94 	const char *	wn;
95 	const char *	mn;
96 	char			year[INT_STRLEN_MAXIMUM(int) + 2];
97 	char result[sizeof buf_ctime];
98 
99 	if (timeptr == NULL) {
100 		errno = EINVAL;
101 		return strcpy(buf, "??? ??? ?? ??:??:?? ????\n");
102 	}
103 	if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK)
104 		wn = "???";
105 	else	wn = wday_name[timeptr->tm_wday];
106 	if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR)
107 		mn = "???";
108 	else	mn = mon_name[timeptr->tm_mon];
109 	/*
110 	** Use strftime's %Y to generate the year, to avoid overflow problems
111 	** when computing timeptr->tm_year + TM_YEAR_BASE.
112 	** Assume that strftime is unaffected by other out-of-range members
113 	** (e.g., timeptr->tm_mday) when processing "%Y".
114 	*/
115 	(void) strftime(year, sizeof year, "%Y", timeptr);
116 	(void) snprintf(result,
117 		sizeof(result),
118 		((strlen(year) <= 4) ? ASCTIME_FMT : ASCTIME_FMT_B),
119 		wn, mn,
120 		timeptr->tm_mday, timeptr->tm_hour,
121 		timeptr->tm_min, timeptr->tm_sec,
122 		year);
123 	if (strlen(result) < STD_ASCTIME_BUF_SIZE
124 	    || buf == buf_ctime || buf == buf_asctime)
125 		return strcpy(buf, result);
126 	else {
127 		errno = EOVERFLOW;
128 		return NULL;
129 	}
130 }
131 
132 char *
133 asctime(const struct tm *timeptr)
134 {
135 	return asctime_r(timeptr, buf_asctime);
136 }
137 
138 #endif /* !__LIBC12_SOURCE__ */
139 
140 char *
141 ctime_rz(timezone_t sp, const time_t *timep, char *buf)
142 {
143   struct tm mytm;
144   struct tm *tmp = localtime_rz(sp, timep, &mytm);
145   return tmp ? asctime_r(tmp, buf) : NULL;
146 }
147 
148 char *
149 ctime_r(const time_t *timep, char *buf)
150 {
151   struct tm mytm;
152   struct tm *tmp = localtime_r(timep, &mytm);
153   return tmp ? asctime_r(tmp, buf) : NULL;
154 }
155 
156 char *
157 ctime(const time_t *timep)
158 {
159   return ctime_r(timep, buf_ctime);
160 }
161