1 /*-------------------------------------------------------------------------
4 * Support functions for date/time types.
6 * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
11 * $Header: /cvsroot/pgsql/src/backend/utils/adt/datetime.c,v 1.70 2001/10/05 06:38:59 thomas Exp $
13 *-------------------------------------------------------------------------
19 #include <sys/types.h>
24 #include "miscadmin.h"
25 #include "utils/guc.h"
26 #include "utils/datetime.h"
29 static int DecodeNumber(int flen, char *field,
30 int fmask, int *tmask,
31 struct tm * tm, double *fsec, int *is2digits);
32 static int DecodeNumberField(int len, char *str,
33 int fmask, int *tmask,
34 struct tm * tm, double *fsec, int *is2digits);
35 static int DecodeTime(char *str, int fmask, int *tmask,
36 struct tm * tm, double *fsec);
37 static int DecodeTimezone(char *str, int *tzp);
38 static datetkn *datebsearch(char *key, datetkn *base, unsigned int nel);
39 static int DecodeDate(char *str, int fmask, int *tmask, struct tm * tm);
40 static int DecodePosixTimezone(char *str, int *val);
43 int day_tab[2][13] = {
44 {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0},
45 {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}};
47 char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
48 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};
50 char *days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
51 "Thursday", "Friday", "Saturday", NULL};
54 /*****************************************************************************
56 *****************************************************************************/
58 /* definitions for squeezing values into "value" */
59 #define ABS_SIGNBIT ((char) 0200)
60 #define VALMASK ((char) 0177)
61 #define NEG(n) ((n)|ABS_SIGNBIT)
62 #define SIGNEDCHAR(c) ((c)&ABS_SIGNBIT? -((c)&VALMASK): (c))
63 #define FROMVAL(tp) (-SIGNEDCHAR((tp)->value) * 10) /* uncompress */
64 #define TOVAL(tp, v) ((tp)->value = ((v) < 0? NEG((-(v))/10): (v)/10))
67 * to keep this table reasonably small, we divide the lexval for TZ and DTZ
68 * entries by 10 and truncate the text field at MAXTOKLEN characters.
69 * the text field is not guaranteed to be NULL-terminated.
71 static datetkn datetktbl[] = {
72 /* text, token, lexval */
73 {EARLY, RESERV, DTK_EARLY}, /* "-infinity" reserved for "early time" */
74 {"acsst", DTZ, 63}, /* Cent. Australia */
75 {"acst", TZ, 57}, /* Cent. Australia */
76 {DA_D, ADBC, AD}, /* "ad" for years >= 0 */
77 {"abstime", IGNORE, 0}, /* "abstime" for pre-v6.1 "Invalid
79 {"adt", DTZ, NEG(18)}, /* Atlantic Daylight Time */
80 {"aesst", DTZ, 66}, /* E. Australia */
81 {"aest", TZ, 60}, /* Australia Eastern Std Time */
82 {"ahst", TZ, NEG(60)}, /* Alaska-Hawaii Std Time */
83 {"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */
87 {"ast", TZ, NEG(24)}, /* Atlantic Std Time (Canada) */
88 {"at", IGNORE, 0}, /* "at" (throwaway) */
91 {"awsst", DTZ, 54}, /* W. Australia */
92 {"awst", TZ, 48}, /* W. Australia */
93 {DB_C, ADBC, BC}, /* "bc" for years < 0 */
94 {"bst", TZ, 6}, /* British Summer Time */
95 {"bt", TZ, 18}, /* Baghdad Time */
96 {"cadt", DTZ, 63}, /* Central Australian DST */
97 {"cast", TZ, 57}, /* Central Australian ST */
98 {"cat", TZ, NEG(60)}, /* Central Alaska Time */
99 {"cct", TZ, 48}, /* China Coast */
100 {"cdt", DTZ, NEG(30)}, /* Central Daylight Time */
101 {"cest", DTZ, 12}, /* Central European Dayl.Time */
102 {"cet", TZ, 6}, /* Central European Time */
103 {"cetdst", DTZ, 12}, /* Central European Dayl.Time */
104 {"cst", TZ, NEG(36)}, /* Central Standard Time */
105 {DCURRENT, RESERV, DTK_CURRENT}, /* "current" is always now */
106 {"d", UNITS, DAY}, /* "day of month" for ISO input */
108 {"december", MONTH, 12},
109 {"dnt", TZ, 6}, /* Dansk Normal Tid */
110 {"dow", RESERV, DTK_DOW}, /* day of week */
111 {"doy", RESERV, DTK_DOY}, /* day of year */
113 {"east", TZ, 60}, /* East Australian Std Time */
114 {"edt", DTZ, NEG(24)}, /* Eastern Daylight Time */
115 {"eet", TZ, 12}, /* East. Europe, USSR Zone 1 */
116 {"eetdst", DTZ, 18}, /* Eastern Europe */
117 {EPOCH, RESERV, DTK_EPOCH}, /* "epoch" reserved for system epoch time */
118 {"est", TZ, NEG(30)}, /* Eastern Standard Time */
120 {"february", MONTH, 2},
123 {"fst", TZ, 6}, /* French Summer Time */
124 {"fwt", DTZ, 12}, /* French Winter Time */
125 {"gmt", TZ, 0}, /* Greenwish Mean Time */
126 {"gst", TZ, 60}, /* Guam Std Time, USSR Zone 9 */
127 {"h", UNITS, HOUR}, /* "hour" */
128 {"hdt", DTZ, NEG(54)}, /* Hawaii/Alaska */
129 {"hmt", DTZ, 18}, /* Hellas ? ? */
130 {"hst", TZ, NEG(60)}, /* Hawaii Std Time */
131 {"idle", TZ, 72}, /* Intl. Date Line, East */
132 {"idlw", TZ, NEG(72)}, /* Intl. Date Line, West */
133 {LATE, RESERV, DTK_LATE}, /* "infinity" reserved for "late time" */
134 {INVALID, RESERV, DTK_INVALID},
135 /* "invalid" reserved for invalid time */
136 {"ist", TZ, 12}, /* Israel */
137 {"it", TZ, 21}, /* Iran Time */
138 {"j", UNITS, JULIAN},
140 {"january", MONTH, 1},
141 {"jd", UNITS, JULIAN},
142 {"jst", TZ, 54}, /* Japan Std Time,USSR Zone 8 */
143 {"jt", TZ, 45}, /* Java Time */
145 {"julian", UNITS, JULIAN},
148 {"kst", TZ, 54}, /* Korea Standard Time */
149 {"ligt", TZ, 60}, /* From Melbourne, Australia */
150 {"m", UNITS, MONTH}, /* "month" for ISO input */
154 {"mdt", DTZ, NEG(36)}, /* Mountain Daylight Time */
155 {"mest", DTZ, 12}, /* Middle Europe Summer Time */
156 {"met", TZ, 6}, /* Middle Europe Time */
157 {"metdst", DTZ, 12}, /* Middle Europe Daylight Time */
158 {"mewt", TZ, 6}, /* Middle Europe Winter Time */
159 {"mez", TZ, 6}, /* Middle Europe Zone */
160 {"mm", UNITS, MINUTE}, /* "minute" for ISO input */
163 {"mst", TZ, NEG(42)}, /* Mountain Standard Time */
164 {"mt", TZ, 51}, /* Moluccas Time */
165 {"ndt", DTZ, NEG(15)}, /* Nfld. Daylight Time */
166 {"nft", TZ, NEG(21)}, /* Newfoundland Standard Time */
167 {"nor", TZ, 6}, /* Norway Standard Time */
169 {"november", MONTH, 11},
170 {NOW, RESERV, DTK_NOW}, /* current transaction time */
171 {"nst", TZ, NEG(21)}, /* Nfld. Standard Time */
172 {"nt", TZ, NEG(66)}, /* Nome Time */
173 {"nzdt", DTZ, 78}, /* New Zealand Daylight Time */
174 {"nzst", TZ, 72}, /* New Zealand Standard Time */
175 {"nzt", TZ, 72}, /* New Zealand Time */
177 {"october", MONTH, 10},
178 {"on", IGNORE, 0}, /* "on" (throwaway) */
179 {"pdt", DTZ, NEG(42)}, /* Pacific Daylight Time */
181 {"pst", TZ, NEG(48)}, /* Pacific Standard Time */
182 {"s", UNITS, SECOND}, /* "seconds" for ISO input */
183 {"sadt", DTZ, 63}, /* S. Australian Dayl. Time */
184 {"sast", TZ, 57}, /* South Australian Std Time */
186 {"saturday", DOW, 6},
189 {"september", MONTH, 9},
190 {"set", TZ, NEG(6)}, /* Seychelles Time ?? */
191 {"sst", DTZ, 12}, /* Swedish Summer Time */
194 {"swt", TZ, 6}, /* Swedish Winter Time */
195 {"t", DTK_ISO_TIME, 0}, /* Filler for ISO time fields */
199 {"thursday", DOW, 4},
200 {TODAY, RESERV, DTK_TODAY}, /* midnight */
201 {TOMORROW, RESERV, DTK_TOMORROW}, /* tomorrow midnight */
205 {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */
208 {"wadt", DTZ, 48}, /* West Australian DST */
209 {"wast", TZ, 42}, /* West Australian Std Time */
210 {"wat", TZ, NEG(6)}, /* West Africa Time */
211 {"wdt", DTZ, 54}, /* West Australian DST */
213 {"wednesday", DOW, 3},
215 {"wet", TZ, 0}, /* Western Europe */
216 {"wetdst", DTZ, 6}, /* Western Europe */
217 {"wst", TZ, 48}, /* West Australian Std Time */
218 {"y", UNITS, YEAR}, /* "year" for ISO input */
219 {"ydt", DTZ, NEG(48)}, /* Yukon Daylight Time */
220 {YESTERDAY, RESERV, DTK_YESTERDAY}, /* yesterday midnight */
221 {"yst", TZ, NEG(54)}, /* Yukon Standard Time */
222 {"z", RESERV, DTK_ZULU}, /* 00:00:00 */
223 {"zp4", TZ, NEG(24)}, /* GMT +4 hours. */
224 {"zp5", TZ, NEG(30)}, /* GMT +5 hours. */
225 {"zp6", TZ, NEG(36)}, /* GMT +6 hours. */
226 {ZULU, RESERV, DTK_ZULU}, /* 00:00:00 */
229 static unsigned int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];
231 /* Used for SET australian_timezones to override North American ones */
232 static datetkn australian_datetktbl[] = {
233 {"cst", TZ, 63}, /* Australia Central Std Time */
234 {"est", TZ, 60}, /* Australia Eastern Std Time */
238 static unsigned int australian_szdatetktbl = sizeof australian_datetktbl /
239 sizeof australian_datetktbl[0];
241 static datetkn deltatktbl[] = {
242 /* text, token, lexval */
243 {"@", IGNORE, 0}, /* postgres relative time prefix */
244 {DAGO, AGO, 0}, /* "ago" indicates negative time offset */
245 {"c", UNITS, DTK_CENTURY}, /* "century" relative time units */
246 {"cent", UNITS, DTK_CENTURY}, /* "century" relative time units */
247 {"centuries", UNITS, DTK_CENTURY}, /* "centuries" relative time units */
248 {DCENTURY, UNITS, DTK_CENTURY}, /* "century" relative time units */
249 {"d", UNITS, DTK_DAY}, /* "day" relative time units */
250 {DDAY, UNITS, DTK_DAY}, /* "day" relative time units */
251 {"days", UNITS, DTK_DAY}, /* "days" relative time units */
252 {"dec", UNITS, DTK_DECADE}, /* "decade" relative time units */
253 {"decs", UNITS, DTK_DECADE},/* "decades" relative time units */
254 {DDECADE, UNITS, DTK_DECADE}, /* "decade" relative time units */
255 {"decades", UNITS, DTK_DECADE}, /* "decades" relative time units */
256 {"h", UNITS, DTK_HOUR}, /* "hour" relative time units */
257 {DHOUR, UNITS, DTK_HOUR}, /* "hour" relative time units */
258 {"hours", UNITS, DTK_HOUR}, /* "hours" relative time units */
259 {"hr", UNITS, DTK_HOUR}, /* "hour" relative time units */
260 {"hrs", UNITS, DTK_HOUR}, /* "hours" relative time units */
261 {INVALID, RESERV, DTK_INVALID}, /* reserved for invalid time */
262 {"m", UNITS, DTK_MINUTE}, /* "minute" relative time units */
263 {"microsecon", UNITS, DTK_MICROSEC}, /* "microsecond" relative
265 {"mil", UNITS, DTK_MILLENNIUM}, /* "millennium" relative time
267 {"mils", UNITS, DTK_MILLENNIUM}, /* "millennia" relative time units */
268 {"millennia", UNITS, DTK_MILLENNIUM}, /* "millennia" relative
270 {DMILLENNIUM, UNITS, DTK_MILLENNIUM}, /* "millennium" relative
272 {"millisecon", UNITS, DTK_MILLISEC}, /* relative time units */
273 {"min", UNITS, DTK_MINUTE}, /* "minute" relative time units */
274 {"mins", UNITS, DTK_MINUTE},/* "minutes" relative time units */
275 {"mins", UNITS, DTK_MINUTE},/* "minutes" relative time units */
276 {DMINUTE, UNITS, DTK_MINUTE}, /* "minute" relative time units */
277 {"minutes", UNITS, DTK_MINUTE}, /* "minutes" relative time units */
278 {"mon", UNITS, DTK_MONTH}, /* "months" relative time units */
279 {"mons", UNITS, DTK_MONTH}, /* "months" relative time units */
280 {DMONTH, UNITS, DTK_MONTH}, /* "month" relative time units */
281 {"months", UNITS, DTK_MONTH},
282 {"ms", UNITS, DTK_MILLISEC},
283 {"msec", UNITS, DTK_MILLISEC},
284 {DMILLISEC, UNITS, DTK_MILLISEC},
285 {"mseconds", UNITS, DTK_MILLISEC},
286 {"msecs", UNITS, DTK_MILLISEC},
287 {"qtr", UNITS, DTK_QUARTER},/* "quarter" relative time */
288 {DQUARTER, UNITS, DTK_QUARTER}, /* "quarter" relative time */
289 {"reltime", IGNORE, 0}, /* for pre-v6.1 "Undefined Reltime" */
290 {"s", UNITS, DTK_SECOND},
291 {"sec", UNITS, DTK_SECOND},
292 {DSECOND, UNITS, DTK_SECOND},
293 {"seconds", UNITS, DTK_SECOND},
294 {"secs", UNITS, DTK_SECOND},
295 {DTIMEZONE, UNITS, DTK_TZ}, /* "timezone" time offset */
296 {"tz", UNITS, DTK_TZ}, /* "timezone" time offset */
297 {"tz_hour", UNITS, DTK_TZ_HOUR}, /* timezone hour units */
298 {"tz_minute", UNITS, DTK_TZ_MINUTE}, /* timezone minutes units */
299 {"undefined", RESERV, DTK_INVALID}, /* pre-v6.1 invalid time */
300 {"us", UNITS, DTK_MICROSEC},/* "microsecond" relative time units */
301 {"usec", UNITS, DTK_MICROSEC}, /* "microsecond" relative time
303 {DMICROSEC, UNITS, DTK_MICROSEC}, /* "microsecond" relative time
305 {"useconds", UNITS, DTK_MICROSEC}, /* "microseconds" relative time
307 {"usecs", UNITS, DTK_MICROSEC}, /* "microseconds" relative time
309 {"w", UNITS, DTK_WEEK}, /* "week" relative time units */
310 {DWEEK, UNITS, DTK_WEEK}, /* "week" relative time units */
311 {"weeks", UNITS, DTK_WEEK}, /* "weeks" relative time units */
312 {"y", UNITS, DTK_YEAR}, /* "year" relative time units */
313 {DYEAR, UNITS, DTK_YEAR}, /* "year" relative time units */
314 {"years", UNITS, DTK_YEAR}, /* "years" relative time units */
315 {"yr", UNITS, DTK_YEAR}, /* "year" relative time units */
316 {"yrs", UNITS, DTK_YEAR}, /* "years" relative time units */
319 static unsigned int szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0];
321 datetkn *datecache[MAXDATEFIELDS] = {NULL};
323 datetkn *deltacache[MAXDATEFIELDS] = {NULL};
327 * Calendar time to Julian date conversions.
328 * Julian date is commonly used in astronomical applications,
329 * since it is numerically accurate and computationally simple.
330 * The algorithms here will accurately convert between Julian day
331 * and calendar date for all non-negative Julian days
332 * (i.e. from Nov 23, -4713 on).
334 * Ref: Explanatory Supplement to the Astronomical Almanac, 1992.
335 * University Science Books, 20 Edgehill Rd. Mill Valley CA 94941.
337 * Use the algorithm by Henry Fliegel, a former NASA/JPL colleague
338 * now at Aerospace Corp. (hi, Henry!)
340 * These routines will be used by other date/time packages
345 date2j(int y, int m, int d)
347 int m12 = (m - 14) / 12;
349 return ((1461 * (y + 4800 + m12)) / 4 + (367 * (m - 2 - 12 * (m12))) / 12
350 - (3 * ((y + 4900 + m12) / 100)) / 4 + d - 32075);
354 j2date(int jd, int *year, int *month, int *day)
366 n = (4 * l) / 146097;
367 l -= (146097 * n + 3) / 4;
368 i = (4000 * (l + 1)) / 1461001;
369 l += 31 - (1461 * i) / 4;
371 d = l - (2447 * j) / 80;
373 m = (j + 2) - (12 * l);
374 y = 100 * (n - 49) + i + l;
387 day = (date + 1) % 7;
394 * parse and convert date in timestr (the normal interface)
396 * Returns the number of seconds since epoch (J2000)
400 * Break string into tokens based on a date/time context.
403 ParseDateTime(char *timestr, char *lowstr,
404 char **field, int *ftype, int maxfields, int *numfields)
410 /* outer loop through fields */
415 /* leading digit? then date or time */
416 if (isdigit((unsigned char) *cp) || (*cp == '.'))
419 while (isdigit((unsigned char) *cp))
424 ftype[nf] = DTK_TIME;
426 while (isdigit((unsigned char) *cp) ||
427 (*cp == ':') || (*cp == '.'))
431 /* date field? allow embedded text month */
432 else if ((*cp == '-') || (*cp == '/') || (*cp == '.'))
434 ftype[nf] = DTK_DATE;
436 /* second field is all digits? then no embedded text month */
437 if (isdigit((unsigned char) *cp))
439 while (isdigit((unsigned char) *cp) || (*cp == '-') ||
440 (*cp == '/') || (*cp == '.'))
445 while (isalnum((unsigned char) *cp) || (*cp == '-') ||
446 (*cp == '/') || (*cp == '.'))
447 *lp++ = tolower((unsigned char) *cp++);
452 * otherwise, number only and will determine year, month, or
456 ftype[nf] = DTK_NUMBER;
461 * text? then date string, month, day of week, special, or
464 else if (isalpha((unsigned char) *cp))
466 ftype[nf] = DTK_STRING;
467 *lp++ = tolower((unsigned char) *cp++);
468 while (isalpha((unsigned char) *cp))
469 *lp++ = tolower((unsigned char) *cp++);
472 * Full date string with leading text month? Could also be a
475 if ((*cp == '-') || (*cp == '/') || (*cp == '.'))
477 ftype[nf] = DTK_DATE;
478 while (isdigit((unsigned char) *cp) ||
479 (*cp == '-') || (*cp == '/') || (*cp == '.'))
480 *lp++ = tolower((unsigned char) *cp++);
483 /* skip leading spaces */
485 else if (isspace((unsigned char) *cp))
490 /* sign? then special or numeric timezone */
492 else if ((*cp == '+') || (*cp == '-'))
495 /* soak up leading whitespace */
496 while (isspace((unsigned char) *cp))
498 /* numeric timezone? */
499 if (isdigit((unsigned char) *cp))
503 while (isdigit((unsigned char) *cp) ||
504 (*cp == ':') || (*cp == '.'))
509 else if (isalpha((unsigned char) *cp))
511 ftype[nf] = DTK_SPECIAL;
512 *lp++ = tolower((unsigned char) *cp++);
513 while (isalpha((unsigned char) *cp))
514 *lp++ = tolower((unsigned char) *cp++);
516 /* otherwise something wrong... */
521 /* ignore punctuation but use as delimiter */
523 else if (ispunct((unsigned char) *cp))
532 /* force in a delimiter */
535 if (nf > MAXDATEFIELDS)
542 } /* ParseDateTime() */
546 * Interpret previously parsed fields for general date and time.
547 * Return 0 if full date, 1 if only time, and -1 if problems.
548 * External format(s):
549 * "<weekday> <month>-<day>-<year> <hour>:<minute>:<second>"
550 * "Fri Feb-7-1997 15:23:27"
551 * "Feb-7-1997 15:23:27"
552 * "2-7-1997 15:23:27"
553 * "1997-2-7 15:23:27"
554 * "1997.038 15:23:27" (day of year 1-366)
555 * Also supports input in compact time:
559 * Use the system-provided functions to get the current time zone
560 * if not specified in the input string.
561 * If the date is outside the time_t system-supported time range,
562 * then assume GMT time zone. - thomas 1997/05/27
565 DecodeDateTime(char **field, int *ftype, int nf,
566 int *dtype, struct tm * tm, double *fsec, int *tzp)
571 int ptype = 0; /* "prefix type" for ISO y2001m02d04 format */
576 int haveTextMonth = FALSE;
577 int is2digits = FALSE;
580 /* We'll insist on at least all of the date fields,
581 * but initialize the remaining fields in case they are not
589 tm->tm_isdst = -1; /* don't know daylight savings time status apriori */
593 for (i = 0; i < nf; i++)
598 /* Previous field was a label for "julian date"?
599 * then this should be a julian date with fractional day...
604 double dt, date, time;
606 dt = strtod(field[i], &cp);
611 TMODULO(time, date, 86400e0);
612 j2date((int) date, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
613 dt2time(time, &tm->tm_hour, &tm->tm_min, fsec);
615 tmask = DTK_DATE_M | DTK_TIME_M;
619 /* Already have a date? Then this might be a POSIX time
620 * zone with an embedded dash (e.g. "PST-3" == "EST")
621 * - thomas 2000-03-15
623 else if ((fmask & DTK_DATE_M) == DTK_DATE_M)
626 || (DecodePosixTimezone(field[i], tzp) != 0))
632 else if (DecodeDate(field[i], fmask, &tmask, tm) != 0)
639 if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0)
642 /* Check upper limit on hours; other limits checked in
645 if (tm->tm_hour > 23)
656 if (DecodeTimezone(field[i], &tz) != 0)
660 * Already have a time zone? Then maybe this is the
661 * second field of a POSIX time: EST+3 (equivalent to
664 if ((i > 0) && ((fmask & DTK_M(TZ)) != 0)
665 && (ftype[i - 1] == DTK_TZ)
666 && (isalpha((unsigned char) *field[i - 1])))
680 flen = strlen(field[i]);
682 /* Was this an "ISO date" with embedded field labels?
683 * An example is "y2001m02d04" - thomas 2001-02-04
690 val = strtol(field[i], &cp, 10);
697 tmask = DTK_M(ptype);
702 tmask = DTK_M(ptype);
707 tmask = DTK_M(ptype);
712 tmask = DTK_M(ptype);
717 tmask = DTK_M(ptype);
722 tmask = DTK_M(ptype);
726 /* previous field was a label for "julian date"?
727 * then this is a julian day with no fractional part
728 * (see DTK_DATE for cases involving fractional parts)
730 j2date(val, &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
744 * long numeric string and either no date or no time read
745 * yet? then interpret as a concatenated date or time...
747 else if ((flen > 4) && !((fmask & DTK_DATE_M) && (fmask & DTK_TIME_M)))
749 if (DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec, &is2digits) != 0)
753 /* otherwise it is a single date/time field... */
754 else if (DecodeNumber(flen, field[i], fmask, &tmask, tm, fsec, &is2digits) != 0)
762 type = DecodeSpecial(i, field[i], &val);
774 tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ));
776 GetCurrentTimeUsec(tm, fsec);
785 j2date((date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - 1),
786 &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
805 j2date((date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + 1),
806 &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
813 tmask = (DTK_TIME_M | DTK_M(TZ));
831 * already have a (numeric) month? then see if we
834 if ((fmask & DTK_M(MONTH)) && (!haveTextMonth)
835 && (!(fmask & DTK_M(DAY)))
836 && ((tm->tm_mon >= 1) && (tm->tm_mon <= 31)))
838 tm->tm_mday = tm->tm_mon;
841 haveTextMonth = TRUE;
848 * daylight savings time modifier (solves "MET
861 * set mask for TZ here _or_ check for DTZ later
862 * when getting default timezone
901 if ((i < 1) || (i >= (nf-1))
902 || (ftype[i-1] != DTK_DATE)
903 || (ftype[i+1] != DTK_TIME))
921 /* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
925 tm->tm_year = -(tm->tm_year - 1);
927 elog(ERROR, "Inconsistant use of year %04d and 'BC'", tm->tm_year);
931 if (tm->tm_year < 70)
933 else if (tm->tm_year < 100)
937 if ((mer != HR24) && (tm->tm_hour > 12))
939 if ((mer == AM) && (tm->tm_hour == 12))
941 else if ((mer == PM) && (tm->tm_hour != 12))
944 /* do additional checking for full date specs... */
945 if (*dtype == DTK_DATE)
947 if ((fmask & DTK_DATE_M) != DTK_DATE_M)
948 return ((fmask & DTK_TIME_M) == DTK_TIME_M) ? 1 : -1;
951 * check for valid day of month, now that we know for sure the
954 if ((tm->tm_mday < 1)
955 || (tm->tm_mday > day_tab[isleap(tm->tm_year)][tm->tm_mon - 1]))
958 /* timezone not specified? then find local timezone if possible */
959 if (((fmask & DTK_DATE_M) == DTK_DATE_M)
960 && (tzp != NULL) && (!(fmask & DTK_M(TZ))))
964 * daylight savings time modifier but no standard timezone?
967 if (fmask & DTK_M(DTZMOD))
970 *tzp = DetermineLocalTimeZone(tm);
975 } /* DecodeDateTime() */
978 /* DetermineLocalTimeZone()
979 * Given a struct tm in which tm_year, tm_mon, tm_mday, tm_hour, tm_min, and
980 * tm_sec fields are set, attempt to determine the applicable local zone
981 * (ie, regular or daylight-savings time) at that time. Set the struct tm's
982 * tm_isdst field accordingly, and return the actual timezone offset.
984 * This subroutine exists mainly to centralize uses of mktime() and defend
985 * against mktime() bugs on various platforms...
988 DetermineLocalTimeZone(struct tm * tm)
992 if (IS_VALID_UTIME(tm->tm_year, tm->tm_mon, tm->tm_mday))
994 #if defined(HAVE_TM_ZONE) || defined(HAVE_INT_TIMEZONE)
996 * Some buggy mktime() implementations may change the year/month/day
997 * when given a time right at a DST boundary. To prevent corruption
998 * of the caller's data, give mktime() a copy...
1004 /* change to Unix conventions for year/month */
1005 tmp->tm_year -= 1900;
1008 /* indicate timezone unknown */
1013 tm->tm_isdst = tmp->tm_isdst;
1015 #if defined(HAVE_TM_ZONE)
1016 /* tm_gmtoff is Sun/DEC-ism */
1017 if (tmp->tm_isdst >= 0)
1018 tz = -(tmp->tm_gmtoff);
1020 tz = 0; /* assume GMT if mktime failed */
1021 #elif defined(HAVE_INT_TIMEZONE)
1022 tz = ((tmp->tm_isdst > 0) ? (TIMEZONE_GLOBAL - 3600) : TIMEZONE_GLOBAL);
1023 #endif /* HAVE_INT_TIMEZONE */
1025 #else /* not (HAVE_TM_ZONE || HAVE_INT_TIMEZONE) */
1032 /* Given date is out of range, so assume GMT */
1042 * Interpret parsed string as time fields only.
1043 * Note that support for time zone is here for
1044 * SQL92 TIME WITH TIME ZONE, but it reveals
1045 * bogosity with SQL92 date/time standards, since
1046 * we must infer a time zone from current time.
1047 * XXX Later, we should probably support
1048 * SET TIME ZONE <integer>
1049 * which of course is a screwed up convention.
1050 * - thomas 2000-03-10
1053 DecodeTimeOnly(char **field, int *ftype, int nf,
1054 int *dtype, struct tm * tm, double *fsec, int *tzp)
1062 int is2digits = FALSE;
1070 tm->tm_isdst = -1; /* don't know daylight savings time status
1077 for (i = 0; i < nf; i++)
1084 * This might be a POSIX time zone with an embedded dash
1085 * (e.g. "PST-3" == "EST") - thomas 2000-03-15
1088 || (DecodePosixTimezone(field[i], tzp) != 0))
1096 if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0)
1107 if (DecodeTimezone(field[i], &tz) != 0)
1111 * Already have a time zone? Then maybe this is the
1112 * second field of a POSIX time: EST+3 (equivalent to
1115 if ((i > 0) && ((fmask & DTK_M(TZ)) != 0)
1116 && (ftype[i - 1] == DTK_TZ) && (isalpha((unsigned char) *field[i - 1])))
1130 flen = strlen(field[i]);
1132 if (DecodeNumberField(flen, field[i], fmask, &tmask, tm, fsec, &is2digits) != 0)
1138 type = DecodeSpecial(i, field[i], &val);
1142 tmask = DTK_M(type);
1151 GetCurrentTimeUsec(tm, fsec);
1155 tmask = (DTK_TIME_M | DTK_M(TZ));
1172 * daylight savings time modifier (solves "MET
1175 tmask |= DTK_M(DTZ);
1185 * set mask for TZ here _or_ check for DTZ later
1186 * when getting default timezone
1225 if ((mer != HR24) && (tm->tm_hour > 12))
1227 if ((mer == AM) && (tm->tm_hour == 12))
1229 else if ((mer == PM) && (tm->tm_hour != 12))
1232 if (((tm->tm_hour < 0) || (tm->tm_hour > 23))
1233 || ((tm->tm_min < 0) || (tm->tm_min > 59))
1234 || ((tm->tm_sec < 0) || ((tm->tm_sec + *fsec) >= 60)))
1237 if ((fmask & DTK_TIME_M) != DTK_TIME_M)
1240 /* timezone not specified? then find local timezone if possible */
1241 if ((tzp != NULL) && (!(fmask & DTK_M(TZ))))
1247 * daylight savings time modifier but no standard timezone? then
1250 if (fmask & DTK_M(DTZMOD))
1253 GetCurrentTime(tmp);
1254 tmp->tm_hour = tm->tm_hour;
1255 tmp->tm_min = tm->tm_min;
1256 tmp->tm_sec = tm->tm_sec;
1258 *tzp = DetermineLocalTimeZone(tmp);
1259 tm->tm_isdst = tmp->tm_isdst;
1263 } /* DecodeTimeOnly() */
1266 * Decode date string which includes delimiters.
1267 * Insist on a complete set of fields.
1270 DecodeDate(char *str, int fmask, int *tmask, struct tm * tm)
1278 int is2digits = FALSE;
1282 char *field[MAXDATEFIELDS];
1284 /* parse this string... */
1285 while ((*str != '\0') && (nf < MAXDATEFIELDS))
1287 /* skip field separators */
1288 while (!isalnum((unsigned char) *str))
1292 if (isdigit((unsigned char) *str))
1294 while (isdigit((unsigned char) *str))
1297 else if (isalpha((unsigned char) *str))
1299 while (isalpha((unsigned char) *str))
1303 /* Just get rid of any non-digit, non-alpha characters... */
1310 /* don't allow too many fields */
1317 /* look first for text fields, since that will be unambiguous month */
1318 for (i = 0; i < nf; i++)
1320 if (isalpha((unsigned char) *field[i]))
1322 type = DecodeSpecial(i, field[i], &val);
1326 dmask = DTK_M(type);
1346 /* mark this field as being completed */
1351 /* now pick up remaining numeric fields */
1352 for (i = 0; i < nf; i++)
1354 if (field[i] == NULL)
1357 if ((len = strlen(field[i])) <= 0)
1360 if (DecodeNumber(len, field[i], fmask, &dmask, tm, &fsec, &is2digits) != 0)
1370 if ((fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M)
1373 /* there is no year zero in AD/BC notation; i.e. "1 BC" == year 0 */
1376 if (tm->tm_year > 0)
1377 tm->tm_year = -(tm->tm_year - 1);
1379 elog(ERROR, "Inconsistant use of year %04d and 'BC'", tm->tm_year);
1383 if (tm->tm_year < 70)
1384 tm->tm_year += 2000;
1385 else if (tm->tm_year < 100)
1386 tm->tm_year += 1900;
1390 } /* DecodeDate() */
1394 * Decode time string which includes delimiters.
1395 * Only check the lower limit on hours, since this same code
1396 * can be used to represent time spans.
1399 DecodeTime(char *str, int fmask, int *tmask, struct tm * tm, double *fsec)
1403 *tmask = DTK_TIME_M;
1405 tm->tm_hour = strtol(str, &cp, 10);
1409 tm->tm_min = strtol(str, &cp, 10);
1416 else if (*cp != ':')
1424 tm->tm_sec = strtol(str, &cp, 10);
1427 else if (*cp == '.')
1430 *fsec = strtod(str, &cp);
1438 /* do a sanity check */
1439 if ((tm->tm_hour < 0)
1440 || (tm->tm_min < 0) || (tm->tm_min > 59)
1441 || (tm->tm_sec < 0) || (tm->tm_sec > 59))
1445 } /* DecodeTime() */
1449 * Interpret numeric field as a date value in context.
1452 DecodeNumber(int flen, char *str, int fmask,
1453 int *tmask, struct tm * tm, double *fsec, int *is2digits)
1460 val = strtol(str, &cp, 10);
1465 *fsec = strtod(cp, &cp);
1470 /* Special case day of year? */
1471 if ((flen == 3) && (fmask & DTK_M(YEAR))
1472 && ((val >= 1) && (val <= 366)))
1474 *tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
1476 j2date((date2j(tm->tm_year, 1, 1) + tm->tm_yday - 1),
1477 &tm->tm_year, &tm->tm_mon, &tm->tm_mday);
1482 * Enough digits to be unequivocal year? Used to test for 4 digits or
1483 * more, but we now test first for a three-digit doy so anything
1484 * bigger than two digits had better be an explicit year.
1485 * - thomas 1999-01-09
1486 * Back to requiring a 4 digit year. We accept a two digit
1487 * year farther down. - thomas 2000-03-28
1491 *tmask = DTK_M(YEAR);
1493 /* already have a year? then see if we can substitute... */
1494 if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(DAY)))
1495 && ((tm->tm_year >= 1) && (tm->tm_year <= 31)))
1497 tm->tm_mday = tm->tm_year;
1498 *tmask = DTK_M(DAY);
1504 /* already have year? then could be month */
1505 else if ((fmask & DTK_M(YEAR)) && (!(fmask & DTK_M(MONTH)))
1506 && ((val >= 1) && (val <= 12)))
1508 *tmask = DTK_M(MONTH);
1512 /* no year and EuroDates enabled? then could be day */
1513 else if ((EuroDates || (fmask & DTK_M(MONTH)))
1514 && (!(fmask & DTK_M(YEAR)) && !(fmask & DTK_M(DAY)))
1515 && ((val >= 1) && (val <= 31)))
1517 *tmask = DTK_M(DAY);
1520 else if ((!(fmask & DTK_M(MONTH)))
1521 && ((val >= 1) && (val <= 12)))
1523 *tmask = DTK_M(MONTH);
1526 else if ((!(fmask & DTK_M(DAY)))
1527 && ((val >= 1) && (val <= 31)))
1529 *tmask = DTK_M(DAY);
1534 * Check for 2 or 4 or more digits, but currently we reach here only
1535 * if two digits. - thomas 2000-03-28
1537 else if (!(fmask & DTK_M(YEAR))
1538 && ((flen >= 4) || (flen == 2)))
1540 *tmask = DTK_M(YEAR);
1543 /* adjust ONLY if exactly two digits... */
1544 *is2digits = (flen == 2);
1550 } /* DecodeNumber() */
1553 /* DecodeNumberField()
1554 * Interpret numeric string as a concatenated date field.
1557 DecodeNumberField(int len, char *str, int fmask,
1558 int *tmask, struct tm * tm, double *fsec, int *is2digits)
1565 *tmask = DTK_DATE_M;
1567 tm->tm_mday = atoi(str + 6);
1569 tm->tm_mon = atoi(str + 4);
1571 tm->tm_year = atoi(str + 0);
1572 /* yymmdd or hhmmss? */
1576 if (fmask & DTK_DATE_M)
1578 *tmask = DTK_TIME_M;
1579 tm->tm_sec = atoi(str + 4);
1581 tm->tm_min = atoi(str + 2);
1583 tm->tm_hour = atoi(str + 0);
1587 *tmask = DTK_DATE_M;
1588 tm->tm_mday = atoi(str + 4);
1590 tm->tm_mon = atoi(str + 2);
1592 tm->tm_year = atoi(str + 0);
1597 else if ((len == 5) && !(fmask & DTK_DATE_M))
1599 *tmask = DTK_DATE_M;
1600 tm->tm_mday = atoi(str + 2);
1603 tm->tm_year = atoi(str + 0);
1606 else if (strchr(str, '.') != NULL)
1608 *tmask = DTK_TIME_M;
1609 tm->tm_sec = strtod((str + 4), &cp);
1610 if (cp == (str + 4))
1613 *fsec = strtod(cp, NULL);
1615 tm->tm_min = strtod((str + 2), &cp);
1617 tm->tm_hour = strtod((str + 0), &cp);
1624 } /* DecodeNumberField() */
1628 * Interpret string as a numeric timezone.
1631 DecodeTimezone(char *str, int *tzp)
1639 /* assume leading character is "+" or "-" */
1640 hr = strtol((str + 1), &cp, 10);
1642 /* explicit delimiter? */
1645 min = strtol((cp + 1), &cp, 10);
1647 /* otherwise, might have run things together... */
1649 else if ((*cp == '\0') && ((len = strlen(str)) > 3))
1651 min = strtol((str + len - 2), &cp, 10);
1652 *(str + len - 2) = '\0';
1653 hr = strtol((str + 1), &cp, 10);
1659 tz = (hr * 60 + min) * 60;
1665 } /* DecodeTimezone() */
1668 /* DecodePosixTimezone()
1669 * Interpret string as a POSIX-compatible timezone:
1672 * - thomas 2000-03-15
1675 DecodePosixTimezone(char *str, int *tzp)
1684 while ((*cp != '\0') && isalpha((unsigned char) *cp))
1687 if (DecodeTimezone(cp, &tz) != 0)
1692 type = DecodeSpecial(MAXDATEFIELDS - 1, str, &val);
1699 *tzp = (val * 60) - tz;
1707 } /* DecodePosixTimezone() */
1711 * Decode text string using lookup table.
1712 * Implement a cache lookup since it is likely that dates
1713 * will be related in format.
1716 DecodeSpecial(int field, char *lowtoken, int *val)
1721 if ((datecache[field] != NULL)
1722 && (strncmp(lowtoken, datecache[field]->token, TOKMAXLEN) == 0))
1723 tp = datecache[field];
1727 if (Australian_timezones)
1728 tp = datebsearch(lowtoken, australian_datetktbl,
1729 australian_szdatetktbl);
1731 tp = datebsearch(lowtoken, datetktbl, szdatetktbl);
1733 datecache[field] = tp;
1736 type = UNKNOWN_FIELD;
1757 } /* DecodeSpecial() */
1760 /* DecodeDateDelta()
1761 * Interpret previously parsed fields for general time interval.
1762 * Return 0 if decoded and -1 if problems.
1764 * Allow "date" field DTK_DATE since this could be just
1765 * an unsigned floating point number. - thomas 1997-11-16
1767 * Allow ISO-style time span, with implicit units on number of days
1768 * preceeding an hh:mm:ss field. - thomas 1998-04-30
1771 DecodeDateDelta(char **field, int *ftype, int nf, int *dtype, struct tm * tm, double *fsec)
1773 int is_before = FALSE;
1795 /* read through list backwards to pick up units before values */
1796 for (i = nf - 1; i >= 0; i--)
1801 if (DecodeTime(field[i], fmask, &tmask, tm, fsec) != 0)
1809 * Timezone is a token with a leading sign character and
1810 * otherwise the same as a non-signed time field
1812 Assert((*field[i] == '-') || (*field[i] == '+'));
1815 * A single signed number ends up here, but will be
1816 * rejected by DecodeTime(). So, work this out to drop
1817 * through to DTK_NUMBER, which *can* tolerate this.
1820 while ((*cp != '\0') && (*cp != ':') && (*cp != '.'))
1823 && (DecodeTime((field[i] + 1), fmask, &tmask, tm, fsec) == 0))
1825 if (*field[i] == '-')
1827 /* flip the sign on all fields */
1828 tm->tm_hour = -tm->tm_hour;
1829 tm->tm_min = -tm->tm_min;
1830 tm->tm_sec = -tm->tm_sec;
1835 * Set the next type to be a day, if units are not
1836 * specified. This handles the case of '1 +02:03'
1837 * since we are reading right to left.
1843 else if (type == IGNORE)
1849 * Got a decimal point? Then assume some sort of
1850 * seconds specification
1854 else if (*cp == '\0')
1858 * Only a signed integer? Then must assume a
1859 * timezone-like usage
1868 val = strtol(field[i], &cp, 10);
1875 fval = strtod(cp, &cp);
1882 else if (*cp == '\0')
1887 tmask = 0; /* DTK_M(type); */
1892 *fsec += ((val + fval) * 1e-6);
1896 *fsec += ((val + fval) * 1e-3);
1902 tmask = DTK_M(SECOND);
1908 tm->tm_sec += (fval * 60);
1909 tmask = DTK_M(MINUTE);
1915 tm->tm_sec += (fval * 3600);
1916 tmask = DTK_M(HOUR);
1922 tm->tm_sec += (fval * 86400);
1923 tmask = ((fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY));
1927 tm->tm_mday += val * 7;
1929 tm->tm_sec += (fval * (7 * 86400));
1930 tmask = ((fmask & DTK_M(DAY)) ? 0 : DTK_M(DAY));
1936 tm->tm_sec += (fval * (30 * 86400));
1937 tmask = DTK_M(MONTH);
1943 tm->tm_mon += (fval * 12);
1944 tmask = ((fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR));
1948 tm->tm_year += val * 10;
1950 tm->tm_mon += (fval * 120);
1951 tmask = ((fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR));
1955 tm->tm_year += val * 100;
1957 tm->tm_mon += (fval * 1200);
1958 tmask = ((fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR));
1961 case DTK_MILLENNIUM:
1962 tm->tm_year += val * 1000;
1964 tm->tm_mon += (fval * 12000);
1965 tmask = ((fmask & DTK_M(YEAR)) ? 0 : DTK_M(YEAR));
1975 type = DecodeUnits(i, field[i], &val);
1979 tmask = 0; /* DTK_M(type); */
1992 tmask = (DTK_DATE_M || DTK_TIME_M);
2012 TMODULO(*fsec, sec, 1e0);
2019 tm->tm_sec = -(tm->tm_sec);
2020 tm->tm_min = -(tm->tm_min);
2021 tm->tm_hour = -(tm->tm_hour);
2022 tm->tm_mday = -(tm->tm_mday);
2023 tm->tm_mon = -(tm->tm_mon);
2024 tm->tm_year = -(tm->tm_year);
2027 /* ensure that at least one time field has been found */
2028 return (fmask != 0) ? 0 : -1;
2029 } /* DecodeDateDelta() */
2033 * Decode text string using lookup table.
2034 * This routine supports time interval decoding.
2037 DecodeUnits(int field, char *lowtoken, int *val)
2042 if ((deltacache[field] != NULL)
2043 && (strncmp(lowtoken, deltacache[field]->token, TOKMAXLEN) == 0))
2044 tp = deltacache[field];
2047 tp = datebsearch(lowtoken, deltatktbl, szdeltatktbl);
2049 deltacache[field] = tp;
2052 type = UNKNOWN_FIELD;
2058 if ((type == TZ) || (type == DTZ))
2065 } /* DecodeUnits() */
2069 * Binary search -- from Knuth (6.2.1) Algorithm B. Special case like this
2070 * is WAY faster than the generic bsearch().
2073 datebsearch(char *key, datetkn *base, unsigned int nel)
2075 datetkn *last = base + nel - 1,
2079 while (last >= base)
2081 position = base + ((last - base) >> 1);
2082 result = key[0] - position->token[0];
2085 result = strncmp(key, position->token, TOKMAXLEN);
2090 last = position - 1;
2092 base = position + 1;
2099 * Encode date as local time.
2102 EncodeDateOnly(struct tm * tm, int style, char *str)
2104 if ((tm->tm_mon < 1) || (tm->tm_mon > 12))
2110 /* compatible with ISO date formats */
2111 if (tm->tm_year > 0)
2112 sprintf(str, "%04d-%02d-%02d",
2113 tm->tm_year, tm->tm_mon, tm->tm_mday);
2115 sprintf(str, "%04d-%02d-%02d %s",
2116 -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
2120 /* compatible with Oracle/Ingres date formats */
2122 sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
2124 sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
2125 if (tm->tm_year > 0)
2126 sprintf((str + 5), "/%04d", tm->tm_year);
2128 sprintf((str + 5), "/%04d %s", -(tm->tm_year - 1), "BC");
2131 case USE_GERMAN_DATES:
2132 /* German-style date format */
2133 sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
2134 if (tm->tm_year > 0)
2135 sprintf((str + 5), ".%04d", tm->tm_year);
2137 sprintf((str + 5), ".%04d %s", -(tm->tm_year - 1), "BC");
2140 case USE_POSTGRES_DATES:
2142 /* traditional date-only style for Postgres */
2144 sprintf(str, "%02d-%02d", tm->tm_mday, tm->tm_mon);
2146 sprintf(str, "%02d-%02d", tm->tm_mon, tm->tm_mday);
2147 if (tm->tm_year > 0)
2148 sprintf((str + 5), "-%04d", tm->tm_year);
2150 sprintf((str + 5), "-%04d %s", -(tm->tm_year - 1), "BC");
2155 } /* EncodeDateOnly() */
2159 * Encode time fields only.
2162 EncodeTimeOnly(struct tm * tm, double fsec, int *tzp, int style, char *str)
2166 if ((tm->tm_hour < 0) || (tm->tm_hour > 24))
2169 sec = (tm->tm_sec + fsec);
2171 sprintf(str, "%02d:%02d", tm->tm_hour, tm->tm_min);
2173 /* If we have fractional seconds, then include a decimal point
2174 * We will do up to 6 fractional digits, and we have rounded any inputs
2175 * to eliminate anything to the right of 6 digits anyway.
2176 * If there are no fractional seconds, then do not bother printing
2177 * a decimal point at all. - thomas 2001-09-29
2180 sprintf((str + strlen(str)), ":%013.10f", sec);
2181 /* chop off trailing pairs of zeros... */
2182 while ((strcmp((str + strlen(str) - 2), "00") == 0)
2183 && (*(str + strlen(str) - 3) != '.'))
2185 *(str + strlen(str) - 2) = '\0';
2190 sprintf((str + strlen(str)), ":%02.0f", sec);
2198 hour = -(*tzp / 3600);
2199 min = ((abs(*tzp) / 60) % 60);
2200 sprintf((str + strlen(str)), ((min != 0) ? "%+03d:%02d" : "%+03d"), hour, min);
2204 } /* EncodeTimeOnly() */
2208 * Encode date and time interpreted as local time.
2209 * Support several date styles:
2210 * Postgres - day mon hh:mm:ss yyyy tz
2211 * SQL - mm/dd/yyyy hh:mm:ss.ss tz
2212 * ISO - yyyy-mm-dd hh:mm:ss+/-tz
2213 * German - dd.mm/yyyy hh:mm:ss tz
2214 * Variants (affects order of month and day for Postgres and SQL styles):
2216 * European - dd/mm/yyyy
2219 EncodeDateTime(struct tm * tm, double fsec, int *tzp, char **tzn, int style, char *str)
2226 if ((tm->tm_mon < 1) || (tm->tm_mon > 12))
2229 sec = (tm->tm_sec + fsec);
2233 /* compatible with ISO date formats */
2236 if (tm->tm_year > 0)
2238 sprintf(str, "%04d-%02d-%02d %02d:%02d",
2239 tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
2241 /* If we have fractional seconds, then include a decimal point
2242 * We will do up to 6 fractional digits, and we have rounded any inputs
2243 * to eliminate anything to the right of 6 digits anyway.
2244 * If there are no fractional seconds, then do not bother printing
2245 * a decimal point at all. - thomas 2001-09-29
2248 sprintf((str + strlen(str)), ":%013.10f", sec);
2249 /* chop off trailing pairs of zeros... */
2250 while ((strcmp((str + strlen(str) - 2), "00") == 0)
2251 && (*(str + strlen(str) - 3) != '.'))
2253 *(str + strlen(str) - 2) = '\0';
2258 sprintf((str + strlen(str)), ":%02.0f", sec);
2261 if ((*tzn != NULL) && (tm->tm_isdst >= 0))
2265 hour = -(*tzp / 3600);
2266 min = ((abs(*tzp) / 60) % 60);
2273 sprintf((str + strlen(str)), ((min != 0) ? "%+03d:%02d" : "%+03d"), hour, min);
2279 if (tm->tm_hour || tm->tm_min)
2280 sprintf(str, "%04d-%02d-%02d %02d:%02d %s",
2281 -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, "BC");
2283 sprintf(str, "%04d-%02d-%02d %s",
2284 -(tm->tm_year - 1), tm->tm_mon, tm->tm_mday, "BC");
2288 /* compatible with Oracle/Ingres date formats */
2291 sprintf(str, "%02d/%02d", tm->tm_mday, tm->tm_mon);
2293 sprintf(str, "%02d/%02d", tm->tm_mon, tm->tm_mday);
2295 if (tm->tm_year > 0)
2297 sprintf((str + 5), "/%04d %02d:%02d",
2298 tm->tm_year, tm->tm_hour, tm->tm_min);
2300 /* If we have fractional seconds, then include a decimal point
2301 * We will do up to 6 fractional digits, and we have rounded any inputs
2302 * to eliminate anything to the right of 6 digits anyway.
2303 * If there are no fractional seconds, then do not bother printing
2304 * a decimal point at all. - thomas 2001-09-29
2307 sprintf((str + strlen(str)), ":%013.10f", sec);
2308 /* chop off trailing pairs of zeros... */
2309 while ((strcmp((str + strlen(str) - 2), "00") == 0)
2310 && (*(str + strlen(str) - 3) != '.'))
2312 *(str + strlen(str) - 2) = '\0';
2317 sprintf((str + strlen(str)), ":%02.0f", sec);
2320 if ((*tzn != NULL) && (tm->tm_isdst >= 0))
2321 sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);
2324 sprintf((str + 5), "/%04d %02d:%02d %s",
2325 -(tm->tm_year - 1), tm->tm_hour, tm->tm_min, "BC");
2328 /* German variant on European style */
2329 case USE_GERMAN_DATES:
2330 sprintf(str, "%02d.%02d", tm->tm_mday, tm->tm_mon);
2331 if (tm->tm_year > 0)
2333 sprintf((str + 5), ".%04d %02d:%02d",
2334 tm->tm_year, tm->tm_hour, tm->tm_min);
2336 /* If we have fractional seconds, then include a decimal point
2337 * We will do up to 6 fractional digits, and we have rounded any inputs
2338 * to eliminate anything to the right of 6 digits anyway.
2339 * If there are no fractional seconds, then do not bother printing
2340 * a decimal point at all. - thomas 2001-09-29
2343 sprintf((str + strlen(str)), ":%013.10f", sec);
2344 /* chop off trailing pairs of zeros... */
2345 while ((strcmp((str + strlen(str) - 2), "00") == 0)
2346 && (*(str + strlen(str) - 3) != '.'))
2348 *(str + strlen(str) - 2) = '\0';
2353 sprintf((str + strlen(str)), ":%02.0f", sec);
2356 if ((*tzn != NULL) && (tm->tm_isdst >= 0))
2357 sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);
2360 sprintf((str + 5), ".%04d %02d:%02d %s",
2361 -(tm->tm_year - 1), tm->tm_hour, tm->tm_min, "BC");
2364 /* backward-compatible with traditional Postgres abstime dates */
2365 case USE_POSTGRES_DATES:
2367 day = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday);
2368 tm->tm_wday = j2day(day);
2370 strncpy(str, days[tm->tm_wday], 3);
2371 strcpy((str + 3), " ");
2374 sprintf((str + 4), "%02d %3s", tm->tm_mday, months[tm->tm_mon - 1]);
2376 sprintf((str + 4), "%3s %02d", months[tm->tm_mon - 1], tm->tm_mday);
2378 if (tm->tm_year > 0)
2380 sprintf((str + 10), " %02d:%02d", tm->tm_hour, tm->tm_min);
2382 /* If we have fractional seconds, then include a decimal point
2383 * We will do up to 6 fractional digits, and we have rounded any inputs
2384 * to eliminate anything to the right of 6 digits anyway.
2385 * If there are no fractional seconds, then do not bother printing
2386 * a decimal point at all. - thomas 2001-09-29
2389 sprintf((str + strlen(str)), ":%013.10f", sec);
2390 /* chop off trailing pairs of zeros... */
2391 while ((strcmp((str + strlen(str) - 2), "00") == 0)
2392 && (*(str + strlen(str) - 3) != '.'))
2394 *(str + strlen(str) - 2) = '\0';
2399 sprintf((str + strlen(str)), ":%02.0f", sec);
2402 sprintf((str + strlen(str)), " %04d", tm->tm_year);
2403 if ((*tzn != NULL) && (tm->tm_isdst >= 0))
2404 sprintf((str + strlen(str)), " %.*s", MAXTZLEN, *tzn);
2408 sprintf((str + 10), " %02d:%02d %04d %s",
2409 tm->tm_hour, tm->tm_min, -(tm->tm_year - 1), "BC");
2415 } /* EncodeDateTime() */
2419 * Interpret time structure as a delta time and convert to string.
2421 * Support "traditional Postgres" and ISO-8601 styles.
2422 * Actually, afaik ISO does not address time interval formatting,
2423 * but this looks similar to the spec for absolute date/time.
2424 * - thomas 1998-04-30
2427 EncodeTimeSpan(struct tm * tm, double fsec, int style, char *str)
2429 int is_before = FALSE;
2430 int is_nonzero = FALSE;
2434 * The sign of year and month are guaranteed to match, since they are
2435 * stored internally as "month". But we'll need to check for is_before
2436 * and is_nonzero when determining the signs of hour/minute/seconds
2441 /* compatible with ISO date formats */
2443 if (tm->tm_year != 0)
2445 sprintf(cp, "%d year%s",
2446 tm->tm_year, ((tm->tm_year != 1) ? "s" : ""));
2448 is_before = (tm->tm_year < 0);
2452 if (tm->tm_mon != 0)
2454 sprintf(cp, "%s%s%d mon%s", (is_nonzero ? " " : ""),
2455 ((is_before && (tm->tm_mon > 0)) ? "+" : ""),
2456 tm->tm_mon, ((tm->tm_mon != 1) ? "s" : ""));
2458 is_before = (tm->tm_mon < 0);
2462 if (tm->tm_mday != 0)
2464 sprintf(cp, "%s%s%d day%s", (is_nonzero ? " " : ""),
2465 ((is_before && (tm->tm_mday > 0)) ? "+" : ""),
2466 tm->tm_mday, ((tm->tm_mday != 1) ? "s" : ""));
2468 is_before = (tm->tm_mday < 0);
2471 if ((!is_nonzero) || (tm->tm_hour != 0) || (tm->tm_min != 0)
2472 || (tm->tm_sec != 0) || (fsec != 0))
2474 int minus = ((tm->tm_hour < 0) || (tm->tm_min < 0)
2475 || (tm->tm_sec < 0) || (fsec < 0));
2477 sprintf(cp, "%s%s%02d:%02d", (is_nonzero ? " " : ""),
2478 (minus ? "-" : (is_before ? "+" : "")),
2479 abs(tm->tm_hour), abs(tm->tm_min));
2481 /* Mark as "non-zero" since the fields are now filled in */
2484 /* fractional seconds? */
2488 sprintf(cp, ":%05.2f", fabs(fsec));
2492 /* otherwise, integer seconds only? */
2494 else if (tm->tm_sec != 0)
2496 sprintf(cp, ":%02d", abs(tm->tm_sec));
2503 case USE_POSTGRES_DATES:
2508 if (tm->tm_year != 0)
2510 int year = tm->tm_year;
2512 if (tm->tm_year < 0)
2515 sprintf(cp, "%d year%s", year,
2516 ((year != 1) ? "s" : ""));
2518 is_before = (tm->tm_year < 0);
2522 if (tm->tm_mon != 0)
2524 int mon = tm->tm_mon;
2526 if (is_before || ((!is_nonzero) && (tm->tm_mon < 0)))
2529 sprintf(cp, "%s%d mon%s", (is_nonzero ? " " : ""), mon,
2530 ((mon != 1) ? "s" : ""));
2533 is_before = (tm->tm_mon < 0);
2537 if (tm->tm_mday != 0)
2539 int day = tm->tm_mday;
2541 if (is_before || ((!is_nonzero) && (tm->tm_mday < 0)))
2544 sprintf(cp, "%s%d day%s", (is_nonzero ? " " : ""), day,
2545 ((day != 1) ? "s" : ""));
2548 is_before = (tm->tm_mday < 0);
2551 if (tm->tm_hour != 0)
2553 int hour = tm->tm_hour;
2555 if (is_before || ((!is_nonzero) && (tm->tm_hour < 0)))
2558 sprintf(cp, "%s%d hour%s", (is_nonzero ? " " : ""), hour,
2559 ((hour != 1) ? "s" : ""));
2562 is_before = (tm->tm_hour < 0);
2566 if (tm->tm_min != 0)
2568 int min = tm->tm_min;
2570 if (is_before || ((!is_nonzero) && (tm->tm_min < 0)))
2573 sprintf(cp, "%s%d min%s", (is_nonzero ? " " : ""), min,
2574 ((min != 1) ? "s" : ""));
2577 is_before = (tm->tm_min < 0);
2581 /* fractional seconds? */
2588 if (is_before || ((!is_nonzero) && (fsec < 0)))
2591 sprintf(cp, "%s%.2f secs", (is_nonzero ? " " : ""), sec);
2594 is_before = (fsec < 0);
2597 /* otherwise, integer seconds only? */
2599 else if (tm->tm_sec != 0)
2601 int sec = tm->tm_sec;
2603 if (is_before || ((!is_nonzero) && (tm->tm_sec < 0)))
2606 sprintf(cp, "%s%d sec%s", (is_nonzero ? " " : ""), sec,
2607 ((sec != 1) ? "s" : ""));
2610 is_before = (tm->tm_sec < 0);
2616 /* identically zero? then put in a unitless zero... */
2623 if (is_before && (style == USE_POSTGRES_DATES))
2630 } /* EncodeTimeSpan() */
2633 void ClearDateCache(bool dummy)
2637 for (i=0; i < MAXDATEFIELDS; i++)
2638 datecache[i] = NULL;