7
7
8
8
/*
9
9
** Avoid the temptation to punt entirely to strftime;
10
+ ** strftime can behave badly when tm components are out of range, and
10
11
** the output of strftime is supposed to be locale specific
11
12
** whereas the output of asctime is supposed to be constant.
12
13
*/
16
17
#include "private.h"
17
18
#include <stdio.h>
18
19
19
- /*
20
- ** All years associated with 32-bit time_t values are exactly four digits long;
21
- ** some years associated with 64-bit time_t values are not.
22
- ** Vintage programs are coded for years that are always four digits long
23
- ** and may assume that the newline always lands in the same place.
24
- ** For years that are less than four digits, we pad the output with
25
- ** leading zeroes to get the newline in the traditional place.
26
- ** The -4 ensures that we get four characters of output even if
27
- ** we call a strftime variant that produces fewer characters for some years.
28
- ** This conforms to recent ISO C and POSIX standards, which say behavior
29
- ** is undefined when the year is less than 1000 or greater than 9999.
30
- */
31
- static char const ASCTIME_FMT [] = "%s %s%3d %.2d:%.2d:%.2d %-4s\n" ;
32
- /*
33
- ** For years that are more than four digits we put extra spaces before the year
34
- ** so that code trying to overwrite the newline won't end up overwriting
35
- ** a digit within a year and truncating the year (operating on the assumption
36
- ** that no output is better than wrong output).
37
- */
38
- static char const ASCTIME_FMT_B [] = "%s %s%3d %.2d:%.2d:%.2d %s\n" ;
39
-
40
20
enum { STD_ASCTIME_BUF_SIZE = 26 };
41
21
/*
42
22
** Big enough for something such as
@@ -50,14 +30,24 @@ enum { STD_ASCTIME_BUF_SIZE = 26 };
50
30
*/
51
31
static char buf_asctime [2 * 3 + 5 * INT_STRLEN_MAXIMUM (int ) + 7 + 2 + 1 + 1 ];
52
32
53
- /* A similar buffer for ctime.
54
- C89 requires that they be the same buffer.
55
- This requirement was removed in C99, so support it only if requested,
56
- as support is more likely to lead to bugs in badly written programs. */
57
- #if SUPPORT_C89
58
- # define buf_ctime buf_asctime
59
- #else
60
- static char buf_ctime [sizeof buf_asctime ];
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
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
61
51
#endif
62
52
63
53
/* Publish asctime_r and ctime_r only when supporting older POSIX. */
@@ -84,38 +74,59 @@ asctime_r(struct tm const *restrict timeptr, char *restrict buf)
84
74
};
85
75
register const char * wn ;
86
76
register const char * mn ;
87
- char year [INT_STRLEN_MAXIMUM (int ) + 2 ];
88
- char result [sizeof buf_asctime ];
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 );
89
81
90
82
if (timeptr == NULL ) {
83
+ strcpy (buf , "??? ??? ?? ??:??:?? ????\n" );
84
+ /* Set errno now, since strcpy might change it in
85
+ POSIX.1-2017 and earlier. */
91
86
errno = EINVAL ;
92
- return strcpy ( buf , "??? ??? ?? ??:??:?? ????\n" ) ;
87
+ return buf ;
93
88
}
94
89
if (timeptr -> tm_wday < 0 || timeptr -> tm_wday >= DAYSPERWEEK )
95
90
wn = "???" ;
96
91
else wn = wday_name [timeptr -> tm_wday ];
97
92
if (timeptr -> tm_mon < 0 || timeptr -> tm_mon >= MONSPERYEAR )
98
93
mn = "???" ;
99
94
else mn = mon_name [timeptr -> tm_mon ];
100
- /*
101
- ** Use strftime's %Y to generate the year, to avoid overflow problems
102
- ** when computing timeptr->tm_year + TM_YEAR_BASE.
103
- ** Assume that strftime is unaffected by other out-of-range members
104
- ** (e.g., timeptr->tm_mday) when processing "%Y".
105
- */
106
- strftime (year , sizeof year , "%Y" , timeptr );
107
- /*
108
- ** We avoid using snprintf since it's not available on all systems.
109
- */
110
- sprintf (result ,
111
- ((strlen (year ) <= 4 ) ? ASCTIME_FMT : ASCTIME_FMT_B ),
112
- wn , mn ,
113
- timeptr -> tm_mday , timeptr -> tm_hour ,
114
- timeptr -> tm_min , timeptr -> tm_sec ,
115
- year );
116
- if (strlen (result ) < STD_ASCTIME_BUF_SIZE
117
- || buf == buf_ctime || buf == buf_asctime )
118
- return strcpy (buf , result );
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 ;
119
130
else {
120
131
errno = EOVERFLOW ;
121
132
return NULL ;
@@ -140,5 +151,8 @@ ctime_r(const time_t *timep, char *buf)
140
151
char *
141
152
ctime (const time_t * timep )
142
153
{
143
- return ctime_r (timep , buf_ctime );
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 ;
144
158
}
0 commit comments