PostgreSQL Source Code git master
Loading...
Searching...
No Matches
strftime.c
Go to the documentation of this file.
1/* Convert a broken-down timestamp to a string. */
2
3/*
4 * Copyright 1989 The Regents of the University of California.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32/*
33 * Based on the UCB version with the copyright notice appearing above.
34 *
35 * This is ANSIish only when "multibyte character == plain character".
36 *
37 * IDENTIFICATION
38 * src/timezone/strftime.c
39 */
40
41#include "postgres.h"
42
43#include <fcntl.h>
44
45#include "private.h"
46
47
49{
50 const char *mon[MONSPERYEAR];
51 const char *month[MONSPERYEAR];
52 const char *wday[DAYSPERWEEK];
53 const char *weekday[DAYSPERWEEK];
54 const char *X_fmt;
55 const char *x_fmt;
56 const char *c_fmt;
57 const char *am;
58 const char *pm;
59 const char *date_fmt;
60};
61
62static const struct lc_time_T C_time_locale = {
63 {
64 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
65 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
66 }, {
67 "January", "February", "March", "April", "May", "June",
68 "July", "August", "September", "October", "November", "December"
69 }, {
70 "Sun", "Mon", "Tue", "Wed",
71 "Thu", "Fri", "Sat"
72 }, {
73 "Sunday", "Monday", "Tuesday", "Wednesday",
74 "Thursday", "Friday", "Saturday"
75 },
76
77 /* X_fmt */
78 "%H:%M:%S",
79
80 /*
81 * x_fmt C99 and later require this format. Using just numbers (as here)
82 * makes Quakers happier; it's also compatible with SVR4.
83 */
84 "%m/%d/%y",
85
86 /*
87 * c_fmt C99 and later require this format. Previously this code used "%D
88 * %X", but we now conform to C99. Note that "%a %b %d %H:%M:%S %Y" is
89 * used by Solaris 2.3.
90 */
91 "%a %b %e %T %Y",
92
93 /* am */
94 "AM",
95
96 /* pm */
97 "PM",
98
99 /* date_fmt */
100 "%a %b %e %H:%M:%S %Z %Y"
101};
102
107
108static char *_add(const char *str, char *pt, const char *ptlim);
109static char *_conv(int n, const char *format, char *pt, const char *ptlim);
110static char *_fmt(const char *format, const struct pg_tm *t, char *pt, const char *ptlim,
111 enum warn *warnp);
112static char *_yconv(int a, int b, bool convert_top, bool convert_yy, char *pt, char const *ptlim);
113
114
115/*
116 * Convert timestamp t to string s, a caller-allocated buffer of size maxsize,
117 * using the given format pattern.
118 *
119 * Unlike standard strftime(), we guarantee to provide a null-terminated
120 * result even on failure, so long as maxsize > 0. If we overrun the buffer,
121 * return an empty string rather than risking mis-encoded multibyte output.
122 * (Since this module only supports C locale, you might think multibyte
123 * characters are impossible --- but the time zone name printed by %Z comes
124 * from outside and could contain such.)
125 *
126 * See also timestamptz_to_str.
127 */
128size_t
129pg_strftime(char *s, size_t maxsize, const char *format, const struct pg_tm *t)
130{
131 char *p;
132 int saved_errno = errno;
133 enum warn warn = IN_NONE;
134
135 p = _fmt(format, t, s, s + maxsize, &warn);
136 if (!p)
137 {
139 if (maxsize > 0)
140 *s = '\0';
141 return 0;
142 }
143 if (p == s + maxsize)
144 {
145 errno = ERANGE;
146 if (maxsize > 0)
147 *s = '\0';
148 return 0;
149 }
150 *p = '\0';
151 errno = saved_errno;
152 return p - s;
153}
154
155static char *
156_fmt(const char *format, const struct pg_tm *t, char *pt,
157 const char *ptlim, enum warn *warnp)
158{
160
161 for (; *format; ++format)
162 {
163 if (*format == '%')
164 {
165 label:
166 switch (*++format)
167 {
168 default:
169
170 /*
171 * Output unknown conversion specifiers as-is, to aid
172 * debugging. This includes '%' at format end. This
173 * conforms to C23 section 7.29.3.5 paragraph 6, which
174 * says behavior is undefined here.
175 */
176 --format;
177 break;
178 case 'A':
179 pt = _add((t->tm_wday < 0 ||
180 t->tm_wday >= DAYSPERWEEK) ?
181 "?" : Locale->weekday[t->tm_wday],
182 pt, ptlim);
183 continue;
184 case 'a':
185 pt = _add((t->tm_wday < 0 ||
186 t->tm_wday >= DAYSPERWEEK) ?
187 "?" : Locale->wday[t->tm_wday],
188 pt, ptlim);
189 continue;
190 case 'B':
191 pt = _add((t->tm_mon < 0 ||
192 t->tm_mon >= MONSPERYEAR) ?
193 "?" : Locale->month[t->tm_mon],
194 pt, ptlim);
195 continue;
196 case 'b':
197 case 'h':
198 pt = _add((t->tm_mon < 0 ||
199 t->tm_mon >= MONSPERYEAR) ?
200 "?" : Locale->mon[t->tm_mon],
201 pt, ptlim);
202 continue;
203 case 'C':
204
205 /*
206 * %C used to do a... _fmt("%a %b %e %X %Y", t);
207 * ...whereas now POSIX 1003.2 calls for something
208 * completely different. (ado, 1993-05-24)
209 */
211 true, false, pt, ptlim);
212 continue;
213 case 'c':
214 {
215 enum warn warn2 = IN_SOME;
216
217 pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
218 if (warn2 == IN_ALL)
219 warn2 = IN_THIS;
220 if (warn2 > *warnp)
221 *warnp = warn2;
222 }
223 continue;
224 case 'D':
225 pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
226 continue;
227 case 'd':
228 pt = _conv(t->tm_mday, "%02d", pt, ptlim);
229 continue;
230 case 'E':
231 case 'O':
232
233 /*
234 * Locale modifiers of C99 and later. The sequences %Ec
235 * %EC %Ex %EX %Ey %EY %Od %oe %OH %OI %Om %OM %OS %Ou %OU
236 * %OV %Ow %OW %Oy are supposed to provide alternative
237 * representations.
238 */
239 goto label;
240 case 'e':
241 pt = _conv(t->tm_mday, "%2d", pt, ptlim);
242 continue;
243 case 'F':
244 pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
245 continue;
246 case 'H':
247 pt = _conv(t->tm_hour, "%02d", pt, ptlim);
248 continue;
249 case 'I':
250 pt = _conv((t->tm_hour % 12) ?
251 (t->tm_hour % 12) : 12,
252 "%02d", pt, ptlim);
253 continue;
254 case 'j':
255 pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
256 continue;
257 case 'k':
258
259 /*
260 * This used to be... _conv(t->tm_hour % 12 ? t->tm_hour %
261 * 12 : 12, 2, ' '); ...and has been changed to the below
262 * to match SunOS 4.1.1 and Arnold Robbins' strftime
263 * version 3.0. That is, "%k" and "%l" have been swapped.
264 * (ado, 1993-05-24)
265 */
266 pt = _conv(t->tm_hour, "%2d", pt, ptlim);
267 continue;
268#ifdef KITCHEN_SINK
269 case 'K':
270
271 /*
272 * After all this time, still unclaimed!
273 */
274 pt = _add("kitchen sink", pt, ptlim);
275 continue;
276#endif /* defined KITCHEN_SINK */
277 case 'l':
278
279 /*
280 * This used to be... _conv(t->tm_hour, 2, ' '); ...and
281 * has been changed to the below to match SunOS 4.1.1 and
282 * Arnold Robbin's strftime version 3.0. That is, "%k" and
283 * "%l" have been swapped. (ado, 1993-05-24)
284 */
285 pt = _conv((t->tm_hour % 12) ?
286 (t->tm_hour % 12) : 12,
287 "%2d", pt, ptlim);
288 continue;
289 case 'M':
290 pt = _conv(t->tm_min, "%02d", pt, ptlim);
291 continue;
292 case 'm':
293 pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
294 continue;
295 case 'n':
296 pt = _add("\n", pt, ptlim);
297 continue;
298 case 'p':
299 pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
300 Locale->pm :
301 Locale->am,
302 pt, ptlim);
303 continue;
304 case 'R':
305 pt = _fmt("%H:%M", t, pt, ptlim, warnp);
306 continue;
307 case 'r':
308 pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
309 continue;
310 case 'S':
311 pt = _conv(t->tm_sec, "%02d", pt, ptlim);
312 continue;
313 case 'T':
314 pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
315 continue;
316 case 't':
317 pt = _add("\t", pt, ptlim);
318 continue;
319 case 'U':
320 pt = _conv((t->tm_yday + DAYSPERWEEK -
321 t->tm_wday) / DAYSPERWEEK,
322 "%02d", pt, ptlim);
323 continue;
324 case 'u':
325
326 /*
327 * From Arnold Robbins' strftime version 3.0: "ISO 8601:
328 * Weekday as a decimal number [1 (Monday) - 7]" (ado,
329 * 1993-05-24)
330 */
331 pt = _conv((t->tm_wday == 0) ?
332 DAYSPERWEEK : t->tm_wday,
333 "%d", pt, ptlim);
334 continue;
335 case 'V': /* ISO 8601 week number */
336 case 'G': /* ISO 8601 year (four digits) */
337 case 'g': /* ISO 8601 year (two digits) */
338/*
339 * From Arnold Robbins' strftime version 3.0: "the week number of the
340 * year (the first Monday as the first day of week 1) as a decimal number
341 * (01-53)."
342 * (ado, 1993-05-24)
343 *
344 * From <https://www.cl.cam.ac.uk/~mgk25/iso-time.html> by Markus Kuhn:
345 * "Week 01 of a year is per definition the first week which has the
346 * Thursday in this year, which is equivalent to the week which contains
347 * the fourth day of January. In other words, the first week of a new year
348 * is the week which has the majority of its days in the new year. Week 01
349 * might also contain days from the previous year and the week before week
350 * 01 of a year is the last week (52 or 53) of the previous year even if
351 * it contains days from the new year. A week starts with Monday (day 1)
352 * and ends with Sunday (day 7). For example, the first week of the year
353 * 1997 lasts from 1996-12-30 to 1997-01-05..."
354 * (ado, 1996-01-02)
355 */
356 {
357 int year;
358 int base;
359 int yday;
360 int wday;
361 int w;
362
363 year = t->tm_year;
364 base = TM_YEAR_BASE;
365 yday = t->tm_yday;
366 wday = t->tm_wday;
367 for (;;)
368 {
369 int len;
370 int bot;
371 int top;
372
373 len = isleap_sum(year, base) ?
376
377 /*
378 * What yday (-3 ... 3) does the ISO year begin
379 * on?
380 */
381 bot = ((yday + 11 - wday) %
382 DAYSPERWEEK) - 3;
383
384 /*
385 * What yday does the NEXT ISO year begin on?
386 */
387 top = bot -
388 (len % DAYSPERWEEK);
389 if (top < -3)
390 top += DAYSPERWEEK;
391 top += len;
392 if (yday >= top)
393 {
394 ++base;
395 w = 1;
396 break;
397 }
398 if (yday >= bot)
399 {
400 w = 1 + ((yday - bot) /
402 break;
403 }
404 --base;
405 yday += isleap_sum(year, base) ?
408 }
409 if (*format == 'V')
410 pt = _conv(w, "%02d",
411 pt, ptlim);
412 else if (*format == 'g')
413 {
414 *warnp = IN_ALL;
415 pt = _yconv(year, base,
416 false, true,
417 pt, ptlim);
418 }
419 else
420 pt = _yconv(year, base,
421 true, true,
422 pt, ptlim);
423 }
424 continue;
425 case 'v':
426
427 /*
428 * From Arnold Robbins' strftime version 3.0: "date as
429 * dd-bbb-YYYY" (ado, 1993-05-24)
430 */
431 pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
432 continue;
433 case 'W':
434 pt = _conv((t->tm_yday + DAYSPERWEEK -
435 (t->tm_wday ?
436 (t->tm_wday - 1) :
437 (DAYSPERWEEK - 1))) / DAYSPERWEEK,
438 "%02d", pt, ptlim);
439 continue;
440 case 'w':
441 pt = _conv(t->tm_wday, "%d", pt, ptlim);
442 continue;
443 case 'X':
444 pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
445 continue;
446 case 'x':
447 {
448 enum warn warn2 = IN_SOME;
449
450 pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
451 if (warn2 == IN_ALL)
452 warn2 = IN_THIS;
453 if (warn2 > *warnp)
454 *warnp = warn2;
455 }
456 continue;
457 case 'y':
458 *warnp = IN_ALL;
460 false, true,
461 pt, ptlim);
462 continue;
463 case 'Y':
465 true, true,
466 pt, ptlim);
467 continue;
468 case 'Z':
469 if (t->tm_zone != NULL)
470 pt = _add(t->tm_zone, pt, ptlim);
471
472 /*
473 * C99 and later say that %Z must be replaced by the empty
474 * string if the time zone abbreviation is not
475 * determinable.
476 */
477 continue;
478 case 'z':
479 {
480 long diff;
481 char const *sign;
482 bool negative;
483
484 diff = t->TM_GMTOFF;
485 negative = diff < 0;
486 if (diff == 0)
487 {
488 if (t->tm_zone != NULL)
489 negative = t->tm_zone[0] == '-';
490 }
491 if (negative)
492 {
493 sign = "-";
494 diff = -diff;
495 }
496 else
497 sign = "+";
498 pt = _add(sign, pt, ptlim);
499 diff /= SECSPERMIN;
500 diff = (diff / MINSPERHOUR) * 100 +
501 (diff % MINSPERHOUR);
502 pt = _conv(diff, "%04d", pt, ptlim);
503 }
504 continue;
505 case '+':
506 pt = _fmt(Locale->date_fmt, t, pt, ptlim,
507 warnp);
508 continue;
509 case '%':
510 break;
511 }
512 }
513 if (pt == ptlim)
514 break;
515 *pt++ = *format;
516 }
517 return pt;
518}
519
520static char *
521_conv(int n, const char *format, char *pt, const char *ptlim)
522{
523 char buf[INT_STRLEN_MAXIMUM(int) + 1];
524
525 sprintf(buf, format, n);
526 return _add(buf, pt, ptlim);
527}
528
529static char *
530_add(const char *str, char *pt, const char *ptlim)
531{
532 while (pt < ptlim && (*pt = *str++) != '\0')
533 ++pt;
534 return pt;
535}
536
537/*
538 * POSIX and the C Standard are unclear or inconsistent about
539 * what %C and %y do if the year is negative or exceeds 9999.
540 * Use the convention that %C concatenated with %y yields the
541 * same output as %Y, and that %Y contains at least 4 bytes,
542 * with more only if necessary.
543 */
544
545static char *
546_yconv(int a, int b, bool convert_top, bool convert_yy,
547 char *pt, const char *ptlim)
548{
549 int lead;
550 int trail;
551
552 int DIVISOR = 100;
553
554 trail = a % DIVISOR + b % DIVISOR;
555 lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
556 trail %= DIVISOR;
558 {
559 trail += DIVISOR;
560 --lead;
561 }
562 else if (lead < 0 && trail > 0)
563 {
564 trail -= DIVISOR;
565 ++lead;
566 }
567 if (convert_top)
568 {
569 if (lead == 0 && trail < 0)
570 pt = _add("-0", pt, ptlim);
571 else
572 pt = _conv(lead, "%02d", pt, ptlim);
573 }
574 if (convert_yy)
575 pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
576 return pt;
577}
const char * str
char sign
Definition informix.c:693
int b
Definition isn.c:74
int a
Definition isn.c:73
static char format
static char * label
const void size_t len
static char buf[DEFAULT_XLOG_SEG_SIZE]
#define sprintf
Definition port.h:263
static int fb(int x)
@ DAYSPERNYEAR
Definition private.h:214
@ DAYSPERLYEAR
Definition private.h:215
@ DAYSPERWEEK
Definition private.h:213
@ SECSPERMIN
Definition private.h:209
@ MONSPERYEAR
Definition private.h:216
@ MINSPERHOUR
Definition private.h:210
@ HOURSPERDAY
Definition private.h:212
#define INT_STRLEN_MAXIMUM(type)
Definition private.h:178
#define EOVERFLOW
Definition private.h:88
@ TM_YEAR_BASE
Definition private.h:272
#define isleap_sum(a, b)
Definition private.h:292
warn
Definition strftime.c:104
@ IN_ALL
Definition strftime.c:105
@ IN_NONE
Definition strftime.c:105
@ IN_SOME
Definition strftime.c:105
@ IN_THIS
Definition strftime.c:105
static char * _add(const char *str, char *pt, const char *ptlim)
Definition strftime.c:530
static char * _fmt(const char *format, const struct pg_tm *t, char *pt, const char *ptlim, enum warn *warnp)
Definition strftime.c:156
static char * _yconv(int a, int b, bool convert_top, bool convert_yy, char *pt, char const *ptlim)
Definition strftime.c:546
static char * _conv(int n, const char *format, char *pt, const char *ptlim)
Definition strftime.c:521
size_t pg_strftime(char *s, size_t maxsize, const char *format, const struct pg_tm *t)
Definition strftime.c:129
static const struct lc_time_T C_time_locale
Definition strftime.c:62
const char * am
Definition strftime.c:57
const char * X_fmt
Definition strftime.c:54
const char * mon[MONSPERYEAR]
Definition strftime.c:50
const char * weekday[DAYSPERWEEK]
Definition strftime.c:53
const char * wday[DAYSPERWEEK]
Definition strftime.c:52
const char * month[MONSPERYEAR]
Definition strftime.c:51
const char * x_fmt
Definition strftime.c:55
const char * pm
Definition strftime.c:58
const char * date_fmt
Definition strftime.c:59
const char * c_fmt
Definition strftime.c:56
Definition pgtime.h:35
int tm_hour
Definition pgtime.h:38
int tm_mday
Definition pgtime.h:39
int tm_mon
Definition pgtime.h:40
int tm_min
Definition pgtime.h:37
const char * tm_zone
Definition pgtime.h:46
int tm_yday
Definition pgtime.h:43
int tm_wday
Definition pgtime.h:42
int tm_sec
Definition pgtime.h:36
int tm_year
Definition pgtime.h:41