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