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