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