1*a37af58cSmillert /* $OpenBSD: strftime.c,v 1.33 2020/07/16 20:08:12 millert Exp $ */
2efdf7294Smillert /*
35e4f8c55Smillert ** Copyright (c) 1989, 1993
45e4f8c55Smillert ** The Regents of the University of California. All rights reserved.
5efdf7294Smillert **
65e4f8c55Smillert ** Redistribution and use in source and binary forms, with or without
75e4f8c55Smillert ** modification, are permitted provided that the following conditions
85e4f8c55Smillert ** are met:
95e4f8c55Smillert ** 1. Redistributions of source code must retain the above copyright
105e4f8c55Smillert ** notice, this list of conditions and the following disclaimer.
115e4f8c55Smillert ** 2. Redistributions in binary form must reproduce the above copyright
125e4f8c55Smillert ** notice, this list of conditions and the following disclaimer in the
135e4f8c55Smillert ** documentation and/or other materials provided with the distribution.
14bed2b053Sderaadt ** 3. Neither the name of the University nor the names of its contributors
155e4f8c55Smillert ** may be used to endorse or promote products derived from this software
165e4f8c55Smillert ** without specific prior written permission.
175e4f8c55Smillert **
185e4f8c55Smillert ** THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
195e4f8c55Smillert ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
205e4f8c55Smillert ** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
215e4f8c55Smillert ** ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
225e4f8c55Smillert ** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
235e4f8c55Smillert ** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
245e4f8c55Smillert ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
255e4f8c55Smillert ** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
265e4f8c55Smillert ** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
275e4f8c55Smillert ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
285e4f8c55Smillert ** SUCH DAMAGE.
29efdf7294Smillert */
30efdf7294Smillert
31a9cc4792Stedu #include <fcntl.h>
32dbd4bd29Sguenther #include <stdio.h>
33a9cc4792Stedu
34a9cc4792Stedu #include "private.h"
35efdf7294Smillert #include "tzfile.h"
36efdf7294Smillert
37efdf7294Smillert struct lc_time_T {
38efdf7294Smillert const char * mon[MONSPERYEAR];
39efdf7294Smillert const char * month[MONSPERYEAR];
40efdf7294Smillert const char * wday[DAYSPERWEEK];
41efdf7294Smillert const char * weekday[DAYSPERWEEK];
42efdf7294Smillert const char * X_fmt;
43efdf7294Smillert const char * x_fmt;
44efdf7294Smillert const char * c_fmt;
45efdf7294Smillert const char * am;
46efdf7294Smillert const char * pm;
47efdf7294Smillert const char * date_fmt;
48efdf7294Smillert };
49efdf7294Smillert
50efdf7294Smillert #define Locale (&C_time_locale)
51efdf7294Smillert static const struct lc_time_T C_time_locale = {
52efdf7294Smillert {
53efdf7294Smillert "Jan", "Feb", "Mar", "Apr", "May", "Jun",
54efdf7294Smillert "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
55efdf7294Smillert }, {
56efdf7294Smillert "January", "February", "March", "April", "May", "June",
57efdf7294Smillert "July", "August", "September", "October", "November", "December"
58efdf7294Smillert }, {
59efdf7294Smillert "Sun", "Mon", "Tue", "Wed",
60efdf7294Smillert "Thu", "Fri", "Sat"
61efdf7294Smillert }, {
62efdf7294Smillert "Sunday", "Monday", "Tuesday", "Wednesday",
63efdf7294Smillert "Thursday", "Friday", "Saturday"
64efdf7294Smillert },
65efdf7294Smillert
66efdf7294Smillert /* X_fmt */
67efdf7294Smillert "%H:%M:%S",
68efdf7294Smillert
69efdf7294Smillert /*
70efdf7294Smillert ** x_fmt
71169f132fSd ** C99 requires this format.
72efdf7294Smillert ** Using just numbers (as here) makes Quakers happier;
73efdf7294Smillert ** it's also compatible with SVR4.
74efdf7294Smillert */
75efdf7294Smillert "%m/%d/%y",
76efdf7294Smillert
77efdf7294Smillert /*
78efdf7294Smillert ** c_fmt
79169f132fSd ** C99 requires this format.
80169f132fSd ** Previously this code used "%D %X", but we now conform to C99.
81169f132fSd ** Note that
82169f132fSd ** "%a %b %d %H:%M:%S %Y"
83169f132fSd ** is used by Solaris 2.3.
84efdf7294Smillert */
85169f132fSd "%a %b %e %T %Y",
86efdf7294Smillert
87efdf7294Smillert /* am */
88efdf7294Smillert "AM",
89efdf7294Smillert
90efdf7294Smillert /* pm */
91efdf7294Smillert "PM",
92efdf7294Smillert
93efdf7294Smillert /* date_fmt */
94efdf7294Smillert "%a %b %e %H:%M:%S %Z %Y"
95efdf7294Smillert };
96efdf7294Smillert
97f896db16Smillert static char * _add(const char *, char *, const char *);
98f896db16Smillert static char * _conv(int, const char *, char *, const char *);
99f896db16Smillert static char * _fmt(const char *, const struct tm *, char *, const char *,
100f896db16Smillert int *);
101f896db16Smillert static char * _yconv(int, int, int, int, char *, const char *);
102efdf7294Smillert
103efdf7294Smillert extern char * tzname[];
104efdf7294Smillert
105efdf7294Smillert #define IN_NONE 0
106efdf7294Smillert #define IN_SOME 1
107efdf7294Smillert #define IN_THIS 2
108efdf7294Smillert #define IN_ALL 3
109efdf7294Smillert
110efdf7294Smillert size_t
strftime(char * s,size_t maxsize,const char * format,const struct tm * t)1118dc2b201Stedu strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
112efdf7294Smillert {
113efdf7294Smillert char * p;
114efdf7294Smillert int warn;
115efdf7294Smillert
116efdf7294Smillert tzset();
117efdf7294Smillert warn = IN_NONE;
118efdf7294Smillert p = _fmt(((format == NULL) ? "%c" : format), t, s, s + maxsize, &warn);
11922bc1b62Smillert if (p == s + maxsize) {
120d6bae9b0Smillert if (maxsize > 0)
12122bc1b62Smillert s[maxsize - 1] = '\0';
122efdf7294Smillert return 0;
12322bc1b62Smillert }
124efdf7294Smillert *p = '\0';
125efdf7294Smillert return p - s;
126efdf7294Smillert }
127d17652bcSguenther DEF_STRONG(strftime);
128efdf7294Smillert
129efdf7294Smillert static char *
_fmt(const char * format,const struct tm * t,char * pt,const char * ptlim,int * warnp)1308dc2b201Stedu _fmt(const char *format, const struct tm *t, char *pt, const char *ptlim, int *warnp)
131efdf7294Smillert {
132efdf7294Smillert for ( ; *format; ++format) {
133efdf7294Smillert if (*format == '%') {
134efdf7294Smillert label:
135efdf7294Smillert switch (*++format) {
136efdf7294Smillert case '\0':
137efdf7294Smillert --format;
138efdf7294Smillert break;
139efdf7294Smillert case 'A':
140efdf7294Smillert pt = _add((t->tm_wday < 0 ||
141efdf7294Smillert t->tm_wday >= DAYSPERWEEK) ?
142efdf7294Smillert "?" : Locale->weekday[t->tm_wday],
143efdf7294Smillert pt, ptlim);
144efdf7294Smillert continue;
145efdf7294Smillert case 'a':
146efdf7294Smillert pt = _add((t->tm_wday < 0 ||
147efdf7294Smillert t->tm_wday >= DAYSPERWEEK) ?
148efdf7294Smillert "?" : Locale->wday[t->tm_wday],
149efdf7294Smillert pt, ptlim);
150efdf7294Smillert continue;
151efdf7294Smillert case 'B':
152efdf7294Smillert pt = _add((t->tm_mon < 0 ||
153efdf7294Smillert t->tm_mon >= MONSPERYEAR) ?
154efdf7294Smillert "?" : Locale->month[t->tm_mon],
155efdf7294Smillert pt, ptlim);
156efdf7294Smillert continue;
157efdf7294Smillert case 'b':
158efdf7294Smillert case 'h':
159efdf7294Smillert pt = _add((t->tm_mon < 0 ||
160efdf7294Smillert t->tm_mon >= MONSPERYEAR) ?
161efdf7294Smillert "?" : Locale->mon[t->tm_mon],
162efdf7294Smillert pt, ptlim);
163efdf7294Smillert continue;
164efdf7294Smillert case 'C':
165efdf7294Smillert /*
166efdf7294Smillert ** %C used to do a...
167efdf7294Smillert ** _fmt("%a %b %e %X %Y", t);
168efdf7294Smillert ** ...whereas now POSIX 1003.2 calls for
169efdf7294Smillert ** something completely different.
170efdf7294Smillert ** (ado, 1993-05-24)
171efdf7294Smillert */
1723f56448eSmillert pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0,
1733f56448eSmillert pt, ptlim);
174efdf7294Smillert continue;
175efdf7294Smillert case 'c':
176efdf7294Smillert {
177efdf7294Smillert int warn2 = IN_SOME;
178efdf7294Smillert
1790cd7f77fSmillert pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
180efdf7294Smillert if (warn2 == IN_ALL)
181efdf7294Smillert warn2 = IN_THIS;
182efdf7294Smillert if (warn2 > *warnp)
183efdf7294Smillert *warnp = warn2;
184efdf7294Smillert }
185efdf7294Smillert continue;
186efdf7294Smillert case 'D':
187efdf7294Smillert pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
188efdf7294Smillert continue;
189efdf7294Smillert case 'd':
190efdf7294Smillert pt = _conv(t->tm_mday, "%02d", pt, ptlim);
191efdf7294Smillert continue;
192efdf7294Smillert case 'E':
193efdf7294Smillert case 'O':
194efdf7294Smillert /*
195ef12f6f8Smillert ** C99 locale modifiers.
196efdf7294Smillert ** The sequences
197ef12f6f8Smillert ** %Ec %EC %Ex %EX %Ey %EY
198efdf7294Smillert ** %Od %oe %OH %OI %Om %OM
199efdf7294Smillert ** %OS %Ou %OU %OV %Ow %OW %Oy
200efdf7294Smillert ** are supposed to provide alternate
201efdf7294Smillert ** representations.
202efdf7294Smillert */
203efdf7294Smillert goto label;
204efdf7294Smillert case 'e':
205efdf7294Smillert pt = _conv(t->tm_mday, "%2d", pt, ptlim);
206efdf7294Smillert continue;
207ef12f6f8Smillert case 'F':
208ef12f6f8Smillert pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
209ef12f6f8Smillert continue;
210efdf7294Smillert case 'H':
211efdf7294Smillert pt = _conv(t->tm_hour, "%02d", pt, ptlim);
212efdf7294Smillert continue;
213efdf7294Smillert case 'I':
214efdf7294Smillert pt = _conv((t->tm_hour % 12) ?
215efdf7294Smillert (t->tm_hour % 12) : 12,
216efdf7294Smillert "%02d", pt, ptlim);
217efdf7294Smillert continue;
218efdf7294Smillert case 'j':
219efdf7294Smillert pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
220efdf7294Smillert continue;
221efdf7294Smillert case 'k':
222efdf7294Smillert /*
223efdf7294Smillert ** This used to be...
224efdf7294Smillert ** _conv(t->tm_hour % 12 ?
225efdf7294Smillert ** t->tm_hour % 12 : 12, 2, ' ');
226efdf7294Smillert ** ...and has been changed to the below to
227efdf7294Smillert ** match SunOS 4.1.1 and Arnold Robbins'
228efdf7294Smillert ** strftime version 3.0. That is, "%k" and
229efdf7294Smillert ** "%l" have been swapped.
230efdf7294Smillert ** (ado, 1993-05-24)
231efdf7294Smillert */
232efdf7294Smillert pt = _conv(t->tm_hour, "%2d", pt, ptlim);
233efdf7294Smillert continue;
234efdf7294Smillert #ifdef KITCHEN_SINK
235efdf7294Smillert case 'K':
236efdf7294Smillert /*
237efdf7294Smillert ** After all this time, still unclaimed!
238efdf7294Smillert */
239efdf7294Smillert pt = _add("kitchen sink", pt, ptlim);
240efdf7294Smillert continue;
241efdf7294Smillert #endif /* defined KITCHEN_SINK */
242efdf7294Smillert case 'l':
243efdf7294Smillert /*
244efdf7294Smillert ** This used to be...
245efdf7294Smillert ** _conv(t->tm_hour, 2, ' ');
246efdf7294Smillert ** ...and has been changed to the below to
247efdf7294Smillert ** match SunOS 4.1.1 and Arnold Robbin's
248efdf7294Smillert ** strftime version 3.0. That is, "%k" and
249efdf7294Smillert ** "%l" have been swapped.
250efdf7294Smillert ** (ado, 1993-05-24)
251efdf7294Smillert */
252efdf7294Smillert pt = _conv((t->tm_hour % 12) ?
253efdf7294Smillert (t->tm_hour % 12) : 12,
254efdf7294Smillert "%2d", pt, ptlim);
255efdf7294Smillert continue;
256efdf7294Smillert case 'M':
257efdf7294Smillert pt = _conv(t->tm_min, "%02d", pt, ptlim);
258efdf7294Smillert continue;
259efdf7294Smillert case 'm':
260efdf7294Smillert pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
261efdf7294Smillert continue;
262efdf7294Smillert case 'n':
263efdf7294Smillert pt = _add("\n", pt, ptlim);
264efdf7294Smillert continue;
265efdf7294Smillert case 'p':
266efdf7294Smillert pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
267efdf7294Smillert Locale->pm :
268efdf7294Smillert Locale->am,
269efdf7294Smillert pt, ptlim);
270efdf7294Smillert continue;
271efdf7294Smillert case 'R':
272efdf7294Smillert pt = _fmt("%H:%M", t, pt, ptlim, warnp);
273efdf7294Smillert continue;
274efdf7294Smillert case 'r':
275efdf7294Smillert pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
276efdf7294Smillert continue;
277efdf7294Smillert case 'S':
278efdf7294Smillert pt = _conv(t->tm_sec, "%02d", pt, ptlim);
279efdf7294Smillert continue;
280efdf7294Smillert case 's':
281efdf7294Smillert {
282efdf7294Smillert struct tm tm;
283efdf7294Smillert char buf[INT_STRLEN_MAXIMUM(
284efdf7294Smillert time_t) + 1];
285efdf7294Smillert time_t mkt;
286efdf7294Smillert
287efdf7294Smillert tm = *t;
288efdf7294Smillert mkt = mktime(&tm);
289c364cbbdSderaadt (void) snprintf(buf, sizeof buf,
290c364cbbdSderaadt "%ld", (long) mkt);
291efdf7294Smillert pt = _add(buf, pt, ptlim);
292efdf7294Smillert }
293efdf7294Smillert continue;
294efdf7294Smillert case 'T':
295efdf7294Smillert pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
296efdf7294Smillert continue;
297efdf7294Smillert case 't':
298efdf7294Smillert pt = _add("\t", pt, ptlim);
299efdf7294Smillert continue;
300efdf7294Smillert case 'U':
301efdf7294Smillert pt = _conv((t->tm_yday + DAYSPERWEEK -
302efdf7294Smillert t->tm_wday) / DAYSPERWEEK,
303efdf7294Smillert "%02d", pt, ptlim);
304efdf7294Smillert continue;
305efdf7294Smillert case 'u':
306efdf7294Smillert /*
307efdf7294Smillert ** From Arnold Robbins' strftime version 3.0:
308efdf7294Smillert ** "ISO 8601: Weekday as a decimal number
309efdf7294Smillert ** [1 (Monday) - 7]"
310efdf7294Smillert ** (ado, 1993-05-24)
311efdf7294Smillert */
312efdf7294Smillert pt = _conv((t->tm_wday == 0) ?
313efdf7294Smillert DAYSPERWEEK : t->tm_wday,
314efdf7294Smillert "%d", pt, ptlim);
315efdf7294Smillert continue;
316efdf7294Smillert case 'V': /* ISO 8601 week number */
317efdf7294Smillert case 'G': /* ISO 8601 year (four digits) */
318efdf7294Smillert case 'g': /* ISO 8601 year (two digits) */
319efdf7294Smillert /*
320efdf7294Smillert ** From Arnold Robbins' strftime version 3.0: "the week number of the
321efdf7294Smillert ** year (the first Monday as the first day of week 1) as a decimal number
322efdf7294Smillert ** (01-53)."
323efdf7294Smillert ** (ado, 1993-05-24)
324efdf7294Smillert **
325efdf7294Smillert ** From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn:
326efdf7294Smillert ** "Week 01 of a year is per definition the first week which has the
327efdf7294Smillert ** Thursday in this year, which is equivalent to the week which contains
328efdf7294Smillert ** the fourth day of January. In other words, the first week of a new year
329efdf7294Smillert ** is the week which has the majority of its days in the new year. Week 01
330efdf7294Smillert ** might also contain days from the previous year and the week before week
331efdf7294Smillert ** 01 of a year is the last week (52 or 53) of the previous year even if
332efdf7294Smillert ** it contains days from the new year. A week starts with Monday (day 1)
333efdf7294Smillert ** and ends with Sunday (day 7). For example, the first week of the year
334efdf7294Smillert ** 1997 lasts from 1996-12-30 to 1997-01-05..."
335efdf7294Smillert ** (ado, 1996-01-02)
336efdf7294Smillert */
337efdf7294Smillert {
3383f56448eSmillert int year;
3393f56448eSmillert int base;
340efdf7294Smillert int yday;
341efdf7294Smillert int wday;
342efdf7294Smillert int w;
343efdf7294Smillert
344dfd58e64Smillert year = t->tm_year;
3453f56448eSmillert base = TM_YEAR_BASE;
346efdf7294Smillert yday = t->tm_yday;
347efdf7294Smillert wday = t->tm_wday;
348efdf7294Smillert for ( ; ; ) {
349efdf7294Smillert int len;
350efdf7294Smillert int bot;
351efdf7294Smillert int top;
352efdf7294Smillert
3533f56448eSmillert len = isleap_sum(year, base) ?
354efdf7294Smillert DAYSPERLYEAR :
355efdf7294Smillert DAYSPERNYEAR;
356efdf7294Smillert /*
357efdf7294Smillert ** What yday (-3 ... 3) does
358efdf7294Smillert ** the ISO year begin on?
359efdf7294Smillert */
360efdf7294Smillert bot = ((yday + 11 - wday) %
361efdf7294Smillert DAYSPERWEEK) - 3;
362efdf7294Smillert /*
363efdf7294Smillert ** What yday does the NEXT
364efdf7294Smillert ** ISO year begin on?
365efdf7294Smillert */
366efdf7294Smillert top = bot -
367efdf7294Smillert (len % DAYSPERWEEK);
368efdf7294Smillert if (top < -3)
369efdf7294Smillert top += DAYSPERWEEK;
370efdf7294Smillert top += len;
371efdf7294Smillert if (yday >= top) {
3723f56448eSmillert ++base;
373efdf7294Smillert w = 1;
374efdf7294Smillert break;
375efdf7294Smillert }
376efdf7294Smillert if (yday >= bot) {
377efdf7294Smillert w = 1 + ((yday - bot) /
378efdf7294Smillert DAYSPERWEEK);
379efdf7294Smillert break;
380efdf7294Smillert }
3813f56448eSmillert --base;
3823f56448eSmillert yday += isleap_sum(year, base) ?
383efdf7294Smillert DAYSPERLYEAR :
384efdf7294Smillert DAYSPERNYEAR;
385efdf7294Smillert }
386efdf7294Smillert #ifdef XPG4_1994_04_09
387dfd58e64Smillert if ((w == 52 &&
388dfd58e64Smillert t->tm_mon == TM_JANUARY) ||
389dfd58e64Smillert (w == 1 &&
390dfd58e64Smillert t->tm_mon == TM_DECEMBER))
391efdf7294Smillert w = 53;
392efdf7294Smillert #endif /* defined XPG4_1994_04_09 */
393efdf7294Smillert if (*format == 'V')
394efdf7294Smillert pt = _conv(w, "%02d",
395efdf7294Smillert pt, ptlim);
396efdf7294Smillert else if (*format == 'g') {
397efdf7294Smillert *warnp = IN_ALL;
3983f56448eSmillert pt = _yconv(year, base, 0, 1,
3993f56448eSmillert pt, ptlim);
4003f56448eSmillert } else pt = _yconv(year, base, 1, 1,
401efdf7294Smillert pt, ptlim);
402efdf7294Smillert }
403efdf7294Smillert continue;
404efdf7294Smillert case 'v':
405efdf7294Smillert /*
406efdf7294Smillert ** From Arnold Robbins' strftime version 3.0:
407efdf7294Smillert ** "date as dd-bbb-YYYY"
408efdf7294Smillert ** (ado, 1993-05-24)
409efdf7294Smillert */
410efdf7294Smillert pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
411efdf7294Smillert continue;
412efdf7294Smillert case 'W':
413efdf7294Smillert pt = _conv((t->tm_yday + DAYSPERWEEK -
414efdf7294Smillert (t->tm_wday ?
415efdf7294Smillert (t->tm_wday - 1) :
416efdf7294Smillert (DAYSPERWEEK - 1))) / DAYSPERWEEK,
417efdf7294Smillert "%02d", pt, ptlim);
418efdf7294Smillert continue;
419efdf7294Smillert case 'w':
420efdf7294Smillert pt = _conv(t->tm_wday, "%d", pt, ptlim);
421efdf7294Smillert continue;
422efdf7294Smillert case 'X':
423efdf7294Smillert pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
424efdf7294Smillert continue;
425efdf7294Smillert case 'x':
426efdf7294Smillert {
427efdf7294Smillert int warn2 = IN_SOME;
428efdf7294Smillert
429efdf7294Smillert pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
430efdf7294Smillert if (warn2 == IN_ALL)
431efdf7294Smillert warn2 = IN_THIS;
432efdf7294Smillert if (warn2 > *warnp)
433efdf7294Smillert *warnp = warn2;
434efdf7294Smillert }
435efdf7294Smillert continue;
436efdf7294Smillert case 'y':
437efdf7294Smillert *warnp = IN_ALL;
4383f56448eSmillert pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1,
4393f56448eSmillert pt, ptlim);
440efdf7294Smillert continue;
441efdf7294Smillert case 'Y':
4423f56448eSmillert pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1,
4433f56448eSmillert pt, ptlim);
444efdf7294Smillert continue;
445efdf7294Smillert case 'Z':
446bb5f5481Sschwarze if (t->tm_zone != NULL)
447bb5f5481Sschwarze pt = _add(t->tm_zone, pt, ptlim);
448bb5f5481Sschwarze else if (t->tm_isdst >= 0)
449ef12f6f8Smillert pt = _add(tzname[t->tm_isdst != 0],
450efdf7294Smillert pt, ptlim);
451ef12f6f8Smillert /*
452ef12f6f8Smillert ** C99 says that %Z must be replaced by the
453ef12f6f8Smillert ** empty string if the time zone is not
454ef12f6f8Smillert ** determinable.
455ef12f6f8Smillert */
456ef12f6f8Smillert continue;
457ef12f6f8Smillert case 'z':
458ef12f6f8Smillert {
459ef12f6f8Smillert int diff;
460ef12f6f8Smillert char const * sign;
461ef12f6f8Smillert
462ef12f6f8Smillert if (t->tm_isdst < 0)
463ef12f6f8Smillert continue;
464bb5f5481Sschwarze diff = t->tm_gmtoff;
465ef12f6f8Smillert if (diff < 0) {
466ef12f6f8Smillert sign = "-";
467ef12f6f8Smillert diff = -diff;
468ef12f6f8Smillert } else sign = "+";
469ef12f6f8Smillert pt = _add(sign, pt, ptlim);
4703f56448eSmillert diff /= SECSPERMIN;
4713f56448eSmillert diff = (diff / MINSPERHOUR) * 100 +
4723f56448eSmillert (diff % MINSPERHOUR);
4733f56448eSmillert pt = _conv(diff, "%04d", pt, ptlim);
474ef12f6f8Smillert }
475efdf7294Smillert continue;
476efdf7294Smillert case '+':
477efdf7294Smillert pt = _fmt(Locale->date_fmt, t, pt, ptlim,
478efdf7294Smillert warnp);
479efdf7294Smillert continue;
480efdf7294Smillert case '%':
481efdf7294Smillert /*
482ef12f6f8Smillert ** X311J/88-090 (4.12.3.5): if conversion char is
483ef12f6f8Smillert ** undefined, behavior is undefined. Print out the
484ef12f6f8Smillert ** character itself as printf(3) also does.
485efdf7294Smillert */
486efdf7294Smillert default:
487efdf7294Smillert break;
488efdf7294Smillert }
489efdf7294Smillert }
490efdf7294Smillert if (pt == ptlim)
491efdf7294Smillert break;
492efdf7294Smillert *pt++ = *format;
493efdf7294Smillert }
494efdf7294Smillert return pt;
495efdf7294Smillert }
496efdf7294Smillert
497efdf7294Smillert static char *
_conv(int n,const char * format,char * pt,const char * ptlim)4988dc2b201Stedu _conv(int n, const char *format, char *pt, const char *ptlim)
499efdf7294Smillert {
500efdf7294Smillert char buf[INT_STRLEN_MAXIMUM(int) + 1];
501efdf7294Smillert
502c364cbbdSderaadt (void) snprintf(buf, sizeof buf, format, n);
503efdf7294Smillert return _add(buf, pt, ptlim);
504efdf7294Smillert }
505efdf7294Smillert
506efdf7294Smillert static char *
_add(const char * str,char * pt,const char * ptlim)5078dc2b201Stedu _add(const char *str, char *pt, const char *ptlim)
508efdf7294Smillert {
509efdf7294Smillert while (pt < ptlim && (*pt = *str++) != '\0')
510efdf7294Smillert ++pt;
511efdf7294Smillert return pt;
512efdf7294Smillert }
513efdf7294Smillert
5143f56448eSmillert /*
5153f56448eSmillert ** POSIX and the C Standard are unclear or inconsistent about
5163f56448eSmillert ** what %C and %y do if the year is negative or exceeds 9999.
5173f56448eSmillert ** Use the convention that %C concatenated with %y yields the
5183f56448eSmillert ** same output as %Y, and that %Y contains at least 4 bytes,
5193f56448eSmillert ** with more only if necessary.
5203f56448eSmillert */
5213f56448eSmillert
5223f56448eSmillert static char *
_yconv(int a,int b,int convert_top,int convert_yy,char * pt,const char * ptlim)5238dc2b201Stedu _yconv(int a, int b, int convert_top, int convert_yy, char *pt, const char *ptlim)
5243f56448eSmillert {
5256a6b658aStedu int lead;
5266a6b658aStedu int trail;
5273f56448eSmillert
5283f56448eSmillert #define DIVISOR 100
5293f56448eSmillert trail = a % DIVISOR + b % DIVISOR;
5303f56448eSmillert lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
5313f56448eSmillert trail %= DIVISOR;
5323f56448eSmillert if (trail < 0 && lead > 0) {
5333f56448eSmillert trail += DIVISOR;
5343f56448eSmillert --lead;
5353f56448eSmillert } else if (lead < 0 && trail > 0) {
5363f56448eSmillert trail -= DIVISOR;
5373f56448eSmillert ++lead;
5383f56448eSmillert }
5393f56448eSmillert if (convert_top) {
5403f56448eSmillert if (lead == 0 && trail < 0)
5413f56448eSmillert pt = _add("-0", pt, ptlim);
5423f56448eSmillert else pt = _conv(lead, "%02d", pt, ptlim);
5433f56448eSmillert }
5443f56448eSmillert if (convert_yy)
5453f56448eSmillert pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
5463f56448eSmillert return pt;
5473f56448eSmillert }
548