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