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