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