1 /*-------------------------------------------------------------------------
2 *
3 * datetime.c
4 * Support functions for date/time types.
5 *
6 * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 *
10 * IDENTIFICATION
11 * src/backend/utils/adt/datetime.c
12 *
13 *-------------------------------------------------------------------------
14 */
15 #include "postgres.h"
16
17 #include <ctype.h>
18 #include <limits.h>
19 #include <math.h>
20
21 #include "access/htup_details.h"
22 #include "access/xact.h"
23 #include "catalog/pg_type.h"
24 #include "common/string.h"
25 #include "funcapi.h"
26 #include "miscadmin.h"
27 #include "nodes/nodeFuncs.h"
28 #include "utils/builtins.h"
29 #include "utils/date.h"
30 #include "utils/datetime.h"
31 #include "utils/memutils.h"
32 #include "utils/tzparser.h"
33
34 static int DecodeNumber(int flen, char *field, bool haveTextMonth,
35 int fmask, int *tmask,
36 struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
37 static int DecodeNumberField(int len, char *str,
38 int fmask, int *tmask,
39 struct pg_tm *tm, fsec_t *fsec, bool *is2digits);
40 static int DecodeTime(char *str, int fmask, int range,
41 int *tmask, struct pg_tm *tm, fsec_t *fsec);
42 static const datetkn *datebsearch(const char *key, const datetkn *base, int nel);
43 static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
44 struct pg_tm *tm);
45 static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
46 int precision, bool fillzeros);
47 static void AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec,
48 int scale);
49 static void AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec,
50 int scale);
51 static int DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp,
52 pg_time_t *tp);
53 static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
54 const char *abbr, pg_tz *tzp,
55 int *offset, int *isdst);
56 static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp);
57
58
59 const int day_tab[2][13] =
60 {
61 {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0},
62 {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}
63 };
64
65 const char *const months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
66 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
67
68 const char *const days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
69 "Thursday", "Friday", "Saturday", NULL};
70
71
72 /*****************************************************************************
73 * PRIVATE ROUTINES *
74 *****************************************************************************/
75
76 /*
77 * datetktbl holds date/time keywords.
78 *
79 * Note that this table must be strictly alphabetically ordered to allow an
80 * O(ln(N)) search algorithm to be used.
81 *
82 * The token field must be NUL-terminated; we truncate entries to TOKMAXLEN
83 * characters to fit.
84 *
85 * The static table contains no TZ, DTZ, or DYNTZ entries; rather those
86 * are loaded from configuration files and stored in zoneabbrevtbl, whose
87 * abbrevs[] field has the same format as the static datetktbl.
88 */
89 static const datetkn datetktbl[] = {
90 /* token, type, value */
91 {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
92 {DA_D, ADBC, AD}, /* "ad" for years > 0 */
93 {"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */
94 {"am", AMPM, AM},
95 {"apr", MONTH, 4},
96 {"april", MONTH, 4},
97 {"at", IGNORE_DTF, 0}, /* "at" (throwaway) */
98 {"aug", MONTH, 8},
99 {"august", MONTH, 8},
100 {DB_C, ADBC, BC}, /* "bc" for years <= 0 */
101 {"d", UNITS, DTK_DAY}, /* "day of month" for ISO input */
102 {"dec", MONTH, 12},
103 {"december", MONTH, 12},
104 {"dow", UNITS, DTK_DOW}, /* day of week */
105 {"doy", UNITS, DTK_DOY}, /* day of year */
106 {"dst", DTZMOD, SECS_PER_HOUR},
107 {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */
108 {"feb", MONTH, 2},
109 {"february", MONTH, 2},
110 {"fri", DOW, 5},
111 {"friday", DOW, 5},
112 {"h", UNITS, DTK_HOUR}, /* "hour" */
113 {LATE, RESERV, DTK_LATE}, /* "infinity" reserved for "late time" */
114 {"isodow", UNITS, DTK_ISODOW}, /* ISO day of week, Sunday == 7 */
115 {"isoyear", UNITS, DTK_ISOYEAR}, /* year in terms of the ISO week date */
116 {"j", UNITS, DTK_JULIAN},
117 {"jan", MONTH, 1},
118 {"january", MONTH, 1},
119 {"jd", UNITS, DTK_JULIAN},
120 {"jul", MONTH, 7},
121 {"julian", UNITS, DTK_JULIAN},
122 {"july", MONTH, 7},
123 {"jun", MONTH, 6},
124 {"june", MONTH, 6},
125 {"m", UNITS, DTK_MONTH}, /* "month" for ISO input */
126 {"mar", MONTH, 3},
127 {"march", MONTH, 3},
128 {"may", MONTH, 5},
129 {"mm", UNITS, DTK_MINUTE}, /* "minute" for ISO input */
130 {"mon", DOW, 1},
131 {"monday", DOW, 1},
132 {"nov", MONTH, 11},
133 {"november", MONTH, 11},
134 {NOW, RESERV, DTK_NOW}, /* current transaction time */
135 {"oct", MONTH, 10},
136 {"october", MONTH, 10},
137 {"on", IGNORE_DTF, 0}, /* "on" (throwaway) */
138 {"pm", AMPM, PM},
139 {"s", UNITS, DTK_SECOND}, /* "seconds" for ISO input */
140 {"sat", DOW, 6},
141 {"saturday", DOW, 6},
142 {"sep", MONTH, 9},
143 {"sept", MONTH, 9},
144 {"september", MONTH, 9},
145 {"sun", DOW, 0},
146 {"sunday", DOW, 0},
147 {"t", ISOTIME, DTK_TIME}, /* Filler for ISO time fields */
148 {"thu", DOW, 4},
149 {"thur", DOW, 4},
150 {"thurs", DOW, 4},
151 {"thursday", DOW, 4},
152 {TODAY, RESERV, DTK_TODAY}, /* midnight */
153 {TOMORROW, RESERV, DTK_TOMORROW}, /* tomorrow midnight */
154 {"tue", DOW, 2},
155 {"tues", DOW, 2},
156 {"tuesday", DOW, 2},
157 {"wed", DOW, 3},
158 {"wednesday", DOW, 3},
159 {"weds", DOW, 3},
160 {"y", UNITS, DTK_YEAR}, /* "year" for ISO input */
161 {YESTERDAY, RESERV, DTK_YESTERDAY} /* yesterday midnight */
162 };
163
164 static const int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];
165
166 /*
167 * deltatktbl: same format as datetktbl, but holds keywords used to represent
168 * time units (eg, for intervals, and for EXTRACT).
169 */
170 static const datetkn deltatktbl[] = {
171 /* token, type, value */
172 {"@", IGNORE_DTF, 0}, /* postgres relative prefix */
173 {DAGO, AGO, 0}, /* "ago" indicates negative time offset */
174 {"c", UNITS, DTK_CENTURY}, /* "century" relative */
175 {"cent", UNITS, DTK_CENTURY}, /* "century" relative */
176 {"centuries", UNITS, DTK_CENTURY}, /* "centuries" relative */
177 {DCENTURY, UNITS, DTK_CENTURY}, /* "century" relative */
178 {"d", UNITS, DTK_DAY}, /* "day" relative */
179 {DDAY, UNITS, DTK_DAY}, /* "day" relative */
180 {"days", UNITS, DTK_DAY}, /* "days" relative */
181 {"dec", UNITS, DTK_DECADE}, /* "decade" relative */
182 {DDECADE, UNITS, DTK_DECADE}, /* "decade" relative */
183 {"decades", UNITS, DTK_DECADE}, /* "decades" relative */
184 {"decs", UNITS, DTK_DECADE}, /* "decades" relative */
185 {"h", UNITS, DTK_HOUR}, /* "hour" relative */
186 {DHOUR, UNITS, DTK_HOUR}, /* "hour" relative */
187 {"hours", UNITS, DTK_HOUR}, /* "hours" relative */
188 {"hr", UNITS, DTK_HOUR}, /* "hour" relative */
189 {"hrs", UNITS, DTK_HOUR}, /* "hours" relative */
190 {"m", UNITS, DTK_MINUTE}, /* "minute" relative */
191 {"microsecon", UNITS, DTK_MICROSEC}, /* "microsecond" relative */
192 {"mil", UNITS, DTK_MILLENNIUM}, /* "millennium" relative */
193 {"millennia", UNITS, DTK_MILLENNIUM}, /* "millennia" relative */
194 {DMILLENNIUM, UNITS, DTK_MILLENNIUM}, /* "millennium" relative */
195 {"millisecon", UNITS, DTK_MILLISEC}, /* relative */
196 {"mils", UNITS, DTK_MILLENNIUM}, /* "millennia" relative */
197 {"min", UNITS, DTK_MINUTE}, /* "minute" relative */
198 {"mins", UNITS, DTK_MINUTE}, /* "minutes" relative */
199 {DMINUTE, UNITS, DTK_MINUTE}, /* "minute" relative */
200 {"minutes", UNITS, DTK_MINUTE}, /* "minutes" relative */
201 {"mon", UNITS, DTK_MONTH}, /* "months" relative */
202 {"mons", UNITS, DTK_MONTH}, /* "months" relative */
203 {DMONTH, UNITS, DTK_MONTH}, /* "month" relative */
204 {"months", UNITS, DTK_MONTH},
205 {"ms", UNITS, DTK_MILLISEC},
206 {"msec", UNITS, DTK_MILLISEC},
207 {DMILLISEC, UNITS, DTK_MILLISEC},
208 {"mseconds", UNITS, DTK_MILLISEC},
209 {"msecs", UNITS, DTK_MILLISEC},
210 {"qtr", UNITS, DTK_QUARTER}, /* "quarter" relative */
211 {DQUARTER, UNITS, DTK_QUARTER}, /* "quarter" relative */
212 {"s", UNITS, DTK_SECOND},
213 {"sec", UNITS, DTK_SECOND},
214 {DSECOND, UNITS, DTK_SECOND},
215 {"seconds", UNITS, DTK_SECOND},
216 {"secs", UNITS, DTK_SECOND},
217 {DTIMEZONE, UNITS, DTK_TZ}, /* "timezone" time offset */
218 {"timezone_h", UNITS, DTK_TZ_HOUR}, /* timezone hour units */
219 {"timezone_m", UNITS, DTK_TZ_MINUTE}, /* timezone minutes units */
220 {"us", UNITS, DTK_MICROSEC}, /* "microsecond" relative */
221 {"usec", UNITS, DTK_MICROSEC}, /* "microsecond" relative */
222 {DMICROSEC, UNITS, DTK_MICROSEC}, /* "microsecond" relative */
223 {"useconds", UNITS, DTK_MICROSEC}, /* "microseconds" relative */
224 {"usecs", UNITS, DTK_MICROSEC}, /* "microseconds" relative */
225 {"w", UNITS, DTK_WEEK}, /* "week" relative */
226 {DWEEK, UNITS, DTK_WEEK}, /* "week" relative */
227 {"weeks", UNITS, DTK_WEEK}, /* "weeks" relative */
228 {"y", UNITS, DTK_YEAR}, /* "year" relative */
229 {DYEAR, UNITS, DTK_YEAR}, /* "year" relative */
230 {"years", UNITS, DTK_YEAR}, /* "years" relative */
231 {"yr", UNITS, DTK_YEAR}, /* "year" relative */
232 {"yrs", UNITS, DTK_YEAR} /* "years" relative */
233 };
234
235 static const int szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0];
236
237 static TimeZoneAbbrevTable *zoneabbrevtbl = NULL;
238
239 /* Caches of recent lookup results in the above tables */
240
241 static const datetkn *datecache[MAXDATEFIELDS] = {NULL};
242
243 static const datetkn *deltacache[MAXDATEFIELDS] = {NULL};
244
245 static const datetkn *abbrevcache[MAXDATEFIELDS] = {NULL};
246
247
248 /*
249 * Calendar time to Julian date conversions.
250 * Julian date is commonly used in astronomical applications,
251 * since it is numerically accurate and computationally simple.
252 * The algorithms here will accurately convert between Julian day
253 * and calendar date for all non-negative Julian days
254 * (i.e. from Nov 24, -4713 on).
255 *
256 * Rewritten to eliminate overflow problems. This now allows the
257 * routines to work correctly for all Julian day counts from
258 * 0 to 2147483647 (Nov 24, -4713 to Jun 3, 5874898) assuming
259 * a 32-bit integer. Longer types should also work to the limits
260 * of their precision.
261 *
262 * Actually, date2j() will work sanely, in the sense of producing
263 * valid negative Julian dates, significantly before Nov 24, -4713.
264 * We rely on it to do so back to Nov 1, -4713; see IS_VALID_JULIAN()
265 * and associated commentary in timestamp.h.
266 */
267
268 int
date2j(int y,int m,int d)269 date2j(int y, int m, int d)
270 {
271 int julian;
272 int century;
273
274 if (m > 2)
275 {
276 m += 1;
277 y += 4800;
278 }
279 else
280 {
281 m += 13;
282 y += 4799;
283 }
284
285 century = y / 100;
286 julian = y * 365 - 32167;
287 julian += y / 4 - century + century / 4;
288 julian += 7834 * m / 256 + d;
289
290 return julian;
291 } /* date2j() */
292
293 void
j2date(int jd,int * year,int * month,int * day)294 j2date(int jd, int *year, int *month, int *day)
295 {
296 unsigned int julian;
297 unsigned int quad;
298 unsigned int extra;
299 int y;
300
301 julian = jd;
302 julian += 32044;
303 quad = julian / 146097;
304 extra = (julian - quad * 146097) * 4 + 3;
305 julian += 60 + quad * 3 + extra / 146097;
306 quad = julian / 1461;
307 julian -= quad * 1461;
308 y = julian * 4 / 1461;
309 julian = ((y != 0) ? ((julian + 305) % 365) : ((julian + 306) % 366))
310 + 123;
311 y += quad * 4;
312 *year = y - 4800;
313 quad = julian * 2141 / 65536;
314 *day = julian - 7834 * quad / 256;
315 *month = (quad + 10) % MONTHS_PER_YEAR + 1;
316 } /* j2date() */
317
318
319 /*
320 * j2day - convert Julian date to day-of-week (0..6 == Sun..Sat)
321 *
322 * Note: various places use the locution j2day(date - 1) to produce a
323 * result according to the convention 0..6 = Mon..Sun. This is a bit of
324 * a crock, but will work as long as the computation here is just a modulo.
325 */
326 int
j2day(int date)327 j2day(int date)
328 {
329 date += 1;
330 date %= 7;
331 /* Cope if division truncates towards zero, as it probably does */
332 if (date < 0)
333 date += 7;
334
335 return date;
336 } /* j2day() */
337
338
339 /*
340 * GetCurrentDateTime()
341 *
342 * Get the transaction start time ("now()") broken down as a struct pg_tm.
343 */
344 void
GetCurrentDateTime(struct pg_tm * tm)345 GetCurrentDateTime(struct pg_tm *tm)
346 {
347 int tz;
348 fsec_t fsec;
349
350 timestamp2tm(GetCurrentTransactionStartTimestamp(), &tz, tm, &fsec,
351 NULL, NULL);
352 /* Note: don't pass NULL tzp to timestamp2tm; affects behavior */
353 }
354
355 /*
356 * GetCurrentTimeUsec()
357 *
358 * Get the transaction start time ("now()") broken down as a struct pg_tm,
359 * including fractional seconds and timezone offset.
360 */
361 void
GetCurrentTimeUsec(struct pg_tm * tm,fsec_t * fsec,int * tzp)362 GetCurrentTimeUsec(struct pg_tm *tm, fsec_t *fsec, int *tzp)
363 {
364 int tz;
365
366 timestamp2tm(GetCurrentTransactionStartTimestamp(), &tz, tm, fsec,
367 NULL, NULL);
368 /* Note: don't pass NULL tzp to timestamp2tm; affects behavior */
369 if (tzp != NULL)
370 *tzp = tz;
371 }
372
373
374 /*
375 * Append seconds and fractional seconds (if any) at *cp.
376 *
377 * precision is the max number of fraction digits, fillzeros says to
378 * pad to two integral-seconds digits.
379 *
380 * Returns a pointer to the new end of string. No NUL terminator is put
381 * there; callers are responsible for NUL terminating str themselves.
382 *
383 * Note that any sign is stripped from the input seconds values.
384 */
385 static char *
AppendSeconds(char * cp,int sec,fsec_t fsec,int precision,bool fillzeros)386 AppendSeconds(char *cp, int sec, fsec_t fsec, int precision, bool fillzeros)
387 {
388 Assert(precision >= 0);
389
390 if (fillzeros)
391 cp = pg_ultostr_zeropad(cp, Abs(sec), 2);
392 else
393 cp = pg_ultostr(cp, Abs(sec));
394
395 /* fsec_t is just an int32 */
396 if (fsec != 0)
397 {
398 int32 value = Abs(fsec);
399 char *end = &cp[precision + 1];
400 bool gotnonzero = false;
401
402 *cp++ = '.';
403
404 /*
405 * Append the fractional seconds part. Note that we don't want any
406 * trailing zeros here, so since we're building the number in reverse
407 * we'll skip appending zeros until we've output a non-zero digit.
408 */
409 while (precision--)
410 {
411 int32 oldval = value;
412 int32 remainder;
413
414 value /= 10;
415 remainder = oldval - value * 10;
416
417 /* check if we got a non-zero */
418 if (remainder)
419 gotnonzero = true;
420
421 if (gotnonzero)
422 cp[precision] = '0' + remainder;
423 else
424 end = &cp[precision];
425 }
426
427 /*
428 * If we still have a non-zero value then precision must have not been
429 * enough to print the number. We punt the problem to pg_ltostr(),
430 * which will generate a correct answer in the minimum valid width.
431 */
432 if (value)
433 return pg_ultostr(cp, Abs(fsec));
434
435 return end;
436 }
437 else
438 return cp;
439 }
440
441
442 /*
443 * Variant of above that's specialized to timestamp case.
444 *
445 * Returns a pointer to the new end of string. No NUL terminator is put
446 * there; callers are responsible for NUL terminating str themselves.
447 */
448 static char *
AppendTimestampSeconds(char * cp,struct pg_tm * tm,fsec_t fsec)449 AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
450 {
451 return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
452 }
453
454 /*
455 * Multiply frac by scale (to produce seconds) and add to *tm & *fsec.
456 * We assume the input frac is less than 1 so overflow is not an issue.
457 */
458 static void
AdjustFractSeconds(double frac,struct pg_tm * tm,fsec_t * fsec,int scale)459 AdjustFractSeconds(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
460 {
461 int sec;
462
463 if (frac == 0)
464 return;
465 frac *= scale;
466 sec = (int) frac;
467 tm->tm_sec += sec;
468 frac -= sec;
469 *fsec += rint(frac * 1000000);
470 }
471
472 /* As above, but initial scale produces days */
473 static void
AdjustFractDays(double frac,struct pg_tm * tm,fsec_t * fsec,int scale)474 AdjustFractDays(double frac, struct pg_tm *tm, fsec_t *fsec, int scale)
475 {
476 int extra_days;
477
478 if (frac == 0)
479 return;
480 frac *= scale;
481 extra_days = (int) frac;
482 tm->tm_mday += extra_days;
483 frac -= extra_days;
484 AdjustFractSeconds(frac, tm, fsec, SECS_PER_DAY);
485 }
486
487 /* Fetch a fractional-second value with suitable error checking */
488 static int
ParseFractionalSecond(char * cp,fsec_t * fsec)489 ParseFractionalSecond(char *cp, fsec_t *fsec)
490 {
491 double frac;
492
493 /* Caller should always pass the start of the fraction part */
494 Assert(*cp == '.');
495 errno = 0;
496 frac = strtod(cp, &cp);
497 /* check for parse failure */
498 if (*cp != '\0' || errno != 0)
499 return DTERR_BAD_FORMAT;
500 *fsec = rint(frac * 1000000);
501 return 0;
502 }
503
504
505 /* ParseDateTime()
506 * Break string into tokens based on a date/time context.
507 * Returns 0 if successful, DTERR code if bogus input detected.
508 *
509 * timestr - the input string
510 * workbuf - workspace for field string storage. This must be
511 * larger than the largest legal input for this datetime type --
512 * some additional space will be needed to NUL terminate fields.
513 * buflen - the size of workbuf
514 * field[] - pointers to field strings are returned in this array
515 * ftype[] - field type indicators are returned in this array
516 * maxfields - dimensions of the above two arrays
517 * *numfields - set to the actual number of fields detected
518 *
519 * The fields extracted from the input are stored as separate,
520 * null-terminated strings in the workspace at workbuf. Any text is
521 * converted to lower case.
522 *
523 * Several field types are assigned:
524 * DTK_NUMBER - digits and (possibly) a decimal point
525 * DTK_DATE - digits and two delimiters, or digits and text
526 * DTK_TIME - digits, colon delimiters, and possibly a decimal point
527 * DTK_STRING - text (no digits or punctuation)
528 * DTK_SPECIAL - leading "+" or "-" followed by text
529 * DTK_TZ - leading "+" or "-" followed by digits (also eats ':', '.', '-')
530 *
531 * Note that some field types can hold unexpected items:
532 * DTK_NUMBER can hold date fields (yy.ddd)
533 * DTK_STRING can hold months (January) and time zones (PST)
534 * DTK_DATE can hold time zone names (America/New_York, GMT-8)
535 */
536 int
ParseDateTime(const char * timestr,char * workbuf,size_t buflen,char ** field,int * ftype,int maxfields,int * numfields)537 ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
538 char **field, int *ftype, int maxfields, int *numfields)
539 {
540 int nf = 0;
541 const char *cp = timestr;
542 char *bufp = workbuf;
543 const char *bufend = workbuf + buflen;
544
545 /*
546 * Set the character pointed-to by "bufptr" to "newchar", and increment
547 * "bufptr". "end" gives the end of the buffer -- we return an error if
548 * there is no space left to append a character to the buffer. Note that
549 * "bufptr" is evaluated twice.
550 */
551 #define APPEND_CHAR(bufptr, end, newchar) \
552 do \
553 { \
554 if (((bufptr) + 1) >= (end)) \
555 return DTERR_BAD_FORMAT; \
556 *(bufptr)++ = newchar; \
557 } while (0)
558
559 /* outer loop through fields */
560 while (*cp != '\0')
561 {
562 /* Ignore spaces between fields */
563 if (isspace((unsigned char) *cp))
564 {
565 cp++;
566 continue;
567 }
568
569 /* Record start of current field */
570 if (nf >= maxfields)
571 return DTERR_BAD_FORMAT;
572 field[nf] = bufp;
573
574 /* leading digit? then date or time */
575 if (isdigit((unsigned char) *cp))
576 {
577 APPEND_CHAR(bufp, bufend, *cp++);
578 while (isdigit((unsigned char) *cp))
579 APPEND_CHAR(bufp, bufend, *cp++);
580
581 /* time field? */
582 if (*cp == ':')
583 {
584 ftype[nf] = DTK_TIME;
585 APPEND_CHAR(bufp, bufend, *cp++);
586 while (isdigit((unsigned char) *cp) ||
587 (*cp == ':') || (*cp == '.'))
588 APPEND_CHAR(bufp, bufend, *cp++);
589 }
590 /* date field? allow embedded text month */
591 else if (*cp == '-' || *cp == '/' || *cp == '.')
592 {
593 /* save delimiting character to use later */
594 char delim = *cp;
595
596 APPEND_CHAR(bufp, bufend, *cp++);
597 /* second field is all digits? then no embedded text month */
598 if (isdigit((unsigned char) *cp))
599 {
600 ftype[nf] = ((delim == '.') ? DTK_NUMBER : DTK_DATE);
601 while (isdigit((unsigned char) *cp))
602 APPEND_CHAR(bufp, bufend, *cp++);
603
604 /*
605 * insist that the delimiters match to get a three-field
606 * date.
607 */
608 if (*cp == delim)
609 {
610 ftype[nf] = DTK_DATE;
611 APPEND_CHAR(bufp, bufend, *cp++);
612 while (isdigit((unsigned char) *cp) || *cp == delim)
613 APPEND_CHAR(bufp, bufend, *cp++);
614 }
615 }
616 else
617 {
618 ftype[nf] = DTK_DATE;
619 while (isalnum((unsigned char) *cp) || *cp == delim)
620 APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
621 }
622 }
623
624 /*
625 * otherwise, number only and will determine year, month, day, or
626 * concatenated fields later...
627 */
628 else
629 ftype[nf] = DTK_NUMBER;
630 }
631 /* Leading decimal point? Then fractional seconds... */
632 else if (*cp == '.')
633 {
634 APPEND_CHAR(bufp, bufend, *cp++);
635 while (isdigit((unsigned char) *cp))
636 APPEND_CHAR(bufp, bufend, *cp++);
637
638 ftype[nf] = DTK_NUMBER;
639 }
640
641 /*
642 * text? then date string, month, day of week, special, or timezone
643 */
644 else if (isalpha((unsigned char) *cp))
645 {
646 bool is_date;
647
648 ftype[nf] = DTK_STRING;
649 APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
650 while (isalpha((unsigned char) *cp))
651 APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
652
653 /*
654 * Dates can have embedded '-', '/', or '.' separators. It could
655 * also be a timezone name containing embedded '/', '+', '-', '_',
656 * or ':' (but '_' or ':' can't be the first punctuation). If the
657 * next character is a digit or '+', we need to check whether what
658 * we have so far is a recognized non-timezone keyword --- if so,
659 * don't believe that this is the start of a timezone.
660 */
661 is_date = false;
662 if (*cp == '-' || *cp == '/' || *cp == '.')
663 is_date = true;
664 else if (*cp == '+' || isdigit((unsigned char) *cp))
665 {
666 *bufp = '\0'; /* null-terminate current field value */
667 /* we need search only the core token table, not TZ names */
668 if (datebsearch(field[nf], datetktbl, szdatetktbl) == NULL)
669 is_date = true;
670 }
671 if (is_date)
672 {
673 ftype[nf] = DTK_DATE;
674 do
675 {
676 APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
677 } while (*cp == '+' || *cp == '-' ||
678 *cp == '/' || *cp == '_' ||
679 *cp == '.' || *cp == ':' ||
680 isalnum((unsigned char) *cp));
681 }
682 }
683 /* sign? then special or numeric timezone */
684 else if (*cp == '+' || *cp == '-')
685 {
686 APPEND_CHAR(bufp, bufend, *cp++);
687 /* soak up leading whitespace */
688 while (isspace((unsigned char) *cp))
689 cp++;
690 /* numeric timezone? */
691 /* note that "DTK_TZ" could also be a signed float or yyyy-mm */
692 if (isdigit((unsigned char) *cp))
693 {
694 ftype[nf] = DTK_TZ;
695 APPEND_CHAR(bufp, bufend, *cp++);
696 while (isdigit((unsigned char) *cp) ||
697 *cp == ':' || *cp == '.' || *cp == '-')
698 APPEND_CHAR(bufp, bufend, *cp++);
699 }
700 /* special? */
701 else if (isalpha((unsigned char) *cp))
702 {
703 ftype[nf] = DTK_SPECIAL;
704 APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
705 while (isalpha((unsigned char) *cp))
706 APPEND_CHAR(bufp, bufend, pg_tolower((unsigned char) *cp++));
707 }
708 /* otherwise something wrong... */
709 else
710 return DTERR_BAD_FORMAT;
711 }
712 /* ignore other punctuation but use as delimiter */
713 else if (ispunct((unsigned char) *cp))
714 {
715 cp++;
716 continue;
717 }
718 /* otherwise, something is not right... */
719 else
720 return DTERR_BAD_FORMAT;
721
722 /* force in a delimiter after each field */
723 *bufp++ = '\0';
724 nf++;
725 }
726
727 *numfields = nf;
728
729 return 0;
730 }
731
732
733 /* DecodeDateTime()
734 * Interpret previously parsed fields for general date and time.
735 * Return 0 if full date, 1 if only time, and negative DTERR code if problems.
736 * (Currently, all callers treat 1 as an error return too.)
737 *
738 * External format(s):
739 * "<weekday> <month>-<day>-<year> <hour>:<minute>:<second>"
740 * "Fri Feb-7-1997 15:23:27"
741 * "Feb-7-1997 15:23:27"
742 * "2-7-1997 15:23:27"
743 * "1997-2-7 15:23:27"
744 * "1997.038 15:23:27" (day of year 1-366)
745 * Also supports input in compact time:
746 * "970207 152327"
747 * "97038 152327"
748 * "20011225T040506.789-07"
749 *
750 * Use the system-provided functions to get the current time zone
751 * if not specified in the input string.
752 *
753 * If the date is outside the range of pg_time_t (in practice that could only
754 * happen if pg_time_t is just 32 bits), then assume UTC time zone - thomas
755 * 1997-05-27
756 */
757 int
DecodeDateTime(char ** field,int * ftype,int nf,int * dtype,struct pg_tm * tm,fsec_t * fsec,int * tzp)758 DecodeDateTime(char **field, int *ftype, int nf,
759 int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
760 {
761 int fmask = 0,
762 tmask,
763 type;
764 int ptype = 0; /* "prefix type" for ISO y2001m02d04 format */
765 int i;
766 int val;
767 int dterr;
768 int mer = HR24;
769 bool haveTextMonth = false;
770 bool isjulian = false;
771 bool is2digits = false;
772 bool bc = false;
773 pg_tz *namedTz = NULL;
774 pg_tz *abbrevTz = NULL;
775 pg_tz *valtz;
776 char *abbrev = NULL;
777 struct pg_tm cur_tm;
778
779 /*
780 * We'll insist on at least all of the date fields, but initialize the
781 * remaining fields in case they are not set later...
782 */
783 *dtype = DTK_DATE;
784 tm->tm_hour = 0;
785 tm->tm_min = 0;
786 tm->tm_sec = 0;
787 *fsec = 0;
788 /* don't know daylight savings time status apriori */
789 tm->tm_isdst = -1;
790 if (tzp != NULL)
791 *tzp = 0;
792
793 for (i = 0; i < nf; i++)
794 {
795 switch (ftype[i])
796 {
797 case DTK_DATE:
798
799 /*
800 * Integral julian day with attached time zone? All other
801 * forms with JD will be separated into distinct fields, so we
802 * handle just this case here.
803 */
804 if (ptype == DTK_JULIAN)
805 {
806 char *cp;
807 int val;
808
809 if (tzp == NULL)
810 return DTERR_BAD_FORMAT;
811
812 errno = 0;
813 val = strtoint(field[i], &cp, 10);
814 if (errno == ERANGE || val < 0)
815 return DTERR_FIELD_OVERFLOW;
816
817 j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
818 isjulian = true;
819
820 /* Get the time zone from the end of the string */
821 dterr = DecodeTimezone(cp, tzp);
822 if (dterr)
823 return dterr;
824
825 tmask = DTK_DATE_M | DTK_TIME_M | DTK_M(TZ);
826 ptype = 0;
827 break;
828 }
829
830 /*
831 * Already have a date? Then this might be a time zone name
832 * with embedded punctuation (e.g. "America/New_York") or a
833 * run-together time with trailing time zone (e.g. hhmmss-zz).
834 * - thomas 2001-12-25
835 *
836 * We consider it a time zone if we already have month & day.
837 * This is to allow the form "mmm dd hhmmss tz year", which
838 * we've historically accepted.
839 */
840 else if (ptype != 0 ||
841 ((fmask & (DTK_M(MONTH) | DTK_M(DAY))) ==
842 (DTK_M(MONTH) | DTK_M(DAY))))
843 {
844 /* No time zone accepted? Then quit... */
845 if (tzp == NULL)
846 return DTERR_BAD_FORMAT;
847
848 if (isdigit((unsigned char) *field[i]) || ptype != 0)
849 {
850 char *cp;
851
852 if (ptype != 0)
853 {
854 /* Sanity check; should not fail this test */
855 if (ptype != DTK_TIME)
856 return DTERR_BAD_FORMAT;
857 ptype = 0;
858 }
859
860 /*
861 * Starts with a digit but we already have a time
862 * field? Then we are in trouble with a date and time
863 * already...
864 */
865 if ((fmask & DTK_TIME_M) == DTK_TIME_M)
866 return DTERR_BAD_FORMAT;
867
868 if ((cp = strchr(field[i], '-')) == NULL)
869 return DTERR_BAD_FORMAT;
870
871 /* Get the time zone from the end of the string */
872 dterr = DecodeTimezone(cp, tzp);
873 if (dterr)
874 return dterr;
875 *cp = '\0';
876
877 /*
878 * Then read the rest of the field as a concatenated
879 * time
880 */
881 dterr = DecodeNumberField(strlen(field[i]), field[i],
882 fmask,
883 &tmask, tm,
884 fsec, &is2digits);
885 if (dterr < 0)
886 return dterr;
887
888 /*
889 * modify tmask after returning from
890 * DecodeNumberField()
891 */
892 tmask |= DTK_M(TZ);
893 }
894 else
895 {
896 namedTz = pg_tzset(field[i]);
897 if (!namedTz)
898 {
899 /*
900 * We should return an error code instead of
901 * ereport'ing directly, but then there is no way
902 * to report the bad time zone name.
903 */
904 ereport(ERROR,
905 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
906 errmsg("time zone \"%s\" not recognized",
907 field[i])));
908 }
909 /* we'll apply the zone setting below */
910 tmask = DTK_M(TZ);
911 }
912 }
913 else
914 {
915 dterr = DecodeDate(field[i], fmask,
916 &tmask, &is2digits, tm);
917 if (dterr)
918 return dterr;
919 }
920 break;
921
922 case DTK_TIME:
923
924 /*
925 * This might be an ISO time following a "t" field.
926 */
927 if (ptype != 0)
928 {
929 /* Sanity check; should not fail this test */
930 if (ptype != DTK_TIME)
931 return DTERR_BAD_FORMAT;
932 ptype = 0;
933 }
934 dterr = DecodeTime(field[i], fmask, INTERVAL_FULL_RANGE,
935 &tmask, tm, fsec);
936 if (dterr)
937 return dterr;
938
939 /* check for time overflow */
940 if (time_overflows(tm->tm_hour, tm->tm_min, tm->tm_sec,
941 *fsec))
942 return DTERR_FIELD_OVERFLOW;
943 break;
944
945 case DTK_TZ:
946 {
947 int tz;
948
949 if (tzp == NULL)
950 return DTERR_BAD_FORMAT;
951
952 dterr = DecodeTimezone(field[i], &tz);
953 if (dterr)
954 return dterr;
955 *tzp = tz;
956 tmask = DTK_M(TZ);
957 }
958 break;
959
960 case DTK_NUMBER:
961
962 /*
963 * Was this an "ISO date" with embedded field labels? An
964 * example is "y2001m02d04" - thomas 2001-02-04
965 */
966 if (ptype != 0)
967 {
968 char *cp;
969 int val;
970
971 errno = 0;
972 val = strtoint(field[i], &cp, 10);
973 if (errno == ERANGE)
974 return DTERR_FIELD_OVERFLOW;
975
976 /*
977 * only a few kinds are allowed to have an embedded
978 * decimal
979 */
980 if (*cp == '.')
981 switch (ptype)
982 {
983 case DTK_JULIAN:
984 case DTK_TIME:
985 case DTK_SECOND:
986 break;
987 default:
988 return DTERR_BAD_FORMAT;
989 break;
990 }
991 else if (*cp != '\0')
992 return DTERR_BAD_FORMAT;
993
994 switch (ptype)
995 {
996 case DTK_YEAR:
997 tm->tm_year = val;
998 tmask = DTK_M(YEAR);
999 break;
1000
1001 case DTK_MONTH:
1002
1003 /*
1004 * already have a month and hour? then assume
1005 * minutes
1006 */
1007 if ((fmask & DTK_M(MONTH)) != 0 &&
1008 (fmask & DTK_M(HOUR)) != 0)
1009 {
1010 tm->tm_min = val;
1011 tmask = DTK_M(MINUTE);
1012 }
1013 else
1014 {
1015 tm->tm_mon = val;
1016 tmask = DTK_M(MONTH);
1017 }
1018 break;
1019
1020 case DTK_DAY:
1021 tm->tm_mday = val;
1022 tmask = DTK_M(DAY);
1023 break;
1024
1025 case DTK_HOUR:
1026 tm->tm_hour = val;
1027 tmask = DTK_M(HOUR);
1028 break;
1029
1030 case DTK_MINUTE:
1031 tm->tm_min = val;
1032 tmask = DTK_M(MINUTE);
1033 break;
1034
1035 case DTK_SECOND:
1036 tm->tm_sec = val;
1037 tmask = DTK_M(SECOND);
1038 if (*cp == '.')
1039 {
1040 dterr = ParseFractionalSecond(cp, fsec);
1041 if (dterr)
1042 return dterr;
1043 tmask = DTK_ALL_SECS_M;
1044 }
1045 break;
1046
1047 case DTK_TZ:
1048 tmask = DTK_M(TZ);
1049 dterr = DecodeTimezone(field[i], tzp);
1050 if (dterr)
1051 return dterr;
1052 break;
1053
1054 case DTK_JULIAN:
1055 /* previous field was a label for "julian date" */
1056 if (val < 0)
1057 return DTERR_FIELD_OVERFLOW;
1058 tmask = DTK_DATE_M;
1059 j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
1060 isjulian = true;
1061
1062 /* fractional Julian Day? */
1063 if (*cp == '.')
1064 {
1065 double time;
1066
1067 errno = 0;
1068 time = strtod(cp, &cp);
1069 if (*cp != '\0' || errno != 0)
1070 return DTERR_BAD_FORMAT;
1071 time *= USECS_PER_DAY;
1072 dt2time(time,
1073 &tm->tm_hour, &tm->tm_min,
1074 &tm->tm_sec, fsec);
1075 tmask |= DTK_TIME_M;
1076 }
1077 break;
1078
1079 case DTK_TIME:
1080 /* previous field was "t" for ISO time */
1081 dterr = DecodeNumberField(strlen(field[i]), field[i],
1082 (fmask | DTK_DATE_M),
1083 &tmask, tm,
1084 fsec, &is2digits);
1085 if (dterr < 0)
1086 return dterr;
1087 if (tmask != DTK_TIME_M)
1088 return DTERR_BAD_FORMAT;
1089 break;
1090
1091 default:
1092 return DTERR_BAD_FORMAT;
1093 break;
1094 }
1095
1096 ptype = 0;
1097 *dtype = DTK_DATE;
1098 }
1099 else
1100 {
1101 char *cp;
1102 int flen;
1103
1104 flen = strlen(field[i]);
1105 cp = strchr(field[i], '.');
1106
1107 /* Embedded decimal and no date yet? */
1108 if (cp != NULL && !(fmask & DTK_DATE_M))
1109 {
1110 dterr = DecodeDate(field[i], fmask,
1111 &tmask, &is2digits, tm);
1112 if (dterr)
1113 return dterr;
1114 }
1115 /* embedded decimal and several digits before? */
1116 else if (cp != NULL && flen - strlen(cp) > 2)
1117 {
1118 /*
1119 * Interpret as a concatenated date or time Set the
1120 * type field to allow decoding other fields later.
1121 * Example: 20011223 or 040506
1122 */
1123 dterr = DecodeNumberField(flen, field[i], fmask,
1124 &tmask, tm,
1125 fsec, &is2digits);
1126 if (dterr < 0)
1127 return dterr;
1128 }
1129
1130 /*
1131 * Is this a YMD or HMS specification, or a year number?
1132 * YMD and HMS are required to be six digits or more, so
1133 * if it is 5 digits, it is a year. If it is six or more
1134 * digits, we assume it is YMD or HMS unless no date and
1135 * no time values have been specified. This forces 6+
1136 * digit years to be at the end of the string, or to use
1137 * the ISO date specification.
1138 */
1139 else if (flen >= 6 && (!(fmask & DTK_DATE_M) ||
1140 !(fmask & DTK_TIME_M)))
1141 {
1142 dterr = DecodeNumberField(flen, field[i], fmask,
1143 &tmask, tm,
1144 fsec, &is2digits);
1145 if (dterr < 0)
1146 return dterr;
1147 }
1148 /* otherwise it is a single date/time field... */
1149 else
1150 {
1151 dterr = DecodeNumber(flen, field[i],
1152 haveTextMonth, fmask,
1153 &tmask, tm,
1154 fsec, &is2digits);
1155 if (dterr)
1156 return dterr;
1157 }
1158 }
1159 break;
1160
1161 case DTK_STRING:
1162 case DTK_SPECIAL:
1163 /* timezone abbrevs take precedence over built-in tokens */
1164 type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz);
1165 if (type == UNKNOWN_FIELD)
1166 type = DecodeSpecial(i, field[i], &val);
1167 if (type == IGNORE_DTF)
1168 continue;
1169
1170 tmask = DTK_M(type);
1171 switch (type)
1172 {
1173 case RESERV:
1174 switch (val)
1175 {
1176 case DTK_NOW:
1177 tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ));
1178 *dtype = DTK_DATE;
1179 GetCurrentTimeUsec(tm, fsec, tzp);
1180 break;
1181
1182 case DTK_YESTERDAY:
1183 tmask = DTK_DATE_M;
1184 *dtype = DTK_DATE;
1185 GetCurrentDateTime(&cur_tm);
1186 j2date(date2j(cur_tm.tm_year, cur_tm.tm_mon, cur_tm.tm_mday) - 1,
1187 &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
1188 break;
1189
1190 case DTK_TODAY:
1191 tmask = DTK_DATE_M;
1192 *dtype = DTK_DATE;
1193 GetCurrentDateTime(&cur_tm);
1194 tm->tm_year = cur_tm.tm_year;
1195 tm->tm_mon = cur_tm.tm_mon;
1196 tm->tm_mday = cur_tm.tm_mday;
1197 break;
1198
1199 case DTK_TOMORROW:
1200 tmask = DTK_DATE_M;
1201 *dtype = DTK_DATE;
1202 GetCurrentDateTime(&cur_tm);
1203 j2date(date2j(cur_tm.tm_year, cur_tm.tm_mon, cur_tm.tm_mday) + 1,
1204 &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
1205 break;
1206
1207 case DTK_ZULU:
1208 tmask = (DTK_TIME_M | DTK_M(TZ));
1209 *dtype = DTK_DATE;
1210 tm->tm_hour = 0;
1211 tm->tm_min = 0;
1212 tm->tm_sec = 0;
1213 if (tzp != NULL)
1214 *tzp = 0;
1215 break;
1216
1217 default:
1218 *dtype = val;
1219 }
1220
1221 break;
1222
1223 case MONTH:
1224
1225 /*
1226 * already have a (numeric) month? then see if we can
1227 * substitute...
1228 */
1229 if ((fmask & DTK_M(MONTH)) && !haveTextMonth &&
1230 !(fmask & DTK_M(DAY)) && tm->tm_mon >= 1 &&
1231 tm->tm_mon <= 31)
1232 {
1233 tm->tm_mday = tm->tm_mon;
1234 tmask = DTK_M(DAY);
1235 }
1236 haveTextMonth = true;
1237 tm->tm_mon = val;
1238 break;
1239
1240 case DTZMOD:
1241
1242 /*
1243 * daylight savings time modifier (solves "MET DST"
1244 * syntax)
1245 */
1246 tmask |= DTK_M(DTZ);
1247 tm->tm_isdst = 1;
1248 if (tzp == NULL)
1249 return DTERR_BAD_FORMAT;
1250 *tzp -= val;
1251 break;
1252
1253 case DTZ:
1254
1255 /*
1256 * set mask for TZ here _or_ check for DTZ later when
1257 * getting default timezone
1258 */
1259 tmask |= DTK_M(TZ);
1260 tm->tm_isdst = 1;
1261 if (tzp == NULL)
1262 return DTERR_BAD_FORMAT;
1263 *tzp = -val;
1264 break;
1265
1266 case TZ:
1267 tm->tm_isdst = 0;
1268 if (tzp == NULL)
1269 return DTERR_BAD_FORMAT;
1270 *tzp = -val;
1271 break;
1272
1273 case DYNTZ:
1274 tmask |= DTK_M(TZ);
1275 if (tzp == NULL)
1276 return DTERR_BAD_FORMAT;
1277 /* we'll determine the actual offset later */
1278 abbrevTz = valtz;
1279 abbrev = field[i];
1280 break;
1281
1282 case AMPM:
1283 mer = val;
1284 break;
1285
1286 case ADBC:
1287 bc = (val == BC);
1288 break;
1289
1290 case DOW:
1291 tm->tm_wday = val;
1292 break;
1293
1294 case UNITS:
1295 tmask = 0;
1296 ptype = val;
1297 break;
1298
1299 case ISOTIME:
1300
1301 /*
1302 * This is a filler field "t" indicating that the next
1303 * field is time. Try to verify that this is sensible.
1304 */
1305 tmask = 0;
1306
1307 /* No preceding date? Then quit... */
1308 if ((fmask & DTK_DATE_M) != DTK_DATE_M)
1309 return DTERR_BAD_FORMAT;
1310
1311 /***
1312 * We will need one of the following fields:
1313 * DTK_NUMBER should be hhmmss.fff
1314 * DTK_TIME should be hh:mm:ss.fff
1315 * DTK_DATE should be hhmmss-zz
1316 ***/
1317 if (i >= nf - 1 ||
1318 (ftype[i + 1] != DTK_NUMBER &&
1319 ftype[i + 1] != DTK_TIME &&
1320 ftype[i + 1] != DTK_DATE))
1321 return DTERR_BAD_FORMAT;
1322
1323 ptype = val;
1324 break;
1325
1326 case UNKNOWN_FIELD:
1327
1328 /*
1329 * Before giving up and declaring error, check to see
1330 * if it is an all-alpha timezone name.
1331 */
1332 namedTz = pg_tzset(field[i]);
1333 if (!namedTz)
1334 return DTERR_BAD_FORMAT;
1335 /* we'll apply the zone setting below */
1336 tmask = DTK_M(TZ);
1337 break;
1338
1339 default:
1340 return DTERR_BAD_FORMAT;
1341 }
1342 break;
1343
1344 default:
1345 return DTERR_BAD_FORMAT;
1346 }
1347
1348 if (tmask & fmask)
1349 return DTERR_BAD_FORMAT;
1350 fmask |= tmask;
1351 } /* end loop over fields */
1352
1353 /* do final checking/adjustment of Y/M/D fields */
1354 dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm);
1355 if (dterr)
1356 return dterr;
1357
1358 /* handle AM/PM */
1359 if (mer != HR24 && tm->tm_hour > HOURS_PER_DAY / 2)
1360 return DTERR_FIELD_OVERFLOW;
1361 if (mer == AM && tm->tm_hour == HOURS_PER_DAY / 2)
1362 tm->tm_hour = 0;
1363 else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2)
1364 tm->tm_hour += HOURS_PER_DAY / 2;
1365
1366 /* do additional checking for full date specs... */
1367 if (*dtype == DTK_DATE)
1368 {
1369 if ((fmask & DTK_DATE_M) != DTK_DATE_M)
1370 {
1371 if ((fmask & DTK_TIME_M) == DTK_TIME_M)
1372 return 1;
1373 return DTERR_BAD_FORMAT;
1374 }
1375
1376 /*
1377 * If we had a full timezone spec, compute the offset (we could not do
1378 * it before, because we need the date to resolve DST status).
1379 */
1380 if (namedTz != NULL)
1381 {
1382 /* daylight savings time modifier disallowed with full TZ */
1383 if (fmask & DTK_M(DTZMOD))
1384 return DTERR_BAD_FORMAT;
1385
1386 *tzp = DetermineTimeZoneOffset(tm, namedTz);
1387 }
1388
1389 /*
1390 * Likewise, if we had a dynamic timezone abbreviation, resolve it
1391 * now.
1392 */
1393 if (abbrevTz != NULL)
1394 {
1395 /* daylight savings time modifier disallowed with dynamic TZ */
1396 if (fmask & DTK_M(DTZMOD))
1397 return DTERR_BAD_FORMAT;
1398
1399 *tzp = DetermineTimeZoneAbbrevOffset(tm, abbrev, abbrevTz);
1400 }
1401
1402 /* timezone not specified? then use session timezone */
1403 if (tzp != NULL && !(fmask & DTK_M(TZ)))
1404 {
1405 /*
1406 * daylight savings time modifier but no standard timezone? then
1407 * error
1408 */
1409 if (fmask & DTK_M(DTZMOD))
1410 return DTERR_BAD_FORMAT;
1411
1412 *tzp = DetermineTimeZoneOffset(tm, session_timezone);
1413 }
1414 }
1415
1416 return 0;
1417 }
1418
1419
1420 /* DetermineTimeZoneOffset()
1421 *
1422 * Given a struct pg_tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min,
1423 * and tm_sec fields are set, and a zic-style time zone definition, determine
1424 * the applicable GMT offset and daylight-savings status at that time.
1425 * Set the struct pg_tm's tm_isdst field accordingly, and return the GMT
1426 * offset as the function result.
1427 *
1428 * Note: if the date is out of the range we can deal with, we return zero
1429 * as the GMT offset and set tm_isdst = 0. We don't throw an error here,
1430 * though probably some higher-level code will.
1431 */
1432 int
DetermineTimeZoneOffset(struct pg_tm * tm,pg_tz * tzp)1433 DetermineTimeZoneOffset(struct pg_tm *tm, pg_tz *tzp)
1434 {
1435 pg_time_t t;
1436
1437 return DetermineTimeZoneOffsetInternal(tm, tzp, &t);
1438 }
1439
1440
1441 /* DetermineTimeZoneOffsetInternal()
1442 *
1443 * As above, but also return the actual UTC time imputed to the date/time
1444 * into *tp.
1445 *
1446 * In event of an out-of-range date, we punt by returning zero into *tp.
1447 * This is okay for the immediate callers but is a good reason for not
1448 * exposing this worker function globally.
1449 *
1450 * Note: it might seem that we should use mktime() for this, but bitter
1451 * experience teaches otherwise. This code is much faster than most versions
1452 * of mktime(), anyway.
1453 */
1454 static int
DetermineTimeZoneOffsetInternal(struct pg_tm * tm,pg_tz * tzp,pg_time_t * tp)1455 DetermineTimeZoneOffsetInternal(struct pg_tm *tm, pg_tz *tzp, pg_time_t *tp)
1456 {
1457 int date,
1458 sec;
1459 pg_time_t day,
1460 mytime,
1461 prevtime,
1462 boundary,
1463 beforetime,
1464 aftertime;
1465 long int before_gmtoff,
1466 after_gmtoff;
1467 int before_isdst,
1468 after_isdst;
1469 int res;
1470
1471 /*
1472 * First, generate the pg_time_t value corresponding to the given
1473 * y/m/d/h/m/s taken as GMT time. If this overflows, punt and decide the
1474 * timezone is GMT. (For a valid Julian date, integer overflow should be
1475 * impossible with 64-bit pg_time_t, but let's check for safety.)
1476 */
1477 if (!IS_VALID_JULIAN(tm->tm_year, tm->tm_mon, tm->tm_mday))
1478 goto overflow;
1479 date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - UNIX_EPOCH_JDATE;
1480
1481 day = ((pg_time_t) date) * SECS_PER_DAY;
1482 if (day / SECS_PER_DAY != date)
1483 goto overflow;
1484 sec = tm->tm_sec + (tm->tm_min + tm->tm_hour * MINS_PER_HOUR) * SECS_PER_MINUTE;
1485 mytime = day + sec;
1486 /* since sec >= 0, overflow could only be from +day to -mytime */
1487 if (mytime < 0 && day > 0)
1488 goto overflow;
1489
1490 /*
1491 * Find the DST time boundary just before or following the target time. We
1492 * assume that all zones have GMT offsets less than 24 hours, and that DST
1493 * boundaries can't be closer together than 48 hours, so backing up 24
1494 * hours and finding the "next" boundary will work.
1495 */
1496 prevtime = mytime - SECS_PER_DAY;
1497 if (mytime < 0 && prevtime > 0)
1498 goto overflow;
1499
1500 res = pg_next_dst_boundary(&prevtime,
1501 &before_gmtoff, &before_isdst,
1502 &boundary,
1503 &after_gmtoff, &after_isdst,
1504 tzp);
1505 if (res < 0)
1506 goto overflow; /* failure? */
1507
1508 if (res == 0)
1509 {
1510 /* Non-DST zone, life is simple */
1511 tm->tm_isdst = before_isdst;
1512 *tp = mytime - before_gmtoff;
1513 return -(int) before_gmtoff;
1514 }
1515
1516 /*
1517 * Form the candidate pg_time_t values with local-time adjustment
1518 */
1519 beforetime = mytime - before_gmtoff;
1520 if ((before_gmtoff > 0 &&
1521 mytime < 0 && beforetime > 0) ||
1522 (before_gmtoff <= 0 &&
1523 mytime > 0 && beforetime < 0))
1524 goto overflow;
1525 aftertime = mytime - after_gmtoff;
1526 if ((after_gmtoff > 0 &&
1527 mytime < 0 && aftertime > 0) ||
1528 (after_gmtoff <= 0 &&
1529 mytime > 0 && aftertime < 0))
1530 goto overflow;
1531
1532 /*
1533 * If both before or both after the boundary time, we know what to do. The
1534 * boundary time itself is considered to be after the transition, which
1535 * means we can accept aftertime == boundary in the second case.
1536 */
1537 if (beforetime < boundary && aftertime < boundary)
1538 {
1539 tm->tm_isdst = before_isdst;
1540 *tp = beforetime;
1541 return -(int) before_gmtoff;
1542 }
1543 if (beforetime > boundary && aftertime >= boundary)
1544 {
1545 tm->tm_isdst = after_isdst;
1546 *tp = aftertime;
1547 return -(int) after_gmtoff;
1548 }
1549
1550 /*
1551 * It's an invalid or ambiguous time due to timezone transition. In a
1552 * spring-forward transition, prefer the "before" interpretation; in a
1553 * fall-back transition, prefer "after". (We used to define and implement
1554 * this test as "prefer the standard-time interpretation", but that rule
1555 * does not help to resolve the behavior when both times are reported as
1556 * standard time; which does happen, eg Europe/Moscow in Oct 2014. Also,
1557 * in some zones such as Europe/Dublin, there is widespread confusion
1558 * about which time offset is "standard" time, so it's fortunate that our
1559 * behavior doesn't depend on that.)
1560 */
1561 if (beforetime > aftertime)
1562 {
1563 tm->tm_isdst = before_isdst;
1564 *tp = beforetime;
1565 return -(int) before_gmtoff;
1566 }
1567 tm->tm_isdst = after_isdst;
1568 *tp = aftertime;
1569 return -(int) after_gmtoff;
1570
1571 overflow:
1572 /* Given date is out of range, so assume UTC */
1573 tm->tm_isdst = 0;
1574 *tp = 0;
1575 return 0;
1576 }
1577
1578
1579 /* DetermineTimeZoneAbbrevOffset()
1580 *
1581 * Determine the GMT offset and DST flag to be attributed to a dynamic
1582 * time zone abbreviation, that is one whose meaning has changed over time.
1583 * *tm contains the local time at which the meaning should be determined,
1584 * and tm->tm_isdst receives the DST flag.
1585 *
1586 * This differs from the behavior of DetermineTimeZoneOffset() in that a
1587 * standard-time or daylight-time abbreviation forces use of the corresponding
1588 * GMT offset even when the zone was then in DS or standard time respectively.
1589 * (However, that happens only if we can match the given abbreviation to some
1590 * abbreviation that appears in the IANA timezone data. Otherwise, we fall
1591 * back to doing DetermineTimeZoneOffset().)
1592 */
1593 int
DetermineTimeZoneAbbrevOffset(struct pg_tm * tm,const char * abbr,pg_tz * tzp)1594 DetermineTimeZoneAbbrevOffset(struct pg_tm *tm, const char *abbr, pg_tz *tzp)
1595 {
1596 pg_time_t t;
1597 int zone_offset;
1598 int abbr_offset;
1599 int abbr_isdst;
1600
1601 /*
1602 * Compute the UTC time we want to probe at. (In event of overflow, we'll
1603 * probe at the epoch, which is a bit random but probably doesn't matter.)
1604 */
1605 zone_offset = DetermineTimeZoneOffsetInternal(tm, tzp, &t);
1606
1607 /*
1608 * Try to match the abbreviation to something in the zone definition.
1609 */
1610 if (DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp,
1611 &abbr_offset, &abbr_isdst))
1612 {
1613 /* Success, so use the abbrev-specific answers. */
1614 tm->tm_isdst = abbr_isdst;
1615 return abbr_offset;
1616 }
1617
1618 /*
1619 * No match, so use the answers we already got from
1620 * DetermineTimeZoneOffsetInternal.
1621 */
1622 return zone_offset;
1623 }
1624
1625
1626 /* DetermineTimeZoneAbbrevOffsetTS()
1627 *
1628 * As above but the probe time is specified as a TimestampTz (hence, UTC time),
1629 * and DST status is returned into *isdst rather than into tm->tm_isdst.
1630 */
1631 int
DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts,const char * abbr,pg_tz * tzp,int * isdst)1632 DetermineTimeZoneAbbrevOffsetTS(TimestampTz ts, const char *abbr,
1633 pg_tz *tzp, int *isdst)
1634 {
1635 pg_time_t t = timestamptz_to_time_t(ts);
1636 int zone_offset;
1637 int abbr_offset;
1638 int tz;
1639 struct pg_tm tm;
1640 fsec_t fsec;
1641
1642 /*
1643 * If the abbrev matches anything in the zone data, this is pretty easy.
1644 */
1645 if (DetermineTimeZoneAbbrevOffsetInternal(t, abbr, tzp,
1646 &abbr_offset, isdst))
1647 return abbr_offset;
1648
1649 /*
1650 * Else, break down the timestamp so we can use DetermineTimeZoneOffset.
1651 */
1652 if (timestamp2tm(ts, &tz, &tm, &fsec, NULL, tzp) != 0)
1653 ereport(ERROR,
1654 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
1655 errmsg("timestamp out of range")));
1656
1657 zone_offset = DetermineTimeZoneOffset(&tm, tzp);
1658 *isdst = tm.tm_isdst;
1659 return zone_offset;
1660 }
1661
1662
1663 /* DetermineTimeZoneAbbrevOffsetInternal()
1664 *
1665 * Workhorse for above two functions: work from a pg_time_t probe instant.
1666 * On success, return GMT offset and DST status into *offset and *isdst.
1667 */
1668 static bool
DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,const char * abbr,pg_tz * tzp,int * offset,int * isdst)1669 DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
1670 int *offset, int *isdst)
1671 {
1672 char upabbr[TZ_STRLEN_MAX + 1];
1673 unsigned char *p;
1674 long int gmtoff;
1675
1676 /* We need to force the abbrev to upper case */
1677 strlcpy(upabbr, abbr, sizeof(upabbr));
1678 for (p = (unsigned char *) upabbr; *p; p++)
1679 *p = pg_toupper(*p);
1680
1681 /* Look up the abbrev's meaning at this time in this zone */
1682 if (pg_interpret_timezone_abbrev(upabbr,
1683 &t,
1684 &gmtoff,
1685 isdst,
1686 tzp))
1687 {
1688 /* Change sign to agree with DetermineTimeZoneOffset() */
1689 *offset = (int) -gmtoff;
1690 return true;
1691 }
1692 return false;
1693 }
1694
1695
1696 /* DecodeTimeOnly()
1697 * Interpret parsed string as time fields only.
1698 * Returns 0 if successful, DTERR code if bogus input detected.
1699 *
1700 * Note that support for time zone is here for
1701 * SQL TIME WITH TIME ZONE, but it reveals
1702 * bogosity with SQL date/time standards, since
1703 * we must infer a time zone from current time.
1704 * - thomas 2000-03-10
1705 * Allow specifying date to get a better time zone,
1706 * if time zones are allowed. - thomas 2001-12-26
1707 */
1708 int
DecodeTimeOnly(char ** field,int * ftype,int nf,int * dtype,struct pg_tm * tm,fsec_t * fsec,int * tzp)1709 DecodeTimeOnly(char **field, int *ftype, int nf,
1710 int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp)
1711 {
1712 int fmask = 0,
1713 tmask,
1714 type;
1715 int ptype = 0; /* "prefix type" for ISO h04mm05s06 format */
1716 int i;
1717 int val;
1718 int dterr;
1719 bool isjulian = false;
1720 bool is2digits = false;
1721 bool bc = false;
1722 int mer = HR24;
1723 pg_tz *namedTz = NULL;
1724 pg_tz *abbrevTz = NULL;
1725 char *abbrev = NULL;
1726 pg_tz *valtz;
1727
1728 *dtype = DTK_TIME;
1729 tm->tm_hour = 0;
1730 tm->tm_min = 0;
1731 tm->tm_sec = 0;
1732 *fsec = 0;
1733 /* don't know daylight savings time status apriori */
1734 tm->tm_isdst = -1;
1735
1736 if (tzp != NULL)
1737 *tzp = 0;
1738
1739 for (i = 0; i < nf; i++)
1740 {
1741 switch (ftype[i])
1742 {
1743 case DTK_DATE:
1744
1745 /*
1746 * Time zone not allowed? Then should not accept dates or time
1747 * zones no matter what else!
1748 */
1749 if (tzp == NULL)
1750 return DTERR_BAD_FORMAT;
1751
1752 /* Under limited circumstances, we will accept a date... */
1753 if (i == 0 && nf >= 2 &&
1754 (ftype[nf - 1] == DTK_DATE || ftype[1] == DTK_TIME))
1755 {
1756 dterr = DecodeDate(field[i], fmask,
1757 &tmask, &is2digits, tm);
1758 if (dterr)
1759 return dterr;
1760 }
1761 /* otherwise, this is a time and/or time zone */
1762 else
1763 {
1764 if (isdigit((unsigned char) *field[i]))
1765 {
1766 char *cp;
1767
1768 /*
1769 * Starts with a digit but we already have a time
1770 * field? Then we are in trouble with time already...
1771 */
1772 if ((fmask & DTK_TIME_M) == DTK_TIME_M)
1773 return DTERR_BAD_FORMAT;
1774
1775 /*
1776 * Should not get here and fail. Sanity check only...
1777 */
1778 if ((cp = strchr(field[i], '-')) == NULL)
1779 return DTERR_BAD_FORMAT;
1780
1781 /* Get the time zone from the end of the string */
1782 dterr = DecodeTimezone(cp, tzp);
1783 if (dterr)
1784 return dterr;
1785 *cp = '\0';
1786
1787 /*
1788 * Then read the rest of the field as a concatenated
1789 * time
1790 */
1791 dterr = DecodeNumberField(strlen(field[i]), field[i],
1792 (fmask | DTK_DATE_M),
1793 &tmask, tm,
1794 fsec, &is2digits);
1795 if (dterr < 0)
1796 return dterr;
1797 ftype[i] = dterr;
1798
1799 tmask |= DTK_M(TZ);
1800 }
1801 else
1802 {
1803 namedTz = pg_tzset(field[i]);
1804 if (!namedTz)
1805 {
1806 /*
1807 * We should return an error code instead of
1808 * ereport'ing directly, but then there is no way
1809 * to report the bad time zone name.
1810 */
1811 ereport(ERROR,
1812 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1813 errmsg("time zone \"%s\" not recognized",
1814 field[i])));
1815 }
1816 /* we'll apply the zone setting below */
1817 ftype[i] = DTK_TZ;
1818 tmask = DTK_M(TZ);
1819 }
1820 }
1821 break;
1822
1823 case DTK_TIME:
1824 dterr = DecodeTime(field[i], (fmask | DTK_DATE_M),
1825 INTERVAL_FULL_RANGE,
1826 &tmask, tm, fsec);
1827 if (dterr)
1828 return dterr;
1829 break;
1830
1831 case DTK_TZ:
1832 {
1833 int tz;
1834
1835 if (tzp == NULL)
1836 return DTERR_BAD_FORMAT;
1837
1838 dterr = DecodeTimezone(field[i], &tz);
1839 if (dterr)
1840 return dterr;
1841 *tzp = tz;
1842 tmask = DTK_M(TZ);
1843 }
1844 break;
1845
1846 case DTK_NUMBER:
1847
1848 /*
1849 * Was this an "ISO time" with embedded field labels? An
1850 * example is "h04mm05s06" - thomas 2001-02-04
1851 */
1852 if (ptype != 0)
1853 {
1854 char *cp;
1855 int val;
1856
1857 /* Only accept a date under limited circumstances */
1858 switch (ptype)
1859 {
1860 case DTK_JULIAN:
1861 case DTK_YEAR:
1862 case DTK_MONTH:
1863 case DTK_DAY:
1864 if (tzp == NULL)
1865 return DTERR_BAD_FORMAT;
1866 default:
1867 break;
1868 }
1869
1870 errno = 0;
1871 val = strtoint(field[i], &cp, 10);
1872 if (errno == ERANGE)
1873 return DTERR_FIELD_OVERFLOW;
1874
1875 /*
1876 * only a few kinds are allowed to have an embedded
1877 * decimal
1878 */
1879 if (*cp == '.')
1880 switch (ptype)
1881 {
1882 case DTK_JULIAN:
1883 case DTK_TIME:
1884 case DTK_SECOND:
1885 break;
1886 default:
1887 return DTERR_BAD_FORMAT;
1888 break;
1889 }
1890 else if (*cp != '\0')
1891 return DTERR_BAD_FORMAT;
1892
1893 switch (ptype)
1894 {
1895 case DTK_YEAR:
1896 tm->tm_year = val;
1897 tmask = DTK_M(YEAR);
1898 break;
1899
1900 case DTK_MONTH:
1901
1902 /*
1903 * already have a month and hour? then assume
1904 * minutes
1905 */
1906 if ((fmask & DTK_M(MONTH)) != 0 &&
1907 (fmask & DTK_M(HOUR)) != 0)
1908 {
1909 tm->tm_min = val;
1910 tmask = DTK_M(MINUTE);
1911 }
1912 else
1913 {
1914 tm->tm_mon = val;
1915 tmask = DTK_M(MONTH);
1916 }
1917 break;
1918
1919 case DTK_DAY:
1920 tm->tm_mday = val;
1921 tmask = DTK_M(DAY);
1922 break;
1923
1924 case DTK_HOUR:
1925 tm->tm_hour = val;
1926 tmask = DTK_M(HOUR);
1927 break;
1928
1929 case DTK_MINUTE:
1930 tm->tm_min = val;
1931 tmask = DTK_M(MINUTE);
1932 break;
1933
1934 case DTK_SECOND:
1935 tm->tm_sec = val;
1936 tmask = DTK_M(SECOND);
1937 if (*cp == '.')
1938 {
1939 dterr = ParseFractionalSecond(cp, fsec);
1940 if (dterr)
1941 return dterr;
1942 tmask = DTK_ALL_SECS_M;
1943 }
1944 break;
1945
1946 case DTK_TZ:
1947 tmask = DTK_M(TZ);
1948 dterr = DecodeTimezone(field[i], tzp);
1949 if (dterr)
1950 return dterr;
1951 break;
1952
1953 case DTK_JULIAN:
1954 /* previous field was a label for "julian date" */
1955 if (val < 0)
1956 return DTERR_FIELD_OVERFLOW;
1957 tmask = DTK_DATE_M;
1958 j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
1959 isjulian = true;
1960
1961 if (*cp == '.')
1962 {
1963 double time;
1964
1965 errno = 0;
1966 time = strtod(cp, &cp);
1967 if (*cp != '\0' || errno != 0)
1968 return DTERR_BAD_FORMAT;
1969 time *= USECS_PER_DAY;
1970 dt2time(time,
1971 &tm->tm_hour, &tm->tm_min,
1972 &tm->tm_sec, fsec);
1973 tmask |= DTK_TIME_M;
1974 }
1975 break;
1976
1977 case DTK_TIME:
1978 /* previous field was "t" for ISO time */
1979 dterr = DecodeNumberField(strlen(field[i]), field[i],
1980 (fmask | DTK_DATE_M),
1981 &tmask, tm,
1982 fsec, &is2digits);
1983 if (dterr < 0)
1984 return dterr;
1985 ftype[i] = dterr;
1986
1987 if (tmask != DTK_TIME_M)
1988 return DTERR_BAD_FORMAT;
1989 break;
1990
1991 default:
1992 return DTERR_BAD_FORMAT;
1993 break;
1994 }
1995
1996 ptype = 0;
1997 *dtype = DTK_DATE;
1998 }
1999 else
2000 {
2001 char *cp;
2002 int flen;
2003
2004 flen = strlen(field[i]);
2005 cp = strchr(field[i], '.');
2006
2007 /* Embedded decimal? */
2008 if (cp != NULL)
2009 {
2010 /*
2011 * Under limited circumstances, we will accept a
2012 * date...
2013 */
2014 if (i == 0 && nf >= 2 && ftype[nf - 1] == DTK_DATE)
2015 {
2016 dterr = DecodeDate(field[i], fmask,
2017 &tmask, &is2digits, tm);
2018 if (dterr)
2019 return dterr;
2020 }
2021 /* embedded decimal and several digits before? */
2022 else if (flen - strlen(cp) > 2)
2023 {
2024 /*
2025 * Interpret as a concatenated date or time Set
2026 * the type field to allow decoding other fields
2027 * later. Example: 20011223 or 040506
2028 */
2029 dterr = DecodeNumberField(flen, field[i],
2030 (fmask | DTK_DATE_M),
2031 &tmask, tm,
2032 fsec, &is2digits);
2033 if (dterr < 0)
2034 return dterr;
2035 ftype[i] = dterr;
2036 }
2037 else
2038 return DTERR_BAD_FORMAT;
2039 }
2040 else if (flen > 4)
2041 {
2042 dterr = DecodeNumberField(flen, field[i],
2043 (fmask | DTK_DATE_M),
2044 &tmask, tm,
2045 fsec, &is2digits);
2046 if (dterr < 0)
2047 return dterr;
2048 ftype[i] = dterr;
2049 }
2050 /* otherwise it is a single date/time field... */
2051 else
2052 {
2053 dterr = DecodeNumber(flen, field[i],
2054 false,
2055 (fmask | DTK_DATE_M),
2056 &tmask, tm,
2057 fsec, &is2digits);
2058 if (dterr)
2059 return dterr;
2060 }
2061 }
2062 break;
2063
2064 case DTK_STRING:
2065 case DTK_SPECIAL:
2066 /* timezone abbrevs take precedence over built-in tokens */
2067 type = DecodeTimezoneAbbrev(i, field[i], &val, &valtz);
2068 if (type == UNKNOWN_FIELD)
2069 type = DecodeSpecial(i, field[i], &val);
2070 if (type == IGNORE_DTF)
2071 continue;
2072
2073 tmask = DTK_M(type);
2074 switch (type)
2075 {
2076 case RESERV:
2077 switch (val)
2078 {
2079 case DTK_NOW:
2080 tmask = DTK_TIME_M;
2081 *dtype = DTK_TIME;
2082 GetCurrentTimeUsec(tm, fsec, NULL);
2083 break;
2084
2085 case DTK_ZULU:
2086 tmask = (DTK_TIME_M | DTK_M(TZ));
2087 *dtype = DTK_TIME;
2088 tm->tm_hour = 0;
2089 tm->tm_min = 0;
2090 tm->tm_sec = 0;
2091 tm->tm_isdst = 0;
2092 break;
2093
2094 default:
2095 return DTERR_BAD_FORMAT;
2096 }
2097
2098 break;
2099
2100 case DTZMOD:
2101
2102 /*
2103 * daylight savings time modifier (solves "MET DST"
2104 * syntax)
2105 */
2106 tmask |= DTK_M(DTZ);
2107 tm->tm_isdst = 1;
2108 if (tzp == NULL)
2109 return DTERR_BAD_FORMAT;
2110 *tzp -= val;
2111 break;
2112
2113 case DTZ:
2114
2115 /*
2116 * set mask for TZ here _or_ check for DTZ later when
2117 * getting default timezone
2118 */
2119 tmask |= DTK_M(TZ);
2120 tm->tm_isdst = 1;
2121 if (tzp == NULL)
2122 return DTERR_BAD_FORMAT;
2123 *tzp = -val;
2124 ftype[i] = DTK_TZ;
2125 break;
2126
2127 case TZ:
2128 tm->tm_isdst = 0;
2129 if (tzp == NULL)
2130 return DTERR_BAD_FORMAT;
2131 *tzp = -val;
2132 ftype[i] = DTK_TZ;
2133 break;
2134
2135 case DYNTZ:
2136 tmask |= DTK_M(TZ);
2137 if (tzp == NULL)
2138 return DTERR_BAD_FORMAT;
2139 /* we'll determine the actual offset later */
2140 abbrevTz = valtz;
2141 abbrev = field[i];
2142 ftype[i] = DTK_TZ;
2143 break;
2144
2145 case AMPM:
2146 mer = val;
2147 break;
2148
2149 case ADBC:
2150 bc = (val == BC);
2151 break;
2152
2153 case UNITS:
2154 tmask = 0;
2155 ptype = val;
2156 break;
2157
2158 case ISOTIME:
2159 tmask = 0;
2160
2161 /***
2162 * We will need one of the following fields:
2163 * DTK_NUMBER should be hhmmss.fff
2164 * DTK_TIME should be hh:mm:ss.fff
2165 * DTK_DATE should be hhmmss-zz
2166 ***/
2167 if (i >= nf - 1 ||
2168 (ftype[i + 1] != DTK_NUMBER &&
2169 ftype[i + 1] != DTK_TIME &&
2170 ftype[i + 1] != DTK_DATE))
2171 return DTERR_BAD_FORMAT;
2172
2173 ptype = val;
2174 break;
2175
2176 case UNKNOWN_FIELD:
2177
2178 /*
2179 * Before giving up and declaring error, check to see
2180 * if it is an all-alpha timezone name.
2181 */
2182 namedTz = pg_tzset(field[i]);
2183 if (!namedTz)
2184 return DTERR_BAD_FORMAT;
2185 /* we'll apply the zone setting below */
2186 tmask = DTK_M(TZ);
2187 break;
2188
2189 default:
2190 return DTERR_BAD_FORMAT;
2191 }
2192 break;
2193
2194 default:
2195 return DTERR_BAD_FORMAT;
2196 }
2197
2198 if (tmask & fmask)
2199 return DTERR_BAD_FORMAT;
2200 fmask |= tmask;
2201 } /* end loop over fields */
2202
2203 /* do final checking/adjustment of Y/M/D fields */
2204 dterr = ValidateDate(fmask, isjulian, is2digits, bc, tm);
2205 if (dterr)
2206 return dterr;
2207
2208 /* handle AM/PM */
2209 if (mer != HR24 && tm->tm_hour > HOURS_PER_DAY / 2)
2210 return DTERR_FIELD_OVERFLOW;
2211 if (mer == AM && tm->tm_hour == HOURS_PER_DAY / 2)
2212 tm->tm_hour = 0;
2213 else if (mer == PM && tm->tm_hour != HOURS_PER_DAY / 2)
2214 tm->tm_hour += HOURS_PER_DAY / 2;
2215
2216 /* check for time overflow */
2217 if (time_overflows(tm->tm_hour, tm->tm_min, tm->tm_sec, *fsec))
2218 return DTERR_FIELD_OVERFLOW;
2219
2220 if ((fmask & DTK_TIME_M) != DTK_TIME_M)
2221 return DTERR_BAD_FORMAT;
2222
2223 /*
2224 * If we had a full timezone spec, compute the offset (we could not do it
2225 * before, because we may need the date to resolve DST status).
2226 */
2227 if (namedTz != NULL)
2228 {
2229 long int gmtoff;
2230
2231 /* daylight savings time modifier disallowed with full TZ */
2232 if (fmask & DTK_M(DTZMOD))
2233 return DTERR_BAD_FORMAT;
2234
2235 /* if non-DST zone, we do not need to know the date */
2236 if (pg_get_timezone_offset(namedTz, &gmtoff))
2237 {
2238 *tzp = -(int) gmtoff;
2239 }
2240 else
2241 {
2242 /* a date has to be specified */
2243 if ((fmask & DTK_DATE_M) != DTK_DATE_M)
2244 return DTERR_BAD_FORMAT;
2245 *tzp = DetermineTimeZoneOffset(tm, namedTz);
2246 }
2247 }
2248
2249 /*
2250 * Likewise, if we had a dynamic timezone abbreviation, resolve it now.
2251 */
2252 if (abbrevTz != NULL)
2253 {
2254 struct pg_tm tt,
2255 *tmp = &tt;
2256
2257 /*
2258 * daylight savings time modifier but no standard timezone? then error
2259 */
2260 if (fmask & DTK_M(DTZMOD))
2261 return DTERR_BAD_FORMAT;
2262
2263 if ((fmask & DTK_DATE_M) == 0)
2264 GetCurrentDateTime(tmp);
2265 else
2266 {
2267 /* a date has to be specified */
2268 if ((fmask & DTK_DATE_M) != DTK_DATE_M)
2269 return DTERR_BAD_FORMAT;
2270 tmp->tm_year = tm->tm_year;
2271 tmp->tm_mon = tm->tm_mon;
2272 tmp->tm_mday = tm->tm_mday;
2273 }
2274 tmp->tm_hour = tm->tm_hour;
2275 tmp->tm_min = tm->tm_min;
2276 tmp->tm_sec = tm->tm_sec;
2277 *tzp = DetermineTimeZoneAbbrevOffset(tmp, abbrev, abbrevTz);
2278 tm->tm_isdst = tmp->tm_isdst;
2279 }
2280
2281 /* timezone not specified? then use session timezone */
2282 if (tzp != NULL && !(fmask & DTK_M(TZ)))
2283 {
2284 struct pg_tm tt,
2285 *tmp = &tt;
2286
2287 /*
2288 * daylight savings time modifier but no standard timezone? then error
2289 */
2290 if (fmask & DTK_M(DTZMOD))
2291 return DTERR_BAD_FORMAT;
2292
2293 if ((fmask & DTK_DATE_M) == 0)
2294 GetCurrentDateTime(tmp);
2295 else
2296 {
2297 /* a date has to be specified */
2298 if ((fmask & DTK_DATE_M) != DTK_DATE_M)
2299 return DTERR_BAD_FORMAT;
2300 tmp->tm_year = tm->tm_year;
2301 tmp->tm_mon = tm->tm_mon;
2302 tmp->tm_mday = tm->tm_mday;
2303 }
2304 tmp->tm_hour = tm->tm_hour;
2305 tmp->tm_min = tm->tm_min;
2306 tmp->tm_sec = tm->tm_sec;
2307 *tzp = DetermineTimeZoneOffset(tmp, session_timezone);
2308 tm->tm_isdst = tmp->tm_isdst;
2309 }
2310
2311 return 0;
2312 }
2313
2314 /* DecodeDate()
2315 * Decode date string which includes delimiters.
2316 * Return 0 if okay, a DTERR code if not.
2317 *
2318 * str: field to be parsed
2319 * fmask: bitmask for field types already seen
2320 * *tmask: receives bitmask for fields found here
2321 * *is2digits: set to true if we find 2-digit year
2322 * *tm: field values are stored into appropriate members of this struct
2323 */
2324 static int
DecodeDate(char * str,int fmask,int * tmask,bool * is2digits,struct pg_tm * tm)2325 DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
2326 struct pg_tm *tm)
2327 {
2328 fsec_t fsec;
2329 int nf = 0;
2330 int i,
2331 len;
2332 int dterr;
2333 bool haveTextMonth = false;
2334 int type,
2335 val,
2336 dmask = 0;
2337 char *field[MAXDATEFIELDS];
2338
2339 *tmask = 0;
2340
2341 /* parse this string... */
2342 while (*str != '\0' && nf < MAXDATEFIELDS)
2343 {
2344 /* skip field separators */
2345 while (*str != '\0' && !isalnum((unsigned char) *str))
2346 str++;
2347
2348 if (*str == '\0')
2349 return DTERR_BAD_FORMAT; /* end of string after separator */
2350
2351 field[nf] = str;
2352 if (isdigit((unsigned char) *str))
2353 {
2354 while (isdigit((unsigned char) *str))
2355 str++;
2356 }
2357 else if (isalpha((unsigned char) *str))
2358 {
2359 while (isalpha((unsigned char) *str))
2360 str++;
2361 }
2362
2363 /* Just get rid of any non-digit, non-alpha characters... */
2364 if (*str != '\0')
2365 *str++ = '\0';
2366 nf++;
2367 }
2368
2369 /* look first for text fields, since that will be unambiguous month */
2370 for (i = 0; i < nf; i++)
2371 {
2372 if (isalpha((unsigned char) *field[i]))
2373 {
2374 type = DecodeSpecial(i, field[i], &val);
2375 if (type == IGNORE_DTF)
2376 continue;
2377
2378 dmask = DTK_M(type);
2379 switch (type)
2380 {
2381 case MONTH:
2382 tm->tm_mon = val;
2383 haveTextMonth = true;
2384 break;
2385
2386 default:
2387 return DTERR_BAD_FORMAT;
2388 }
2389 if (fmask & dmask)
2390 return DTERR_BAD_FORMAT;
2391
2392 fmask |= dmask;
2393 *tmask |= dmask;
2394
2395 /* mark this field as being completed */
2396 field[i] = NULL;
2397 }
2398 }
2399
2400 /* now pick up remaining numeric fields */
2401 for (i = 0; i < nf; i++)
2402 {
2403 if (field[i] == NULL)
2404 continue;
2405
2406 if ((len = strlen(field[i])) <= 0)
2407 return DTERR_BAD_FORMAT;
2408
2409 dterr = DecodeNumber(len, field[i], haveTextMonth, fmask,
2410 &dmask, tm,
2411 &fsec, is2digits);
2412 if (dterr)
2413 return dterr;
2414
2415 if (fmask & dmask)
2416 return DTERR_BAD_FORMAT;
2417
2418 fmask |= dmask;
2419 *tmask |= dmask;
2420 }
2421
2422 if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M)
2423 return DTERR_BAD_FORMAT;
2424
2425 /* validation of the field values must wait until ValidateDate() */
2426
2427 return 0;
2428 }
2429
2430 /* ValidateDate()
2431 * Check valid year/month/day values, handle BC and DOY cases
2432 * Return 0 if okay, a DTERR code if not.
2433 */
2434 int
ValidateDate(int fmask,bool isjulian,bool is2digits,bool bc,struct pg_tm * tm)2435 ValidateDate(int fmask, bool isjulian, bool is2digits, bool bc,
2436 struct pg_tm *tm)
2437 {
2438 if (fmask & DTK_M(YEAR))
2439 {
2440 if (isjulian)
2441 {
2442 /* tm_year is correct and should not be touched */
2443 }
2444 else if (bc)
2445 {
2446 /* there is no year zero in AD/BC notation */
2447 if (tm->tm_year <= 0)
2448 return DTERR_FIELD_OVERFLOW;
2449 /* internally, we represent 1 BC as year zero, 2 BC as -1, etc */
2450 tm->tm_year = -(tm->tm_year - 1);
2451 }
2452 else if (is2digits)
2453 {
2454 /* process 1 or 2-digit input as 1970-2069 AD, allow '0' and '00' */
2455 if (tm->tm_year < 0) /* just paranoia */
2456 return DTERR_FIELD_OVERFLOW;
2457 if (tm->tm_year < 70)
2458 tm->tm_year += 2000;
2459 else if (tm->tm_year < 100)
2460 tm->tm_year += 1900;
2461 }
2462 else
2463 {
2464 /* there is no year zero in AD/BC notation */
2465 if (tm->tm_year <= 0)
2466 return DTERR_FIELD_OVERFLOW;
2467 }
2468 }
2469
2470 /* now that we have correct year, decode DOY */
2471 if (fmask & DTK_M(DOY))
2472 {
2473 j2date(date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1,
2474 &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
2475 }
2476
2477 /* check for valid month */
2478 if (fmask & DTK_M(MONTH))
2479 {
2480 if (tm->tm_mon < 1 || tm->tm_mon > MONTHS_PER_YEAR)
2481 return DTERR_MD_FIELD_OVERFLOW;
2482 }
2483
2484 /* minimal check for valid day */
2485 if (fmask & DTK_M(DAY))
2486 {
2487 if (tm->tm_mday < 1 || tm->tm_mday > 31)
2488 return DTERR_MD_FIELD_OVERFLOW;
2489 }
2490
2491 if ((fmask & DTK_DATE_M) == DTK_DATE_M)
2492 {
2493 /*
2494 * Check for valid day of month, now that we know for sure the month
2495 * and year. Note we don't use MD_FIELD_OVERFLOW here, since it seems
2496 * unlikely that "Feb 29" is a YMD-order error.
2497 */
2498 if (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1])
2499 return DTERR_FIELD_OVERFLOW;
2500 }
2501
2502 return 0;
2503 }
2504
2505
2506 /* DecodeTime()
2507 * Decode time string which includes delimiters.
2508 * Return 0 if okay, a DTERR code if not.
2509 *
2510 * Only check the lower limit on hours, since this same code can be
2511 * used to represent time spans.
2512 */
2513 static int
DecodeTime(char * str,int fmask,int range,int * tmask,struct pg_tm * tm,fsec_t * fsec)2514 DecodeTime(char *str, int fmask, int range,
2515 int *tmask, struct pg_tm *tm, fsec_t *fsec)
2516 {
2517 char *cp;
2518 int dterr;
2519
2520 *tmask = DTK_TIME_M;
2521
2522 errno = 0;
2523 tm->tm_hour = strtoint(str, &cp, 10);
2524 if (errno == ERANGE)
2525 return DTERR_FIELD_OVERFLOW;
2526 if (*cp != ':')
2527 return DTERR_BAD_FORMAT;
2528 errno = 0;
2529 tm->tm_min = strtoint(cp + 1, &cp, 10);
2530 if (errno == ERANGE)
2531 return DTERR_FIELD_OVERFLOW;
2532 if (*cp == '\0')
2533 {
2534 tm->tm_sec = 0;
2535 *fsec = 0;
2536 /* If it's a MINUTE TO SECOND interval, take 2 fields as being mm:ss */
2537 if (range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND)))
2538 {
2539 tm->tm_sec = tm->tm_min;
2540 tm->tm_min = tm->tm_hour;
2541 tm->tm_hour = 0;
2542 }
2543 }
2544 else if (*cp == '.')
2545 {
2546 /* always assume mm:ss.sss is MINUTE TO SECOND */
2547 dterr = ParseFractionalSecond(cp, fsec);
2548 if (dterr)
2549 return dterr;
2550 tm->tm_sec = tm->tm_min;
2551 tm->tm_min = tm->tm_hour;
2552 tm->tm_hour = 0;
2553 }
2554 else if (*cp == ':')
2555 {
2556 errno = 0;
2557 tm->tm_sec = strtoint(cp + 1, &cp, 10);
2558 if (errno == ERANGE)
2559 return DTERR_FIELD_OVERFLOW;
2560 if (*cp == '\0')
2561 *fsec = 0;
2562 else if (*cp == '.')
2563 {
2564 dterr = ParseFractionalSecond(cp, fsec);
2565 if (dterr)
2566 return dterr;
2567 }
2568 else
2569 return DTERR_BAD_FORMAT;
2570 }
2571 else
2572 return DTERR_BAD_FORMAT;
2573
2574 /* do a sanity check */
2575 if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_min > MINS_PER_HOUR - 1 ||
2576 tm->tm_sec < 0 || tm->tm_sec > SECS_PER_MINUTE ||
2577 *fsec < INT64CONST(0) ||
2578 *fsec > USECS_PER_SEC)
2579 return DTERR_FIELD_OVERFLOW;
2580
2581 return 0;
2582 }
2583
2584
2585 /* DecodeNumber()
2586 * Interpret plain numeric field as a date value in context.
2587 * Return 0 if okay, a DTERR code if not.
2588 */
2589 static int
DecodeNumber(int flen,char * str,bool haveTextMonth,int fmask,int * tmask,struct pg_tm * tm,fsec_t * fsec,bool * is2digits)2590 DecodeNumber(int flen, char *str, bool haveTextMonth, int fmask,
2591 int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits)
2592 {
2593 int val;
2594 char *cp;
2595 int dterr;
2596
2597 *tmask = 0;
2598
2599 errno = 0;
2600 val = strtoint(str, &cp, 10);
2601 if (errno == ERANGE)
2602 return DTERR_FIELD_OVERFLOW;
2603 if (cp == str)
2604 return DTERR_BAD_FORMAT;
2605
2606 if (*cp == '.')
2607 {
2608 /*
2609 * More than two digits before decimal point? Then could be a date or
2610 * a run-together time: 2001.360 20011225 040506.789
2611 */
2612 if (cp - str > 2)
2613 {
2614 dterr = DecodeNumberField(flen, str,
2615 (fmask | DTK_DATE_M),
2616 tmask, tm,
2617 fsec, is2digits);
2618 if (dterr < 0)
2619 return dterr;
2620 return 0;
2621 }
2622
2623 dterr = ParseFractionalSecond(cp, fsec);
2624 if (dterr)
2625 return dterr;
2626 }
2627 else if (*cp != '\0')
2628 return DTERR_BAD_FORMAT;
2629
2630 /* Special case for day of year */
2631 if (flen == 3 && (fmask & DTK_DATE_M) == DTK_M(YEAR) && val >= 1 &&
2632 val <= 366)
2633 {
2634 *tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
2635 tm->tm_yday = val;
2636 /* tm_mon and tm_mday can't actually be set yet ... */
2637 return 0;
2638 }
2639
2640 /* Switch based on what we have so far */
2641 switch (fmask & DTK_DATE_M)
2642 {
2643 case 0:
2644
2645 /*
2646 * Nothing so far; make a decision about what we think the input
2647 * is. There used to be lots of heuristics here, but the
2648 * consensus now is to be paranoid. It *must* be either
2649 * YYYY-MM-DD (with a more-than-two-digit year field), or the
2650 * field order defined by DateOrder.
2651 */
2652 if (flen >= 3 || DateOrder == DATEORDER_YMD)
2653 {
2654 *tmask = DTK_M(YEAR);
2655 tm->tm_year = val;
2656 }
2657 else if (DateOrder == DATEORDER_DMY)
2658 {
2659 *tmask = DTK_M(DAY);
2660 tm->tm_mday = val;
2661 }
2662 else
2663 {
2664 *tmask = DTK_M(MONTH);
2665 tm->tm_mon = val;
2666 }
2667 break;
2668
2669 case (DTK_M(YEAR)):
2670 /* Must be at second field of YY-MM-DD */
2671 *tmask = DTK_M(MONTH);
2672 tm->tm_mon = val;
2673 break;
2674
2675 case (DTK_M(MONTH)):
2676 if (haveTextMonth)
2677 {
2678 /*
2679 * We are at the first numeric field of a date that included a
2680 * textual month name. We want to support the variants
2681 * MON-DD-YYYY, DD-MON-YYYY, and YYYY-MON-DD as unambiguous
2682 * inputs. We will also accept MON-DD-YY or DD-MON-YY in
2683 * either DMY or MDY modes, as well as YY-MON-DD in YMD mode.
2684 */
2685 if (flen >= 3 || DateOrder == DATEORDER_YMD)
2686 {
2687 *tmask = DTK_M(YEAR);
2688 tm->tm_year = val;
2689 }
2690 else
2691 {
2692 *tmask = DTK_M(DAY);
2693 tm->tm_mday = val;
2694 }
2695 }
2696 else
2697 {
2698 /* Must be at second field of MM-DD-YY */
2699 *tmask = DTK_M(DAY);
2700 tm->tm_mday = val;
2701 }
2702 break;
2703
2704 case (DTK_M(YEAR) | DTK_M(MONTH)):
2705 if (haveTextMonth)
2706 {
2707 /* Need to accept DD-MON-YYYY even in YMD mode */
2708 if (flen >= 3 && *is2digits)
2709 {
2710 /* Guess that first numeric field is day was wrong */
2711 *tmask = DTK_M(DAY); /* YEAR is already set */
2712 tm->tm_mday = tm->tm_year;
2713 tm->tm_year = val;
2714 *is2digits = false;
2715 }
2716 else
2717 {
2718 *tmask = DTK_M(DAY);
2719 tm->tm_mday = val;
2720 }
2721 }
2722 else
2723 {
2724 /* Must be at third field of YY-MM-DD */
2725 *tmask = DTK_M(DAY);
2726 tm->tm_mday = val;
2727 }
2728 break;
2729
2730 case (DTK_M(DAY)):
2731 /* Must be at second field of DD-MM-YY */
2732 *tmask = DTK_M(MONTH);
2733 tm->tm_mon = val;
2734 break;
2735
2736 case (DTK_M(MONTH) | DTK_M(DAY)):
2737 /* Must be at third field of DD-MM-YY or MM-DD-YY */
2738 *tmask = DTK_M(YEAR);
2739 tm->tm_year = val;
2740 break;
2741
2742 case (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY)):
2743 /* we have all the date, so it must be a time field */
2744 dterr = DecodeNumberField(flen, str, fmask,
2745 tmask, tm,
2746 fsec, is2digits);
2747 if (dterr < 0)
2748 return dterr;
2749 return 0;
2750
2751 default:
2752 /* Anything else is bogus input */
2753 return DTERR_BAD_FORMAT;
2754 }
2755
2756 /*
2757 * When processing a year field, mark it for adjustment if it's only one
2758 * or two digits.
2759 */
2760 if (*tmask == DTK_M(YEAR))
2761 *is2digits = (flen <= 2);
2762
2763 return 0;
2764 }
2765
2766
2767 /* DecodeNumberField()
2768 * Interpret numeric string as a concatenated date or time field.
2769 * Return a DTK token (>= 0) if successful, a DTERR code (< 0) if not.
2770 *
2771 * Use the context of previously decoded fields to help with
2772 * the interpretation.
2773 */
2774 static int
DecodeNumberField(int len,char * str,int fmask,int * tmask,struct pg_tm * tm,fsec_t * fsec,bool * is2digits)2775 DecodeNumberField(int len, char *str, int fmask,
2776 int *tmask, struct pg_tm *tm, fsec_t *fsec, bool *is2digits)
2777 {
2778 char *cp;
2779
2780 /*
2781 * Have a decimal point? Then this is a date or something with a seconds
2782 * field...
2783 */
2784 if ((cp = strchr(str, '.')) != NULL)
2785 {
2786 /*
2787 * Can we use ParseFractionalSecond here? Not clear whether trailing
2788 * junk should be rejected ...
2789 */
2790 double frac;
2791
2792 errno = 0;
2793 frac = strtod(cp, NULL);
2794 if (errno != 0)
2795 return DTERR_BAD_FORMAT;
2796 *fsec = rint(frac * 1000000);
2797 /* Now truncate off the fraction for further processing */
2798 *cp = '\0';
2799 len = strlen(str);
2800 }
2801 /* No decimal point and no complete date yet? */
2802 else if ((fmask & DTK_DATE_M) != DTK_DATE_M)
2803 {
2804 if (len >= 6)
2805 {
2806 *tmask = DTK_DATE_M;
2807
2808 /*
2809 * Start from end and consider first 2 as Day, next 2 as Month,
2810 * and the rest as Year.
2811 */
2812 tm->tm_mday = atoi(str + (len - 2));
2813 *(str + (len - 2)) = '\0';
2814 tm->tm_mon = atoi(str + (len - 4));
2815 *(str + (len - 4)) = '\0';
2816 tm->tm_year = atoi(str);
2817 if ((len - 4) == 2)
2818 *is2digits = true;
2819
2820 return DTK_DATE;
2821 }
2822 }
2823
2824 /* not all time fields are specified? */
2825 if ((fmask & DTK_TIME_M) != DTK_TIME_M)
2826 {
2827 /* hhmmss */
2828 if (len == 6)
2829 {
2830 *tmask = DTK_TIME_M;
2831 tm->tm_sec = atoi(str + 4);
2832 *(str + 4) = '\0';
2833 tm->tm_min = atoi(str + 2);
2834 *(str + 2) = '\0';
2835 tm->tm_hour = atoi(str);
2836
2837 return DTK_TIME;
2838 }
2839 /* hhmm? */
2840 else if (len == 4)
2841 {
2842 *tmask = DTK_TIME_M;
2843 tm->tm_sec = 0;
2844 tm->tm_min = atoi(str + 2);
2845 *(str + 2) = '\0';
2846 tm->tm_hour = atoi(str);
2847
2848 return DTK_TIME;
2849 }
2850 }
2851
2852 return DTERR_BAD_FORMAT;
2853 }
2854
2855
2856 /* DecodeTimezone()
2857 * Interpret string as a numeric timezone.
2858 *
2859 * Return 0 if okay (and set *tzp), a DTERR code if not okay.
2860 */
2861 int
DecodeTimezone(char * str,int * tzp)2862 DecodeTimezone(char *str, int *tzp)
2863 {
2864 int tz;
2865 int hr,
2866 min,
2867 sec = 0;
2868 char *cp;
2869
2870 /* leading character must be "+" or "-" */
2871 if (*str != '+' && *str != '-')
2872 return DTERR_BAD_FORMAT;
2873
2874 errno = 0;
2875 hr = strtoint(str + 1, &cp, 10);
2876 if (errno == ERANGE)
2877 return DTERR_TZDISP_OVERFLOW;
2878
2879 /* explicit delimiter? */
2880 if (*cp == ':')
2881 {
2882 errno = 0;
2883 min = strtoint(cp + 1, &cp, 10);
2884 if (errno == ERANGE)
2885 return DTERR_TZDISP_OVERFLOW;
2886 if (*cp == ':')
2887 {
2888 errno = 0;
2889 sec = strtoint(cp + 1, &cp, 10);
2890 if (errno == ERANGE)
2891 return DTERR_TZDISP_OVERFLOW;
2892 }
2893 }
2894 /* otherwise, might have run things together... */
2895 else if (*cp == '\0' && strlen(str) > 3)
2896 {
2897 min = hr % 100;
2898 hr = hr / 100;
2899 /* we could, but don't, support a run-together hhmmss format */
2900 }
2901 else
2902 min = 0;
2903
2904 /* Range-check the values; see notes in datatype/timestamp.h */
2905 if (hr < 0 || hr > MAX_TZDISP_HOUR)
2906 return DTERR_TZDISP_OVERFLOW;
2907 if (min < 0 || min >= MINS_PER_HOUR)
2908 return DTERR_TZDISP_OVERFLOW;
2909 if (sec < 0 || sec >= SECS_PER_MINUTE)
2910 return DTERR_TZDISP_OVERFLOW;
2911
2912 tz = (hr * MINS_PER_HOUR + min) * SECS_PER_MINUTE + sec;
2913 if (*str == '-')
2914 tz = -tz;
2915
2916 *tzp = -tz;
2917
2918 if (*cp != '\0')
2919 return DTERR_BAD_FORMAT;
2920
2921 return 0;
2922 }
2923
2924
2925 /* DecodeTimezoneAbbrev()
2926 * Interpret string as a timezone abbreviation, if possible.
2927 *
2928 * Returns an abbreviation type (TZ, DTZ, or DYNTZ), or UNKNOWN_FIELD if
2929 * string is not any known abbreviation. On success, set *offset and *tz to
2930 * represent the UTC offset (for TZ or DTZ) or underlying zone (for DYNTZ).
2931 * Note that full timezone names (such as America/New_York) are not handled
2932 * here, mostly for historical reasons.
2933 *
2934 * Given string must be lowercased already.
2935 *
2936 * Implement a cache lookup since it is likely that dates
2937 * will be related in format.
2938 */
2939 int
DecodeTimezoneAbbrev(int field,char * lowtoken,int * offset,pg_tz ** tz)2940 DecodeTimezoneAbbrev(int field, char *lowtoken,
2941 int *offset, pg_tz **tz)
2942 {
2943 int type;
2944 const datetkn *tp;
2945
2946 tp = abbrevcache[field];
2947 /* use strncmp so that we match truncated tokens */
2948 if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
2949 {
2950 if (zoneabbrevtbl)
2951 tp = datebsearch(lowtoken, zoneabbrevtbl->abbrevs,
2952 zoneabbrevtbl->numabbrevs);
2953 else
2954 tp = NULL;
2955 }
2956 if (tp == NULL)
2957 {
2958 type = UNKNOWN_FIELD;
2959 *offset = 0;
2960 *tz = NULL;
2961 }
2962 else
2963 {
2964 abbrevcache[field] = tp;
2965 type = tp->type;
2966 if (type == DYNTZ)
2967 {
2968 *offset = 0;
2969 *tz = FetchDynamicTimeZone(zoneabbrevtbl, tp);
2970 }
2971 else
2972 {
2973 *offset = tp->value;
2974 *tz = NULL;
2975 }
2976 }
2977
2978 return type;
2979 }
2980
2981
2982 /* DecodeSpecial()
2983 * Decode text string using lookup table.
2984 *
2985 * Recognizes the keywords listed in datetktbl.
2986 * Note: at one time this would also recognize timezone abbreviations,
2987 * but no more; use DecodeTimezoneAbbrev for that.
2988 *
2989 * Given string must be lowercased already.
2990 *
2991 * Implement a cache lookup since it is likely that dates
2992 * will be related in format.
2993 */
2994 int
DecodeSpecial(int field,char * lowtoken,int * val)2995 DecodeSpecial(int field, char *lowtoken, int *val)
2996 {
2997 int type;
2998 const datetkn *tp;
2999
3000 tp = datecache[field];
3001 /* use strncmp so that we match truncated tokens */
3002 if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
3003 {
3004 tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
3005 }
3006 if (tp == NULL)
3007 {
3008 type = UNKNOWN_FIELD;
3009 *val = 0;
3010 }
3011 else
3012 {
3013 datecache[field] = tp;
3014 type = tp->type;
3015 *val = tp->value;
3016 }
3017
3018 return type;
3019 }
3020
3021
3022 /* ClearPgTm
3023 *
3024 * Zero out a pg_tm and associated fsec_t
3025 */
3026 static inline void
ClearPgTm(struct pg_tm * tm,fsec_t * fsec)3027 ClearPgTm(struct pg_tm *tm, fsec_t *fsec)
3028 {
3029 tm->tm_year = 0;
3030 tm->tm_mon = 0;
3031 tm->tm_mday = 0;
3032 tm->tm_hour = 0;
3033 tm->tm_min = 0;
3034 tm->tm_sec = 0;
3035 *fsec = 0;
3036 }
3037
3038
3039 /* DecodeInterval()
3040 * Interpret previously parsed fields for general time interval.
3041 * Returns 0 if successful, DTERR code if bogus input detected.
3042 * dtype, tm, fsec are output parameters.
3043 *
3044 * Allow "date" field DTK_DATE since this could be just
3045 * an unsigned floating point number. - thomas 1997-11-16
3046 *
3047 * Allow ISO-style time span, with implicit units on number of days
3048 * preceding an hh:mm:ss field. - thomas 1998-04-30
3049 */
3050 int
DecodeInterval(char ** field,int * ftype,int nf,int range,int * dtype,struct pg_tm * tm,fsec_t * fsec)3051 DecodeInterval(char **field, int *ftype, int nf, int range,
3052 int *dtype, struct pg_tm *tm, fsec_t *fsec)
3053 {
3054 bool is_before = false;
3055 char *cp;
3056 int fmask = 0,
3057 tmask,
3058 type;
3059 int i;
3060 int dterr;
3061 int val;
3062 double fval;
3063
3064 *dtype = DTK_DELTA;
3065 type = IGNORE_DTF;
3066 ClearPgTm(tm, fsec);
3067
3068 /* read through list backwards to pick up units before values */
3069 for (i = nf - 1; i >= 0; i--)
3070 {
3071 switch (ftype[i])
3072 {
3073 case DTK_TIME:
3074 dterr = DecodeTime(field[i], fmask, range,
3075 &tmask, tm, fsec);
3076 if (dterr)
3077 return dterr;
3078 type = DTK_DAY;
3079 break;
3080
3081 case DTK_TZ:
3082
3083 /*
3084 * Timezone means a token with a leading sign character and at
3085 * least one digit; there could be ':', '.', '-' embedded in
3086 * it as well.
3087 */
3088 Assert(*field[i] == '-' || *field[i] == '+');
3089
3090 /*
3091 * Check for signed hh:mm or hh:mm:ss. If so, process exactly
3092 * like DTK_TIME case above, plus handling the sign.
3093 */
3094 if (strchr(field[i] + 1, ':') != NULL &&
3095 DecodeTime(field[i] + 1, fmask, range,
3096 &tmask, tm, fsec) == 0)
3097 {
3098 if (*field[i] == '-')
3099 {
3100 /* flip the sign on all fields */
3101 tm->tm_hour = -tm->tm_hour;
3102 tm->tm_min = -tm->tm_min;
3103 tm->tm_sec = -tm->tm_sec;
3104 *fsec = -(*fsec);
3105 }
3106
3107 /*
3108 * Set the next type to be a day, if units are not
3109 * specified. This handles the case of '1 +02:03' since we
3110 * are reading right to left.
3111 */
3112 type = DTK_DAY;
3113 break;
3114 }
3115
3116 /*
3117 * Otherwise, fall through to DTK_NUMBER case, which can
3118 * handle signed float numbers and signed year-month values.
3119 */
3120
3121 /* FALLTHROUGH */
3122
3123 case DTK_DATE:
3124 case DTK_NUMBER:
3125 if (type == IGNORE_DTF)
3126 {
3127 /* use typmod to decide what rightmost field is */
3128 switch (range)
3129 {
3130 case INTERVAL_MASK(YEAR):
3131 type = DTK_YEAR;
3132 break;
3133 case INTERVAL_MASK(MONTH):
3134 case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH):
3135 type = DTK_MONTH;
3136 break;
3137 case INTERVAL_MASK(DAY):
3138 type = DTK_DAY;
3139 break;
3140 case INTERVAL_MASK(HOUR):
3141 case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
3142 type = DTK_HOUR;
3143 break;
3144 case INTERVAL_MASK(MINUTE):
3145 case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
3146 case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
3147 type = DTK_MINUTE;
3148 break;
3149 case INTERVAL_MASK(SECOND):
3150 case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
3151 case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
3152 case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
3153 type = DTK_SECOND;
3154 break;
3155 default:
3156 type = DTK_SECOND;
3157 break;
3158 }
3159 }
3160
3161 errno = 0;
3162 val = strtoint(field[i], &cp, 10);
3163 if (errno == ERANGE)
3164 return DTERR_FIELD_OVERFLOW;
3165
3166 if (*cp == '-')
3167 {
3168 /* SQL "years-months" syntax */
3169 int val2;
3170
3171 val2 = strtoint(cp + 1, &cp, 10);
3172 if (errno == ERANGE || val2 < 0 || val2 >= MONTHS_PER_YEAR)
3173 return DTERR_FIELD_OVERFLOW;
3174 if (*cp != '\0')
3175 return DTERR_BAD_FORMAT;
3176 type = DTK_MONTH;
3177 if (*field[i] == '-')
3178 val2 = -val2;
3179 if (((double) val * MONTHS_PER_YEAR + val2) > INT_MAX ||
3180 ((double) val * MONTHS_PER_YEAR + val2) < INT_MIN)
3181 return DTERR_FIELD_OVERFLOW;
3182 val = val * MONTHS_PER_YEAR + val2;
3183 fval = 0;
3184 }
3185 else if (*cp == '.')
3186 {
3187 errno = 0;
3188 fval = strtod(cp, &cp);
3189 if (*cp != '\0' || errno != 0)
3190 return DTERR_BAD_FORMAT;
3191
3192 if (*field[i] == '-')
3193 fval = -fval;
3194 }
3195 else if (*cp == '\0')
3196 fval = 0;
3197 else
3198 return DTERR_BAD_FORMAT;
3199
3200 tmask = 0; /* DTK_M(type); */
3201
3202 switch (type)
3203 {
3204 case DTK_MICROSEC:
3205 *fsec += rint(val + fval);
3206 tmask = DTK_M(MICROSECOND);
3207 break;
3208
3209 case DTK_MILLISEC:
3210 /* avoid overflowing the fsec field */
3211 tm->tm_sec += val / 1000;
3212 val -= (val / 1000) * 1000;
3213 *fsec += rint((val + fval) * 1000);
3214 tmask = DTK_M(MILLISECOND);
3215 break;
3216
3217 case DTK_SECOND:
3218 tm->tm_sec += val;
3219 *fsec += rint(fval * 1000000);
3220
3221 /*
3222 * If any subseconds were specified, consider this
3223 * microsecond and millisecond input as well.
3224 */
3225 if (fval == 0)
3226 tmask = DTK_M(SECOND);
3227 else
3228 tmask = DTK_ALL_SECS_M;
3229 break;
3230
3231 case DTK_MINUTE:
3232 tm->tm_min += val;
3233 AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
3234 tmask = DTK_M(MINUTE);
3235 break;
3236
3237 case DTK_HOUR:
3238 tm->tm_hour += val;
3239 AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
3240 tmask = DTK_M(HOUR);
3241 type = DTK_DAY; /* set for next field */
3242 break;
3243
3244 case DTK_DAY:
3245 tm->tm_mday += val;
3246 AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
3247 tmask = DTK_M(DAY);
3248 break;
3249
3250 case DTK_WEEK:
3251 tm->tm_mday += val * 7;
3252 AdjustFractDays(fval, tm, fsec, 7);
3253 tmask = DTK_M(WEEK);
3254 break;
3255
3256 case DTK_MONTH:
3257 tm->tm_mon += val;
3258 AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
3259 tmask = DTK_M(MONTH);
3260 break;
3261
3262 case DTK_YEAR:
3263 tm->tm_year += val;
3264 if (fval != 0)
3265 tm->tm_mon += fval * MONTHS_PER_YEAR;
3266 tmask = DTK_M(YEAR);
3267 break;
3268
3269 case DTK_DECADE:
3270 tm->tm_year += val * 10;
3271 if (fval != 0)
3272 tm->tm_mon += fval * MONTHS_PER_YEAR * 10;
3273 tmask = DTK_M(DECADE);
3274 break;
3275
3276 case DTK_CENTURY:
3277 tm->tm_year += val * 100;
3278 if (fval != 0)
3279 tm->tm_mon += fval * MONTHS_PER_YEAR * 100;
3280 tmask = DTK_M(CENTURY);
3281 break;
3282
3283 case DTK_MILLENNIUM:
3284 tm->tm_year += val * 1000;
3285 if (fval != 0)
3286 tm->tm_mon += fval * MONTHS_PER_YEAR * 1000;
3287 tmask = DTK_M(MILLENNIUM);
3288 break;
3289
3290 default:
3291 return DTERR_BAD_FORMAT;
3292 }
3293 break;
3294
3295 case DTK_STRING:
3296 case DTK_SPECIAL:
3297 type = DecodeUnits(i, field[i], &val);
3298 if (type == IGNORE_DTF)
3299 continue;
3300
3301 tmask = 0; /* DTK_M(type); */
3302 switch (type)
3303 {
3304 case UNITS:
3305 type = val;
3306 break;
3307
3308 case AGO:
3309 is_before = true;
3310 type = val;
3311 break;
3312
3313 case RESERV:
3314 tmask = (DTK_DATE_M | DTK_TIME_M);
3315 *dtype = val;
3316 break;
3317
3318 default:
3319 return DTERR_BAD_FORMAT;
3320 }
3321 break;
3322
3323 default:
3324 return DTERR_BAD_FORMAT;
3325 }
3326
3327 if (tmask & fmask)
3328 return DTERR_BAD_FORMAT;
3329 fmask |= tmask;
3330 }
3331
3332 /* ensure that at least one time field has been found */
3333 if (fmask == 0)
3334 return DTERR_BAD_FORMAT;
3335
3336 /* ensure fractional seconds are fractional */
3337 if (*fsec != 0)
3338 {
3339 int sec;
3340
3341 sec = *fsec / USECS_PER_SEC;
3342 *fsec -= sec * USECS_PER_SEC;
3343 tm->tm_sec += sec;
3344 }
3345
3346 /*----------
3347 * The SQL standard defines the interval literal
3348 * '-1 1:00:00'
3349 * to mean "negative 1 days and negative 1 hours", while Postgres
3350 * traditionally treats this as meaning "negative 1 days and positive
3351 * 1 hours". In SQL_STANDARD intervalstyle, we apply the leading sign
3352 * to all fields if there are no other explicit signs.
3353 *
3354 * We leave the signs alone if there are additional explicit signs.
3355 * This protects us against misinterpreting postgres-style dump output,
3356 * since the postgres-style output code has always put an explicit sign on
3357 * all fields following a negative field. But note that SQL-spec output
3358 * is ambiguous and can be misinterpreted on load! (So it's best practice
3359 * to dump in postgres style, not SQL style.)
3360 *----------
3361 */
3362 if (IntervalStyle == INTSTYLE_SQL_STANDARD && *field[0] == '-')
3363 {
3364 /* Check for additional explicit signs */
3365 bool more_signs = false;
3366
3367 for (i = 1; i < nf; i++)
3368 {
3369 if (*field[i] == '-' || *field[i] == '+')
3370 {
3371 more_signs = true;
3372 break;
3373 }
3374 }
3375
3376 if (!more_signs)
3377 {
3378 /*
3379 * Rather than re-determining which field was field[0], just force
3380 * 'em all negative.
3381 */
3382 if (*fsec > 0)
3383 *fsec = -(*fsec);
3384 if (tm->tm_sec > 0)
3385 tm->tm_sec = -tm->tm_sec;
3386 if (tm->tm_min > 0)
3387 tm->tm_min = -tm->tm_min;
3388 if (tm->tm_hour > 0)
3389 tm->tm_hour = -tm->tm_hour;
3390 if (tm->tm_mday > 0)
3391 tm->tm_mday = -tm->tm_mday;
3392 if (tm->tm_mon > 0)
3393 tm->tm_mon = -tm->tm_mon;
3394 if (tm->tm_year > 0)
3395 tm->tm_year = -tm->tm_year;
3396 }
3397 }
3398
3399 /* finally, AGO negates everything */
3400 if (is_before)
3401 {
3402 *fsec = -(*fsec);
3403 tm->tm_sec = -tm->tm_sec;
3404 tm->tm_min = -tm->tm_min;
3405 tm->tm_hour = -tm->tm_hour;
3406 tm->tm_mday = -tm->tm_mday;
3407 tm->tm_mon = -tm->tm_mon;
3408 tm->tm_year = -tm->tm_year;
3409 }
3410
3411 return 0;
3412 }
3413
3414
3415 /*
3416 * Helper functions to avoid duplicated code in DecodeISO8601Interval.
3417 *
3418 * Parse a decimal value and break it into integer and fractional parts.
3419 * Returns 0 or DTERR code.
3420 */
3421 static int
ParseISO8601Number(char * str,char ** endptr,int * ipart,double * fpart)3422 ParseISO8601Number(char *str, char **endptr, int *ipart, double *fpart)
3423 {
3424 double val;
3425
3426 if (!(isdigit((unsigned char) *str) || *str == '-' || *str == '.'))
3427 return DTERR_BAD_FORMAT;
3428 errno = 0;
3429 val = strtod(str, endptr);
3430 /* did we not see anything that looks like a double? */
3431 if (*endptr == str || errno != 0)
3432 return DTERR_BAD_FORMAT;
3433 /* watch out for overflow */
3434 if (val < INT_MIN || val > INT_MAX)
3435 return DTERR_FIELD_OVERFLOW;
3436 /* be very sure we truncate towards zero (cf dtrunc()) */
3437 if (val >= 0)
3438 *ipart = (int) floor(val);
3439 else
3440 *ipart = (int) -floor(-val);
3441 *fpart = val - *ipart;
3442 return 0;
3443 }
3444
3445 /*
3446 * Determine number of integral digits in a valid ISO 8601 number field
3447 * (we should ignore sign and any fraction part)
3448 */
3449 static int
ISO8601IntegerWidth(char * fieldstart)3450 ISO8601IntegerWidth(char *fieldstart)
3451 {
3452 /* We might have had a leading '-' */
3453 if (*fieldstart == '-')
3454 fieldstart++;
3455 return strspn(fieldstart, "0123456789");
3456 }
3457
3458
3459 /* DecodeISO8601Interval()
3460 * Decode an ISO 8601 time interval of the "format with designators"
3461 * (section 4.4.3.2) or "alternative format" (section 4.4.3.3)
3462 * Examples: P1D for 1 day
3463 * PT1H for 1 hour
3464 * P2Y6M7DT1H30M for 2 years, 6 months, 7 days 1 hour 30 min
3465 * P0002-06-07T01:30:00 the same value in alternative format
3466 *
3467 * Returns 0 if successful, DTERR code if bogus input detected.
3468 * Note: error code should be DTERR_BAD_FORMAT if input doesn't look like
3469 * ISO8601, otherwise this could cause unexpected error messages.
3470 * dtype, tm, fsec are output parameters.
3471 *
3472 * A couple exceptions from the spec:
3473 * - a week field ('W') may coexist with other units
3474 * - allows decimals in fields other than the least significant unit.
3475 */
3476 int
DecodeISO8601Interval(char * str,int * dtype,struct pg_tm * tm,fsec_t * fsec)3477 DecodeISO8601Interval(char *str,
3478 int *dtype, struct pg_tm *tm, fsec_t *fsec)
3479 {
3480 bool datepart = true;
3481 bool havefield = false;
3482
3483 *dtype = DTK_DELTA;
3484 ClearPgTm(tm, fsec);
3485
3486 if (strlen(str) < 2 || str[0] != 'P')
3487 return DTERR_BAD_FORMAT;
3488
3489 str++;
3490 while (*str)
3491 {
3492 char *fieldstart;
3493 int val;
3494 double fval;
3495 char unit;
3496 int dterr;
3497
3498 if (*str == 'T') /* T indicates the beginning of the time part */
3499 {
3500 datepart = false;
3501 havefield = false;
3502 str++;
3503 continue;
3504 }
3505
3506 fieldstart = str;
3507 dterr = ParseISO8601Number(str, &str, &val, &fval);
3508 if (dterr)
3509 return dterr;
3510
3511 /*
3512 * Note: we could step off the end of the string here. Code below
3513 * *must* exit the loop if unit == '\0'.
3514 */
3515 unit = *str++;
3516
3517 if (datepart)
3518 {
3519 switch (unit) /* before T: Y M W D */
3520 {
3521 case 'Y':
3522 tm->tm_year += val;
3523 tm->tm_mon += (fval * MONTHS_PER_YEAR);
3524 break;
3525 case 'M':
3526 tm->tm_mon += val;
3527 AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
3528 break;
3529 case 'W':
3530 tm->tm_mday += val * 7;
3531 AdjustFractDays(fval, tm, fsec, 7);
3532 break;
3533 case 'D':
3534 tm->tm_mday += val;
3535 AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
3536 break;
3537 case 'T': /* ISO 8601 4.4.3.3 Alternative Format / Basic */
3538 case '\0':
3539 if (ISO8601IntegerWidth(fieldstart) == 8 && !havefield)
3540 {
3541 tm->tm_year += val / 10000;
3542 tm->tm_mon += (val / 100) % 100;
3543 tm->tm_mday += val % 100;
3544 AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
3545 if (unit == '\0')
3546 return 0;
3547 datepart = false;
3548 havefield = false;
3549 continue;
3550 }
3551 /* Else fall through to extended alternative format */
3552 /* FALLTHROUGH */
3553 case '-': /* ISO 8601 4.4.3.3 Alternative Format,
3554 * Extended */
3555 if (havefield)
3556 return DTERR_BAD_FORMAT;
3557
3558 tm->tm_year += val;
3559 tm->tm_mon += (fval * MONTHS_PER_YEAR);
3560 if (unit == '\0')
3561 return 0;
3562 if (unit == 'T')
3563 {
3564 datepart = false;
3565 havefield = false;
3566 continue;
3567 }
3568
3569 dterr = ParseISO8601Number(str, &str, &val, &fval);
3570 if (dterr)
3571 return dterr;
3572 tm->tm_mon += val;
3573 AdjustFractDays(fval, tm, fsec, DAYS_PER_MONTH);
3574 if (*str == '\0')
3575 return 0;
3576 if (*str == 'T')
3577 {
3578 datepart = false;
3579 havefield = false;
3580 continue;
3581 }
3582 if (*str != '-')
3583 return DTERR_BAD_FORMAT;
3584 str++;
3585
3586 dterr = ParseISO8601Number(str, &str, &val, &fval);
3587 if (dterr)
3588 return dterr;
3589 tm->tm_mday += val;
3590 AdjustFractSeconds(fval, tm, fsec, SECS_PER_DAY);
3591 if (*str == '\0')
3592 return 0;
3593 if (*str == 'T')
3594 {
3595 datepart = false;
3596 havefield = false;
3597 continue;
3598 }
3599 return DTERR_BAD_FORMAT;
3600 default:
3601 /* not a valid date unit suffix */
3602 return DTERR_BAD_FORMAT;
3603 }
3604 }
3605 else
3606 {
3607 switch (unit) /* after T: H M S */
3608 {
3609 case 'H':
3610 tm->tm_hour += val;
3611 AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
3612 break;
3613 case 'M':
3614 tm->tm_min += val;
3615 AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
3616 break;
3617 case 'S':
3618 tm->tm_sec += val;
3619 AdjustFractSeconds(fval, tm, fsec, 1);
3620 break;
3621 case '\0': /* ISO 8601 4.4.3.3 Alternative Format */
3622 if (ISO8601IntegerWidth(fieldstart) == 6 && !havefield)
3623 {
3624 tm->tm_hour += val / 10000;
3625 tm->tm_min += (val / 100) % 100;
3626 tm->tm_sec += val % 100;
3627 AdjustFractSeconds(fval, tm, fsec, 1);
3628 return 0;
3629 }
3630 /* Else fall through to extended alternative format */
3631 /* FALLTHROUGH */
3632 case ':': /* ISO 8601 4.4.3.3 Alternative Format,
3633 * Extended */
3634 if (havefield)
3635 return DTERR_BAD_FORMAT;
3636
3637 tm->tm_hour += val;
3638 AdjustFractSeconds(fval, tm, fsec, SECS_PER_HOUR);
3639 if (unit == '\0')
3640 return 0;
3641
3642 dterr = ParseISO8601Number(str, &str, &val, &fval);
3643 if (dterr)
3644 return dterr;
3645 tm->tm_min += val;
3646 AdjustFractSeconds(fval, tm, fsec, SECS_PER_MINUTE);
3647 if (*str == '\0')
3648 return 0;
3649 if (*str != ':')
3650 return DTERR_BAD_FORMAT;
3651 str++;
3652
3653 dterr = ParseISO8601Number(str, &str, &val, &fval);
3654 if (dterr)
3655 return dterr;
3656 tm->tm_sec += val;
3657 AdjustFractSeconds(fval, tm, fsec, 1);
3658 if (*str == '\0')
3659 return 0;
3660 return DTERR_BAD_FORMAT;
3661
3662 default:
3663 /* not a valid time unit suffix */
3664 return DTERR_BAD_FORMAT;
3665 }
3666 }
3667
3668 havefield = true;
3669 }
3670
3671 return 0;
3672 }
3673
3674
3675 /* DecodeUnits()
3676 * Decode text string using lookup table.
3677 *
3678 * This routine recognizes keywords associated with time interval units.
3679 *
3680 * Given string must be lowercased already.
3681 *
3682 * Implement a cache lookup since it is likely that dates
3683 * will be related in format.
3684 */
3685 int
DecodeUnits(int field,char * lowtoken,int * val)3686 DecodeUnits(int field, char *lowtoken, int *val)
3687 {
3688 int type;
3689 const datetkn *tp;
3690
3691 tp = deltacache[field];
3692 /* use strncmp so that we match truncated tokens */
3693 if (tp == NULL || strncmp(lowtoken, tp->token, TOKMAXLEN) != 0)
3694 {
3695 tp = datebsearch(lowtoken, deltatktbl, szdeltatktbl);
3696 }
3697 if (tp == NULL)
3698 {
3699 type = UNKNOWN_FIELD;
3700 *val = 0;
3701 }
3702 else
3703 {
3704 deltacache[field] = tp;
3705 type = tp->type;
3706 *val = tp->value;
3707 }
3708
3709 return type;
3710 } /* DecodeUnits() */
3711
3712 /*
3713 * Report an error detected by one of the datetime input processing routines.
3714 *
3715 * dterr is the error code, str is the original input string, datatype is
3716 * the name of the datatype we were trying to accept.
3717 *
3718 * Note: it might seem useless to distinguish DTERR_INTERVAL_OVERFLOW and
3719 * DTERR_TZDISP_OVERFLOW from DTERR_FIELD_OVERFLOW, but SQL99 mandates three
3720 * separate SQLSTATE codes, so ...
3721 */
3722 void
DateTimeParseError(int dterr,const char * str,const char * datatype)3723 DateTimeParseError(int dterr, const char *str, const char *datatype)
3724 {
3725 switch (dterr)
3726 {
3727 case DTERR_FIELD_OVERFLOW:
3728 ereport(ERROR,
3729 (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
3730 errmsg("date/time field value out of range: \"%s\"",
3731 str)));
3732 break;
3733 case DTERR_MD_FIELD_OVERFLOW:
3734 /* <nanny>same as above, but add hint about DateStyle</nanny> */
3735 ereport(ERROR,
3736 (errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
3737 errmsg("date/time field value out of range: \"%s\"",
3738 str),
3739 errhint("Perhaps you need a different \"datestyle\" setting.")));
3740 break;
3741 case DTERR_INTERVAL_OVERFLOW:
3742 ereport(ERROR,
3743 (errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),
3744 errmsg("interval field value out of range: \"%s\"",
3745 str)));
3746 break;
3747 case DTERR_TZDISP_OVERFLOW:
3748 ereport(ERROR,
3749 (errcode(ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE),
3750 errmsg("time zone displacement out of range: \"%s\"",
3751 str)));
3752 break;
3753 case DTERR_BAD_FORMAT:
3754 default:
3755 ereport(ERROR,
3756 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
3757 errmsg("invalid input syntax for type %s: \"%s\"",
3758 datatype, str)));
3759 break;
3760 }
3761 }
3762
3763 /* datebsearch()
3764 * Binary search -- from Knuth (6.2.1) Algorithm B. Special case like this
3765 * is WAY faster than the generic bsearch().
3766 */
3767 static const datetkn *
datebsearch(const char * key,const datetkn * base,int nel)3768 datebsearch(const char *key, const datetkn *base, int nel)
3769 {
3770 if (nel > 0)
3771 {
3772 const datetkn *last = base + nel - 1,
3773 *position;
3774 int result;
3775
3776 while (last >= base)
3777 {
3778 position = base + ((last - base) >> 1);
3779 /* precheck the first character for a bit of extra speed */
3780 result = (int) key[0] - (int) position->token[0];
3781 if (result == 0)
3782 {
3783 /* use strncmp so that we match truncated tokens */
3784 result = strncmp(key, position->token, TOKMAXLEN);
3785 if (result == 0)
3786 return position;
3787 }
3788 if (result < 0)
3789 last = position - 1;
3790 else
3791 base = position + 1;
3792 }
3793 }
3794 return NULL;
3795 }
3796
3797 /* EncodeTimezone()
3798 * Copies representation of a numeric timezone offset to str.
3799 *
3800 * Returns a pointer to the new end of string. No NUL terminator is put
3801 * there; callers are responsible for NUL terminating str themselves.
3802 */
3803 static char *
EncodeTimezone(char * str,int tz,int style)3804 EncodeTimezone(char *str, int tz, int style)
3805 {
3806 int hour,
3807 min,
3808 sec;
3809
3810 sec = abs(tz);
3811 min = sec / SECS_PER_MINUTE;
3812 sec -= min * SECS_PER_MINUTE;
3813 hour = min / MINS_PER_HOUR;
3814 min -= hour * MINS_PER_HOUR;
3815
3816 /* TZ is negated compared to sign we wish to display ... */
3817 *str++ = (tz <= 0 ? '+' : '-');
3818
3819 if (sec != 0)
3820 {
3821 str = pg_ultostr_zeropad(str, hour, 2);
3822 *str++ = ':';
3823 str = pg_ultostr_zeropad(str, min, 2);
3824 *str++ = ':';
3825 str = pg_ultostr_zeropad(str, sec, 2);
3826 }
3827 else if (min != 0 || style == USE_XSD_DATES)
3828 {
3829 str = pg_ultostr_zeropad(str, hour, 2);
3830 *str++ = ':';
3831 str = pg_ultostr_zeropad(str, min, 2);
3832 }
3833 else
3834 str = pg_ultostr_zeropad(str, hour, 2);
3835 return str;
3836 }
3837
3838 /* EncodeDateOnly()
3839 * Encode date as local time.
3840 */
3841 void
EncodeDateOnly(struct pg_tm * tm,int style,char * str)3842 EncodeDateOnly(struct pg_tm *tm, int style, char *str)
3843 {
3844 Assert(tm->tm_mon >= 1 && tm->tm_mon <= MONTHS_PER_YEAR);
3845
3846 switch (style)
3847 {
3848 case USE_ISO_DATES:
3849 case USE_XSD_DATES:
3850 /* compatible with ISO date formats */
3851 str = pg_ultostr_zeropad(str,
3852 (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
3853 *str++ = '-';
3854 str = pg_ultostr_zeropad(str, tm->tm_mon, 2);
3855 *str++ = '-';
3856 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
3857 break;
3858
3859 case USE_SQL_DATES:
3860 /* compatible with Oracle/Ingres date formats */
3861 if (DateOrder == DATEORDER_DMY)
3862 {
3863 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
3864 *str++ = '/';
3865 str = pg_ultostr_zeropad(str, tm->tm_mon, 2);
3866 }
3867 else
3868 {
3869 str = pg_ultostr_zeropad(str, tm->tm_mon, 2);
3870 *str++ = '/';
3871 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
3872 }
3873 *str++ = '/';
3874 str = pg_ultostr_zeropad(str,
3875 (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
3876 break;
3877
3878 case USE_GERMAN_DATES:
3879 /* German-style date format */
3880 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
3881 *str++ = '.';
3882 str = pg_ultostr_zeropad(str, tm->tm_mon, 2);
3883 *str++ = '.';
3884 str = pg_ultostr_zeropad(str,
3885 (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
3886 break;
3887
3888 case USE_POSTGRES_DATES:
3889 default:
3890 /* traditional date-only style for Postgres */
3891 if (DateOrder == DATEORDER_DMY)
3892 {
3893 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
3894 *str++ = '-';
3895 str = pg_ultostr_zeropad(str, tm->tm_mon, 2);
3896 }
3897 else
3898 {
3899 str = pg_ultostr_zeropad(str, tm->tm_mon, 2);
3900 *str++ = '-';
3901 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
3902 }
3903 *str++ = '-';
3904 str = pg_ultostr_zeropad(str,
3905 (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
3906 break;
3907 }
3908
3909 if (tm->tm_year <= 0)
3910 {
3911 memcpy(str, " BC", 3); /* Don't copy NUL */
3912 str += 3;
3913 }
3914 *str = '\0';
3915 }
3916
3917
3918 /* EncodeTimeOnly()
3919 * Encode time fields only.
3920 *
3921 * tm and fsec are the value to encode, print_tz determines whether to include
3922 * a time zone (the difference between time and timetz types), tz is the
3923 * numeric time zone offset, style is the date style, str is where to write the
3924 * output.
3925 */
3926 void
EncodeTimeOnly(struct pg_tm * tm,fsec_t fsec,bool print_tz,int tz,int style,char * str)3927 EncodeTimeOnly(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, int style, char *str)
3928 {
3929 str = pg_ultostr_zeropad(str, tm->tm_hour, 2);
3930 *str++ = ':';
3931 str = pg_ultostr_zeropad(str, tm->tm_min, 2);
3932 *str++ = ':';
3933 str = AppendSeconds(str, tm->tm_sec, fsec, MAX_TIME_PRECISION, true);
3934 if (print_tz)
3935 str = EncodeTimezone(str, tz, style);
3936 *str = '\0';
3937 }
3938
3939
3940 /* EncodeDateTime()
3941 * Encode date and time interpreted as local time.
3942 *
3943 * tm and fsec are the value to encode, print_tz determines whether to include
3944 * a time zone (the difference between timestamp and timestamptz types), tz is
3945 * the numeric time zone offset, tzn is the textual time zone, which if
3946 * specified will be used instead of tz by some styles, style is the date
3947 * style, str is where to write the output.
3948 *
3949 * Supported date styles:
3950 * Postgres - day mon hh:mm:ss yyyy tz
3951 * SQL - mm/dd/yyyy hh:mm:ss.ss tz
3952 * ISO - yyyy-mm-dd hh:mm:ss+/-tz
3953 * German - dd.mm.yyyy hh:mm:ss tz
3954 * XSD - yyyy-mm-ddThh:mm:ss.ss+/-tz
3955 */
3956 void
EncodeDateTime(struct pg_tm * tm,fsec_t fsec,bool print_tz,int tz,const char * tzn,int style,char * str)3957 EncodeDateTime(struct pg_tm *tm, fsec_t fsec, bool print_tz, int tz, const char *tzn, int style, char *str)
3958 {
3959 int day;
3960
3961 Assert(tm->tm_mon >= 1 && tm->tm_mon <= MONTHS_PER_YEAR);
3962
3963 /*
3964 * Negative tm_isdst means we have no valid time zone translation.
3965 */
3966 if (tm->tm_isdst < 0)
3967 print_tz = false;
3968
3969 switch (style)
3970 {
3971 case USE_ISO_DATES:
3972 case USE_XSD_DATES:
3973 /* Compatible with ISO-8601 date formats */
3974 str = pg_ultostr_zeropad(str,
3975 (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
3976 *str++ = '-';
3977 str = pg_ultostr_zeropad(str, tm->tm_mon, 2);
3978 *str++ = '-';
3979 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
3980 *str++ = (style == USE_ISO_DATES) ? ' ' : 'T';
3981 str = pg_ultostr_zeropad(str, tm->tm_hour, 2);
3982 *str++ = ':';
3983 str = pg_ultostr_zeropad(str, tm->tm_min, 2);
3984 *str++ = ':';
3985 str = AppendTimestampSeconds(str, tm, fsec);
3986 if (print_tz)
3987 str = EncodeTimezone(str, tz, style);
3988 break;
3989
3990 case USE_SQL_DATES:
3991 /* Compatible with Oracle/Ingres date formats */
3992 if (DateOrder == DATEORDER_DMY)
3993 {
3994 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
3995 *str++ = '/';
3996 str = pg_ultostr_zeropad(str, tm->tm_mon, 2);
3997 }
3998 else
3999 {
4000 str = pg_ultostr_zeropad(str, tm->tm_mon, 2);
4001 *str++ = '/';
4002 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
4003 }
4004 *str++ = '/';
4005 str = pg_ultostr_zeropad(str,
4006 (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
4007 *str++ = ' ';
4008 str = pg_ultostr_zeropad(str, tm->tm_hour, 2);
4009 *str++ = ':';
4010 str = pg_ultostr_zeropad(str, tm->tm_min, 2);
4011 *str++ = ':';
4012 str = AppendTimestampSeconds(str, tm, fsec);
4013
4014 /*
4015 * Note: the uses of %.*s in this function would be risky if the
4016 * timezone names ever contain non-ASCII characters. However, all
4017 * TZ abbreviations in the IANA database are plain ASCII.
4018 */
4019 if (print_tz)
4020 {
4021 if (tzn)
4022 {
4023 sprintf(str, " %.*s", MAXTZLEN, tzn);
4024 str += strlen(str);
4025 }
4026 else
4027 str = EncodeTimezone(str, tz, style);
4028 }
4029 break;
4030
4031 case USE_GERMAN_DATES:
4032 /* German variant on European style */
4033 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
4034 *str++ = '.';
4035 str = pg_ultostr_zeropad(str, tm->tm_mon, 2);
4036 *str++ = '.';
4037 str = pg_ultostr_zeropad(str,
4038 (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
4039 *str++ = ' ';
4040 str = pg_ultostr_zeropad(str, tm->tm_hour, 2);
4041 *str++ = ':';
4042 str = pg_ultostr_zeropad(str, tm->tm_min, 2);
4043 *str++ = ':';
4044 str = AppendTimestampSeconds(str, tm, fsec);
4045
4046 if (print_tz)
4047 {
4048 if (tzn)
4049 {
4050 sprintf(str, " %.*s", MAXTZLEN, tzn);
4051 str += strlen(str);
4052 }
4053 else
4054 str = EncodeTimezone(str, tz, style);
4055 }
4056 break;
4057
4058 case USE_POSTGRES_DATES:
4059 default:
4060 /* Backward-compatible with traditional Postgres abstime dates */
4061 day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
4062 tm->tm_wday = j2day(day);
4063 memcpy(str, days[tm->tm_wday], 3);
4064 str += 3;
4065 *str++ = ' ';
4066 if (DateOrder == DATEORDER_DMY)
4067 {
4068 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
4069 *str++ = ' ';
4070 memcpy(str, months[tm->tm_mon - 1], 3);
4071 str += 3;
4072 }
4073 else
4074 {
4075 memcpy(str, months[tm->tm_mon - 1], 3);
4076 str += 3;
4077 *str++ = ' ';
4078 str = pg_ultostr_zeropad(str, tm->tm_mday, 2);
4079 }
4080 *str++ = ' ';
4081 str = pg_ultostr_zeropad(str, tm->tm_hour, 2);
4082 *str++ = ':';
4083 str = pg_ultostr_zeropad(str, tm->tm_min, 2);
4084 *str++ = ':';
4085 str = AppendTimestampSeconds(str, tm, fsec);
4086 *str++ = ' ';
4087 str = pg_ultostr_zeropad(str,
4088 (tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1), 4);
4089
4090 if (print_tz)
4091 {
4092 if (tzn)
4093 {
4094 sprintf(str, " %.*s", MAXTZLEN, tzn);
4095 str += strlen(str);
4096 }
4097 else
4098 {
4099 /*
4100 * We have a time zone, but no string version. Use the
4101 * numeric form, but be sure to include a leading space to
4102 * avoid formatting something which would be rejected by
4103 * the date/time parser later. - thomas 2001-10-19
4104 */
4105 *str++ = ' ';
4106 str = EncodeTimezone(str, tz, style);
4107 }
4108 }
4109 break;
4110 }
4111
4112 if (tm->tm_year <= 0)
4113 {
4114 memcpy(str, " BC", 3); /* Don't copy NUL */
4115 str += 3;
4116 }
4117 *str = '\0';
4118 }
4119
4120
4121 /*
4122 * Helper functions to avoid duplicated code in EncodeInterval.
4123 */
4124
4125 /* Append an ISO-8601-style interval field, but only if value isn't zero */
4126 static char *
AddISO8601IntPart(char * cp,int value,char units)4127 AddISO8601IntPart(char *cp, int value, char units)
4128 {
4129 if (value == 0)
4130 return cp;
4131 sprintf(cp, "%d%c", value, units);
4132 return cp + strlen(cp);
4133 }
4134
4135 /* Append a postgres-style interval field, but only if value isn't zero */
4136 static char *
AddPostgresIntPart(char * cp,int value,const char * units,bool * is_zero,bool * is_before)4137 AddPostgresIntPart(char *cp, int value, const char *units,
4138 bool *is_zero, bool *is_before)
4139 {
4140 if (value == 0)
4141 return cp;
4142 sprintf(cp, "%s%s%d %s%s",
4143 (!*is_zero) ? " " : "",
4144 (*is_before && value > 0) ? "+" : "",
4145 value,
4146 units,
4147 (value != 1) ? "s" : "");
4148
4149 /*
4150 * Each nonzero field sets is_before for (only) the next one. This is a
4151 * tad bizarre but it's how it worked before...
4152 */
4153 *is_before = (value < 0);
4154 *is_zero = false;
4155 return cp + strlen(cp);
4156 }
4157
4158 /* Append a verbose-style interval field, but only if value isn't zero */
4159 static char *
AddVerboseIntPart(char * cp,int value,const char * units,bool * is_zero,bool * is_before)4160 AddVerboseIntPart(char *cp, int value, const char *units,
4161 bool *is_zero, bool *is_before)
4162 {
4163 if (value == 0)
4164 return cp;
4165 /* first nonzero value sets is_before */
4166 if (*is_zero)
4167 {
4168 *is_before = (value < 0);
4169 value = abs(value);
4170 }
4171 else if (*is_before)
4172 value = -value;
4173 sprintf(cp, " %d %s%s", value, units, (value == 1) ? "" : "s");
4174 *is_zero = false;
4175 return cp + strlen(cp);
4176 }
4177
4178
4179 /* EncodeInterval()
4180 * Interpret time structure as a delta time and convert to string.
4181 *
4182 * Support "traditional Postgres" and ISO-8601 styles.
4183 * Actually, afaik ISO does not address time interval formatting,
4184 * but this looks similar to the spec for absolute date/time.
4185 * - thomas 1998-04-30
4186 *
4187 * Actually, afaik, ISO 8601 does specify formats for "time
4188 * intervals...[of the]...format with time-unit designators", which
4189 * are pretty ugly. The format looks something like
4190 * P1Y1M1DT1H1M1.12345S
4191 * but useful for exchanging data with computers instead of humans.
4192 * - ron 2003-07-14
4193 *
4194 * And ISO's SQL 2008 standard specifies standards for
4195 * "year-month literal"s (that look like '2-3') and
4196 * "day-time literal"s (that look like ('4 5:6:7')
4197 */
4198 void
EncodeInterval(struct pg_tm * tm,fsec_t fsec,int style,char * str)4199 EncodeInterval(struct pg_tm *tm, fsec_t fsec, int style, char *str)
4200 {
4201 char *cp = str;
4202 int year = tm->tm_year;
4203 int mon = tm->tm_mon;
4204 int mday = tm->tm_mday;
4205 int hour = tm->tm_hour;
4206 int min = tm->tm_min;
4207 int sec = tm->tm_sec;
4208 bool is_before = false;
4209 bool is_zero = true;
4210
4211 /*
4212 * The sign of year and month are guaranteed to match, since they are
4213 * stored internally as "month". But we'll need to check for is_before and
4214 * is_zero when determining the signs of day and hour/minute/seconds
4215 * fields.
4216 */
4217 switch (style)
4218 {
4219 /* SQL Standard interval format */
4220 case INTSTYLE_SQL_STANDARD:
4221 {
4222 bool has_negative = year < 0 || mon < 0 ||
4223 mday < 0 || hour < 0 ||
4224 min < 0 || sec < 0 || fsec < 0;
4225 bool has_positive = year > 0 || mon > 0 ||
4226 mday > 0 || hour > 0 ||
4227 min > 0 || sec > 0 || fsec > 0;
4228 bool has_year_month = year != 0 || mon != 0;
4229 bool has_day_time = mday != 0 || hour != 0 ||
4230 min != 0 || sec != 0 || fsec != 0;
4231 bool has_day = mday != 0;
4232 bool sql_standard_value = !(has_negative && has_positive) &&
4233 !(has_year_month && has_day_time);
4234
4235 /*
4236 * SQL Standard wants only 1 "<sign>" preceding the whole
4237 * interval ... but can't do that if mixed signs.
4238 */
4239 if (has_negative && sql_standard_value)
4240 {
4241 *cp++ = '-';
4242 year = -year;
4243 mon = -mon;
4244 mday = -mday;
4245 hour = -hour;
4246 min = -min;
4247 sec = -sec;
4248 fsec = -fsec;
4249 }
4250
4251 if (!has_negative && !has_positive)
4252 {
4253 sprintf(cp, "0");
4254 }
4255 else if (!sql_standard_value)
4256 {
4257 /*
4258 * For non sql-standard interval values, force outputting
4259 * the signs to avoid ambiguities with intervals with
4260 * mixed sign components.
4261 */
4262 char year_sign = (year < 0 || mon < 0) ? '-' : '+';
4263 char day_sign = (mday < 0) ? '-' : '+';
4264 char sec_sign = (hour < 0 || min < 0 ||
4265 sec < 0 || fsec < 0) ? '-' : '+';
4266
4267 sprintf(cp, "%c%d-%d %c%d %c%d:%02d:",
4268 year_sign, abs(year), abs(mon),
4269 day_sign, abs(mday),
4270 sec_sign, abs(hour), abs(min));
4271 cp += strlen(cp);
4272 cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
4273 *cp = '\0';
4274 }
4275 else if (has_year_month)
4276 {
4277 sprintf(cp, "%d-%d", year, mon);
4278 }
4279 else if (has_day)
4280 {
4281 sprintf(cp, "%d %d:%02d:", mday, hour, min);
4282 cp += strlen(cp);
4283 cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
4284 *cp = '\0';
4285 }
4286 else
4287 {
4288 sprintf(cp, "%d:%02d:", hour, min);
4289 cp += strlen(cp);
4290 cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
4291 *cp = '\0';
4292 }
4293 }
4294 break;
4295
4296 /* ISO 8601 "time-intervals by duration only" */
4297 case INTSTYLE_ISO_8601:
4298 /* special-case zero to avoid printing nothing */
4299 if (year == 0 && mon == 0 && mday == 0 &&
4300 hour == 0 && min == 0 && sec == 0 && fsec == 0)
4301 {
4302 sprintf(cp, "PT0S");
4303 break;
4304 }
4305 *cp++ = 'P';
4306 cp = AddISO8601IntPart(cp, year, 'Y');
4307 cp = AddISO8601IntPart(cp, mon, 'M');
4308 cp = AddISO8601IntPart(cp, mday, 'D');
4309 if (hour != 0 || min != 0 || sec != 0 || fsec != 0)
4310 *cp++ = 'T';
4311 cp = AddISO8601IntPart(cp, hour, 'H');
4312 cp = AddISO8601IntPart(cp, min, 'M');
4313 if (sec != 0 || fsec != 0)
4314 {
4315 if (sec < 0 || fsec < 0)
4316 *cp++ = '-';
4317 cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
4318 *cp++ = 'S';
4319 *cp++ = '\0';
4320 }
4321 break;
4322
4323 /* Compatible with postgresql < 8.4 when DateStyle = 'iso' */
4324 case INTSTYLE_POSTGRES:
4325 cp = AddPostgresIntPart(cp, year, "year", &is_zero, &is_before);
4326
4327 /*
4328 * Ideally we should spell out "month" like we do for "year" and
4329 * "day". However, for backward compatibility, we can't easily
4330 * fix this. bjm 2011-05-24
4331 */
4332 cp = AddPostgresIntPart(cp, mon, "mon", &is_zero, &is_before);
4333 cp = AddPostgresIntPart(cp, mday, "day", &is_zero, &is_before);
4334 if (is_zero || hour != 0 || min != 0 || sec != 0 || fsec != 0)
4335 {
4336 bool minus = (hour < 0 || min < 0 || sec < 0 || fsec < 0);
4337
4338 sprintf(cp, "%s%s%02d:%02d:",
4339 is_zero ? "" : " ",
4340 (minus ? "-" : (is_before ? "+" : "")),
4341 abs(hour), abs(min));
4342 cp += strlen(cp);
4343 cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, true);
4344 *cp = '\0';
4345 }
4346 break;
4347
4348 /* Compatible with postgresql < 8.4 when DateStyle != 'iso' */
4349 case INTSTYLE_POSTGRES_VERBOSE:
4350 default:
4351 strcpy(cp, "@");
4352 cp++;
4353 cp = AddVerboseIntPart(cp, year, "year", &is_zero, &is_before);
4354 cp = AddVerboseIntPart(cp, mon, "mon", &is_zero, &is_before);
4355 cp = AddVerboseIntPart(cp, mday, "day", &is_zero, &is_before);
4356 cp = AddVerboseIntPart(cp, hour, "hour", &is_zero, &is_before);
4357 cp = AddVerboseIntPart(cp, min, "min", &is_zero, &is_before);
4358 if (sec != 0 || fsec != 0)
4359 {
4360 *cp++ = ' ';
4361 if (sec < 0 || (sec == 0 && fsec < 0))
4362 {
4363 if (is_zero)
4364 is_before = true;
4365 else if (!is_before)
4366 *cp++ = '-';
4367 }
4368 else if (is_before)
4369 *cp++ = '-';
4370 cp = AppendSeconds(cp, sec, fsec, MAX_INTERVAL_PRECISION, false);
4371 sprintf(cp, " sec%s",
4372 (abs(sec) != 1 || fsec != 0) ? "s" : "");
4373 is_zero = false;
4374 }
4375 /* identically zero? then put in a unitless zero... */
4376 if (is_zero)
4377 strcat(cp, " 0");
4378 if (is_before)
4379 strcat(cp, " ago");
4380 break;
4381 }
4382 }
4383
4384
4385 /*
4386 * We've been burnt by stupid errors in the ordering of the datetkn tables
4387 * once too often. Arrange to check them during postmaster start.
4388 */
4389 static bool
CheckDateTokenTable(const char * tablename,const datetkn * base,int nel)4390 CheckDateTokenTable(const char *tablename, const datetkn *base, int nel)
4391 {
4392 bool ok = true;
4393 int i;
4394
4395 for (i = 0; i < nel; i++)
4396 {
4397 /* check for token strings that don't fit */
4398 if (strlen(base[i].token) > TOKMAXLEN)
4399 {
4400 /* %.*s is safe since all our tokens are ASCII */
4401 elog(LOG, "token too long in %s table: \"%.*s\"",
4402 tablename,
4403 TOKMAXLEN + 1, base[i].token);
4404 ok = false;
4405 break; /* don't risk applying strcmp */
4406 }
4407 /* check for out of order */
4408 if (i > 0 &&
4409 strcmp(base[i - 1].token, base[i].token) >= 0)
4410 {
4411 elog(LOG, "ordering error in %s table: \"%s\" >= \"%s\"",
4412 tablename,
4413 base[i - 1].token,
4414 base[i].token);
4415 ok = false;
4416 }
4417 }
4418 return ok;
4419 }
4420
4421 bool
CheckDateTokenTables(void)4422 CheckDateTokenTables(void)
4423 {
4424 bool ok = true;
4425
4426 Assert(UNIX_EPOCH_JDATE == date2j(1970, 1, 1));
4427 Assert(POSTGRES_EPOCH_JDATE == date2j(2000, 1, 1));
4428
4429 ok &= CheckDateTokenTable("datetktbl", datetktbl, szdatetktbl);
4430 ok &= CheckDateTokenTable("deltatktbl", deltatktbl, szdeltatktbl);
4431 return ok;
4432 }
4433
4434 /*
4435 * Common code for temporal prosupport functions: simplify, if possible,
4436 * a call to a temporal type's length-coercion function.
4437 *
4438 * Types time, timetz, timestamp and timestamptz each have a range of allowed
4439 * precisions. An unspecified precision is rigorously equivalent to the
4440 * highest specifiable precision. We can replace the function call with a
4441 * no-op RelabelType if it is coercing to the same or higher precision as the
4442 * input is known to have.
4443 *
4444 * The input Node is always a FuncExpr, but to reduce the #include footprint
4445 * of datetime.h, we declare it as Node *.
4446 *
4447 * Note: timestamp_scale throws an error when the typmod is out of range, but
4448 * we can't get there from a cast: our typmodin will have caught it already.
4449 */
4450 Node *
TemporalSimplify(int32 max_precis,Node * node)4451 TemporalSimplify(int32 max_precis, Node *node)
4452 {
4453 FuncExpr *expr = castNode(FuncExpr, node);
4454 Node *ret = NULL;
4455 Node *typmod;
4456
4457 Assert(list_length(expr->args) >= 2);
4458
4459 typmod = (Node *) lsecond(expr->args);
4460
4461 if (IsA(typmod, Const) && !((Const *) typmod)->constisnull)
4462 {
4463 Node *source = (Node *) linitial(expr->args);
4464 int32 old_precis = exprTypmod(source);
4465 int32 new_precis = DatumGetInt32(((Const *) typmod)->constvalue);
4466
4467 if (new_precis < 0 || new_precis == max_precis ||
4468 (old_precis >= 0 && new_precis >= old_precis))
4469 ret = relabel_to_typmod(source, new_precis);
4470 }
4471
4472 return ret;
4473 }
4474
4475 /*
4476 * This function gets called during timezone config file load or reload
4477 * to create the final array of timezone tokens. The argument array
4478 * is already sorted in name order.
4479 *
4480 * The result is a TimeZoneAbbrevTable (which must be a single malloc'd chunk)
4481 * or NULL on malloc failure. No other error conditions are defined.
4482 */
4483 TimeZoneAbbrevTable *
ConvertTimeZoneAbbrevs(struct tzEntry * abbrevs,int n)4484 ConvertTimeZoneAbbrevs(struct tzEntry *abbrevs, int n)
4485 {
4486 TimeZoneAbbrevTable *tbl;
4487 Size tbl_size;
4488 int i;
4489
4490 /* Space for fixed fields and datetkn array */
4491 tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) +
4492 n * sizeof(datetkn);
4493 tbl_size = MAXALIGN(tbl_size);
4494 /* Count up space for dynamic abbreviations */
4495 for (i = 0; i < n; i++)
4496 {
4497 struct tzEntry *abbr = abbrevs + i;
4498
4499 if (abbr->zone != NULL)
4500 {
4501 Size dsize;
4502
4503 dsize = offsetof(DynamicZoneAbbrev, zone) +
4504 strlen(abbr->zone) + 1;
4505 tbl_size += MAXALIGN(dsize);
4506 }
4507 }
4508
4509 /* Alloc the result ... */
4510 tbl = malloc(tbl_size);
4511 if (!tbl)
4512 return NULL;
4513
4514 /* ... and fill it in */
4515 tbl->tblsize = tbl_size;
4516 tbl->numabbrevs = n;
4517 /* in this loop, tbl_size reprises the space calculation above */
4518 tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) +
4519 n * sizeof(datetkn);
4520 tbl_size = MAXALIGN(tbl_size);
4521 for (i = 0; i < n; i++)
4522 {
4523 struct tzEntry *abbr = abbrevs + i;
4524 datetkn *dtoken = tbl->abbrevs + i;
4525
4526 /* use strlcpy to truncate name if necessary */
4527 strlcpy(dtoken->token, abbr->abbrev, TOKMAXLEN + 1);
4528 if (abbr->zone != NULL)
4529 {
4530 /* Allocate a DynamicZoneAbbrev for this abbreviation */
4531 DynamicZoneAbbrev *dtza;
4532 Size dsize;
4533
4534 dtza = (DynamicZoneAbbrev *) ((char *) tbl + tbl_size);
4535 dtza->tz = NULL;
4536 strcpy(dtza->zone, abbr->zone);
4537
4538 dtoken->type = DYNTZ;
4539 /* value is offset from table start to DynamicZoneAbbrev */
4540 dtoken->value = (int32) tbl_size;
4541
4542 dsize = offsetof(DynamicZoneAbbrev, zone) +
4543 strlen(abbr->zone) + 1;
4544 tbl_size += MAXALIGN(dsize);
4545 }
4546 else
4547 {
4548 dtoken->type = abbr->is_dst ? DTZ : TZ;
4549 dtoken->value = abbr->offset;
4550 }
4551 }
4552
4553 /* Assert the two loops above agreed on size calculations */
4554 Assert(tbl->tblsize == tbl_size);
4555
4556 /* Check the ordering, if testing */
4557 Assert(CheckDateTokenTable("timezone abbreviations", tbl->abbrevs, n));
4558
4559 return tbl;
4560 }
4561
4562 /*
4563 * Install a TimeZoneAbbrevTable as the active table.
4564 *
4565 * Caller is responsible that the passed table doesn't go away while in use.
4566 */
4567 void
InstallTimeZoneAbbrevs(TimeZoneAbbrevTable * tbl)4568 InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
4569 {
4570 zoneabbrevtbl = tbl;
4571 /* reset abbrevcache, which may contain pointers into old table */
4572 memset(abbrevcache, 0, sizeof(abbrevcache));
4573 }
4574
4575 /*
4576 * Helper subroutine to locate pg_tz timezone for a dynamic abbreviation.
4577 */
4578 static pg_tz *
FetchDynamicTimeZone(TimeZoneAbbrevTable * tbl,const datetkn * tp)4579 FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp)
4580 {
4581 DynamicZoneAbbrev *dtza;
4582
4583 /* Just some sanity checks to prevent indexing off into nowhere */
4584 Assert(tp->type == DYNTZ);
4585 Assert(tp->value > 0 && tp->value < tbl->tblsize);
4586
4587 dtza = (DynamicZoneAbbrev *) ((char *) tbl + tp->value);
4588
4589 /* Look up the underlying zone if we haven't already */
4590 if (dtza->tz == NULL)
4591 {
4592 dtza->tz = pg_tzset(dtza->zone);
4593
4594 /*
4595 * Ideally we'd let the caller ereport instead of doing it here, but
4596 * then there is no way to report the bad time zone name.
4597 */
4598 if (dtza->tz == NULL)
4599 ereport(ERROR,
4600 (errcode(ERRCODE_CONFIG_FILE_ERROR),
4601 errmsg("time zone \"%s\" not recognized",
4602 dtza->zone),
4603 errdetail("This time zone name appears in the configuration file for time zone abbreviation \"%s\".",
4604 tp->token)));
4605 }
4606 return dtza->tz;
4607 }
4608
4609
4610 /*
4611 * This set-returning function reads all the available time zone abbreviations
4612 * and returns a set of (abbrev, utc_offset, is_dst).
4613 */
4614 Datum
pg_timezone_abbrevs(PG_FUNCTION_ARGS)4615 pg_timezone_abbrevs(PG_FUNCTION_ARGS)
4616 {
4617 FuncCallContext *funcctx;
4618 int *pindex;
4619 Datum result;
4620 HeapTuple tuple;
4621 Datum values[3];
4622 bool nulls[3];
4623 const datetkn *tp;
4624 char buffer[TOKMAXLEN + 1];
4625 int gmtoffset;
4626 bool is_dst;
4627 unsigned char *p;
4628 struct pg_tm tm;
4629 Interval *resInterval;
4630
4631 /* stuff done only on the first call of the function */
4632 if (SRF_IS_FIRSTCALL())
4633 {
4634 TupleDesc tupdesc;
4635 MemoryContext oldcontext;
4636
4637 /* create a function context for cross-call persistence */
4638 funcctx = SRF_FIRSTCALL_INIT();
4639
4640 /*
4641 * switch to memory context appropriate for multiple function calls
4642 */
4643 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
4644
4645 /* allocate memory for user context */
4646 pindex = (int *) palloc(sizeof(int));
4647 *pindex = 0;
4648 funcctx->user_fctx = (void *) pindex;
4649
4650 /*
4651 * build tupdesc for result tuples. This must match this function's
4652 * pg_proc entry!
4653 */
4654 tupdesc = CreateTemplateTupleDesc(3);
4655 TupleDescInitEntry(tupdesc, (AttrNumber) 1, "abbrev",
4656 TEXTOID, -1, 0);
4657 TupleDescInitEntry(tupdesc, (AttrNumber) 2, "utc_offset",
4658 INTERVALOID, -1, 0);
4659 TupleDescInitEntry(tupdesc, (AttrNumber) 3, "is_dst",
4660 BOOLOID, -1, 0);
4661
4662 funcctx->tuple_desc = BlessTupleDesc(tupdesc);
4663 MemoryContextSwitchTo(oldcontext);
4664 }
4665
4666 /* stuff done on every call of the function */
4667 funcctx = SRF_PERCALL_SETUP();
4668 pindex = (int *) funcctx->user_fctx;
4669
4670 if (zoneabbrevtbl == NULL ||
4671 *pindex >= zoneabbrevtbl->numabbrevs)
4672 SRF_RETURN_DONE(funcctx);
4673
4674 tp = zoneabbrevtbl->abbrevs + *pindex;
4675
4676 switch (tp->type)
4677 {
4678 case TZ:
4679 gmtoffset = tp->value;
4680 is_dst = false;
4681 break;
4682 case DTZ:
4683 gmtoffset = tp->value;
4684 is_dst = true;
4685 break;
4686 case DYNTZ:
4687 {
4688 /* Determine the current meaning of the abbrev */
4689 pg_tz *tzp;
4690 TimestampTz now;
4691 int isdst;
4692
4693 tzp = FetchDynamicTimeZone(zoneabbrevtbl, tp);
4694 now = GetCurrentTransactionStartTimestamp();
4695 gmtoffset = -DetermineTimeZoneAbbrevOffsetTS(now,
4696 tp->token,
4697 tzp,
4698 &isdst);
4699 is_dst = (bool) isdst;
4700 break;
4701 }
4702 default:
4703 elog(ERROR, "unrecognized timezone type %d", (int) tp->type);
4704 gmtoffset = 0; /* keep compiler quiet */
4705 is_dst = false;
4706 break;
4707 }
4708
4709 MemSet(nulls, 0, sizeof(nulls));
4710
4711 /*
4712 * Convert name to text, using upcasing conversion that is the inverse of
4713 * what ParseDateTime() uses.
4714 */
4715 strlcpy(buffer, tp->token, sizeof(buffer));
4716 for (p = (unsigned char *) buffer; *p; p++)
4717 *p = pg_toupper(*p);
4718
4719 values[0] = CStringGetTextDatum(buffer);
4720
4721 /* Convert offset (in seconds) to an interval */
4722 MemSet(&tm, 0, sizeof(struct pg_tm));
4723 tm.tm_sec = gmtoffset;
4724 resInterval = (Interval *) palloc(sizeof(Interval));
4725 tm2interval(&tm, 0, resInterval);
4726 values[1] = IntervalPGetDatum(resInterval);
4727
4728 values[2] = BoolGetDatum(is_dst);
4729
4730 (*pindex)++;
4731
4732 tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
4733 result = HeapTupleGetDatum(tuple);
4734
4735 SRF_RETURN_NEXT(funcctx, result);
4736 }
4737
4738 /*
4739 * This set-returning function reads all the available full time zones
4740 * and returns a set of (name, abbrev, utc_offset, is_dst).
4741 */
4742 Datum
pg_timezone_names(PG_FUNCTION_ARGS)4743 pg_timezone_names(PG_FUNCTION_ARGS)
4744 {
4745 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
4746 bool randomAccess;
4747 TupleDesc tupdesc;
4748 Tuplestorestate *tupstore;
4749 pg_tzenum *tzenum;
4750 pg_tz *tz;
4751 Datum values[4];
4752 bool nulls[4];
4753 int tzoff;
4754 struct pg_tm tm;
4755 fsec_t fsec;
4756 const char *tzn;
4757 Interval *resInterval;
4758 struct pg_tm itm;
4759 MemoryContext oldcontext;
4760
4761 /* check to see if caller supports us returning a tuplestore */
4762 if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
4763 ereport(ERROR,
4764 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4765 errmsg("set-valued function called in context that cannot accept a set")));
4766 if (!(rsinfo->allowedModes & SFRM_Materialize))
4767 ereport(ERROR,
4768 (errcode(ERRCODE_SYNTAX_ERROR),
4769 errmsg("materialize mode required, but it is not allowed in this context")));
4770
4771 /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */
4772 oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory);
4773
4774 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
4775 elog(ERROR, "return type must be a row type");
4776
4777 randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0;
4778 tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
4779 rsinfo->returnMode = SFRM_Materialize;
4780 rsinfo->setResult = tupstore;
4781 rsinfo->setDesc = tupdesc;
4782
4783 MemoryContextSwitchTo(oldcontext);
4784
4785 /* initialize timezone scanning code */
4786 tzenum = pg_tzenumerate_start();
4787
4788 /* search for another zone to display */
4789 for (;;)
4790 {
4791 tz = pg_tzenumerate_next(tzenum);
4792 if (!tz)
4793 break;
4794
4795 /* Convert now() to local time in this zone */
4796 if (timestamp2tm(GetCurrentTransactionStartTimestamp(),
4797 &tzoff, &tm, &fsec, &tzn, tz) != 0)
4798 continue; /* ignore if conversion fails */
4799
4800 /*
4801 * IANA's rather silly "Factory" time zone used to emit ridiculously
4802 * long "abbreviations" such as "Local time zone must be set--see zic
4803 * manual page" or "Local time zone must be set--use tzsetup". While
4804 * modern versions of tzdb emit the much saner "-00", it seems some
4805 * benighted packagers are hacking the IANA data so that it continues
4806 * to produce these strings. To prevent producing a weirdly wide
4807 * abbrev column, reject ridiculously long abbreviations.
4808 */
4809 if (tzn && strlen(tzn) > 31)
4810 continue;
4811
4812 MemSet(nulls, 0, sizeof(nulls));
4813
4814 values[0] = CStringGetTextDatum(pg_get_timezone_name(tz));
4815 values[1] = CStringGetTextDatum(tzn ? tzn : "");
4816
4817 MemSet(&itm, 0, sizeof(struct pg_tm));
4818 itm.tm_sec = -tzoff;
4819 resInterval = (Interval *) palloc(sizeof(Interval));
4820 tm2interval(&itm, 0, resInterval);
4821 values[2] = IntervalPGetDatum(resInterval);
4822
4823 values[3] = BoolGetDatum(tm.tm_isdst > 0);
4824
4825 tuplestore_putvalues(tupstore, tupdesc, values, nulls);
4826 }
4827
4828 pg_tzenumerate_end(tzenum);
4829 return (Datum) 0;
4830 }
4831