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