1 #include "postgres_fe.h"
10 #include "pgtypes_error.h"
11 #include "pgtypes_date.h"
14 PGTYPESdate_from_timestamp(timestamp dt)
18 dDate = 0; /* suppress compiler warning */
20 if (TIMESTAMP_NOT_FINITE(dt))
23 #ifdef HAVE_INT64_TIMESTAMP
24 /* Microseconds to days */
25 dDate = (dt / USECS_PER_DAY);
28 dDate = (dt / (double) SECS_PER_DAY);
35 PGTYPESdate_from_asc(char *str, char **endptr)
45 char *field[MAXDATEFIELDS];
46 int ftype[MAXDATEFIELDS];
47 char lowstr[MAXDATELEN + 1];
49 char **ptr = (endptr != NULL) ? endptr : &realptr;
51 bool EuroDates = FALSE;
54 if (strlen(str) >= sizeof(lowstr))
56 errno = PGTYPES_DATE_BAD_DATE;
60 if (ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf, ptr) != 0 ||
61 DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp, EuroDates) != 0)
63 errno = PGTYPES_DATE_BAD_DATE;
77 errno = PGTYPES_DATE_BAD_DATE;
81 dDate = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1));
87 PGTYPESdate_to_asc(date dDate)
91 char buf[MAXDATELEN + 1];
93 bool EuroDates = FALSE;
95 j2date(dDate + date2j(2000, 1, 1), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
96 EncodeDateOnly(tm, DateStyle, buf, EuroDates);
97 return pgtypes_strdup(buf);
101 PGTYPESdate_julmdy(date jd, int *mdy)
107 j2date((int) (jd + date2j(2000, 1, 1)), &y, &m, &d);
114 PGTYPESdate_mdyjul(int *mdy, date * jdate)
116 /* month is mdy[0] */
120 *jdate = (date) (date2j(mdy[2], mdy[0], mdy[1]) - date2j(2000, 1, 1));
124 PGTYPESdate_dayofweek(date dDate)
127 * Sunday: 0 Monday: 1 Tuesday: 2 Wednesday: 3 Thursday: 4
128 * Friday: 5 Saturday: 6
130 return (int) (dDate + date2j(2000, 1, 1) + 1) % 7;
134 PGTYPESdate_today(date * d)
138 GetCurrentDateTime(&ts);
139 *d = date2j(ts.tm_year, ts.tm_mon, ts.tm_mday) - date2j(2000, 1, 1);
143 #define PGTYPES_DATE_NUM_MAX_DIGITS 20 /* should suffice for most
146 #define PGTYPES_FMTDATE_DAY_DIGITS_LZ 1 /* LZ means "leading zeroes" */
147 #define PGTYPES_FMTDATE_DOW_LITERAL_SHORT 2
148 #define PGTYPES_FMTDATE_MONTH_DIGITS_LZ 3
149 #define PGTYPES_FMTDATE_MONTH_LITERAL_SHORT 4
150 #define PGTYPES_FMTDATE_YEAR_DIGITS_SHORT 5
151 #define PGTYPES_FMTDATE_YEAR_DIGITS_LONG 6
154 PGTYPESdate_fmt_asc(date dDate, char *fmtstring, char *outbuf)
163 * format items have to be sorted according to their length, since the
164 * first pattern that matches gets replaced by its value
167 "ddd", PGTYPES_FMTDATE_DOW_LITERAL_SHORT
170 "dd", PGTYPES_FMTDATE_DAY_DIGITS_LZ
173 "mmm", PGTYPES_FMTDATE_MONTH_LITERAL_SHORT
176 "mm", PGTYPES_FMTDATE_MONTH_DIGITS_LZ
179 "yyyy", PGTYPES_FMTDATE_YEAR_DIGITS_LONG
182 "yy", PGTYPES_FMTDATE_YEAR_DIGITS_SHORT
189 union un_fmt_comb replace_val;
197 /* XXX error handling ? */
198 /* copy the string over */
199 strcpy(outbuf, fmtstring);
202 j2date(dDate + date2j(2000, 1, 1), &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
203 dow = PGTYPESdate_dayofweek(dDate);
205 for (i = 0; mapping[i].format != NULL; i++)
207 while ((start_pattern = strstr(outbuf, mapping[i].format)) != NULL)
209 switch (mapping[i].component)
211 case PGTYPES_FMTDATE_DOW_LITERAL_SHORT:
212 replace_val.str_val = pgtypes_date_weekdays_short[dow];
213 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
215 case PGTYPES_FMTDATE_DAY_DIGITS_LZ:
216 replace_val.uint_val = tm.tm_mday;
217 replace_type = PGTYPES_TYPE_UINT_2_LZ;
219 case PGTYPES_FMTDATE_MONTH_LITERAL_SHORT:
220 replace_val.str_val = months[tm.tm_mon - 1];
221 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
223 case PGTYPES_FMTDATE_MONTH_DIGITS_LZ:
224 replace_val.uint_val = tm.tm_mon;
225 replace_type = PGTYPES_TYPE_UINT_2_LZ;
227 case PGTYPES_FMTDATE_YEAR_DIGITS_LONG:
228 replace_val.uint_val = tm.tm_year;
229 replace_type = PGTYPES_TYPE_UINT_4_LZ;
231 case PGTYPES_FMTDATE_YEAR_DIGITS_SHORT:
232 replace_val.uint_val = tm.tm_year % 1000;
233 replace_type = PGTYPES_TYPE_UINT_2_LZ;
238 * should not happen, set something anyway
240 replace_val.str_val = " ";
241 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
243 switch (replace_type)
245 case PGTYPES_TYPE_STRING_MALLOCED:
246 case PGTYPES_TYPE_STRING_CONSTANT:
247 strncpy(start_pattern, replace_val.str_val,
248 strlen(replace_val.str_val));
249 if (replace_type == PGTYPES_TYPE_STRING_MALLOCED)
250 free(replace_val.str_val);
252 case PGTYPES_TYPE_UINT:
254 char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
258 snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
259 "%u", replace_val.uint_val);
260 strncpy(start_pattern, t, strlen(t));
264 case PGTYPES_TYPE_UINT_2_LZ:
266 char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
270 snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
271 "%02u", replace_val.uint_val);
272 strncpy(start_pattern, t, strlen(t));
276 case PGTYPES_TYPE_UINT_4_LZ:
278 char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
282 snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
283 "%04u", replace_val.uint_val);
284 strncpy(start_pattern, t, strlen(t));
291 * doesn't happen (we set replace_type to
292 * PGTYPES_TYPE_STRING_CONSTANT in case of an error above)
303 * PGTYPESdate_defmt_asc
305 * function works as follows:
306 * - first we analyze the paramters
307 * - if this is a special case with no delimiters, add delimters
308 * - find the tokens. First we look for numerical values. If we have found
309 * less than 3 tokens, we check for the months' names and thereafter for
310 * the abbreviations of the months' names.
311 * - then we see which parameter should be the date, the month and the
312 * year and from these values we calculate the date
315 #define PGTYPES_DATE_MONTH_MAXLENGTH 20 /* probably even less :-) */
317 PGTYPESdate_defmt_asc(date * d, char *fmt, char *str)
320 * token[2] = { 4,6 } means that token 2 starts at position 4 and ends at
321 * (including) position 6
324 int token_values[3] = {-1, -1, -1};
325 char *fmt_token_order;
335 tm.tm_year = tm.tm_mon = tm.tm_mday = 0; /* keep compiler quiet */
337 if (!d || !str || !fmt)
339 errno = PGTYPES_DATE_ERR_EARGS;
343 /* analyze the fmt string */
344 fmt_ystart = strstr(fmt, "yy");
345 fmt_mstart = strstr(fmt, "mm");
346 fmt_dstart = strstr(fmt, "dd");
348 if (!fmt_ystart || !fmt_mstart || !fmt_dstart)
350 errno = PGTYPES_DATE_ERR_EARGS;
354 if (fmt_ystart < fmt_mstart)
357 if (fmt_dstart < fmt_ystart)
360 fmt_token_order = "dym";
362 else if (fmt_dstart > fmt_mstart)
365 fmt_token_order = "ymd";
370 fmt_token_order = "ydm";
375 /* fmt_ystart > fmt_mstart */
377 if (fmt_dstart < fmt_mstart)
380 fmt_token_order = "dmy";
382 else if (fmt_dstart > fmt_ystart)
385 fmt_token_order = "myd";
390 fmt_token_order = "mdy";
395 * handle the special cases where there is no delimiter between the
396 * digits. If we see this:
398 * only digits, 6 or 8 bytes then it might be ddmmyy and ddmmyyyy (or
401 * we reduce it to a string with delimiters and continue processing
404 /* check if we have only digits */
406 for (i = 0; str[i]; i++)
408 if (!isdigit((unsigned char) str[i]))
420 if (i != 8 && i != 6)
422 errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
425 /* okay, this really is the special case */
428 * as long as the string, one additional byte for the terminator and 2
429 * for the delimiters between the 3 fiedls
431 str_copy = pgtypes_alloc(strlen(str) + 1 + 2);
435 /* determine length of the fragments */
444 if (fmt_token_order[0] == 'y')
450 else if (fmt_token_order[1] == 'y')
466 * XXX: Here we could calculate the positions of the tokens and save
467 * the for loop down there where we again check with isdigit() for
470 for (i = 0; i < 3; i++)
475 start_pos += frag_length[0];
477 start_pos += frag_length[1];
479 strncpy(str_copy + target_pos, str + start_pos,
481 target_pos += frag_length[i];
484 str_copy[target_pos] = ' ';
488 str_copy[target_pos] = '\0';
492 str_copy = pgtypes_strdup(str);
496 /* convert the whole string to lower case */
497 for (i = 0; str_copy[i]; i++)
498 str_copy[i] = (char) pg_tolower((unsigned char) str_copy[i]);
501 /* look for numerical tokens */
504 for (i = 0; i < strlen(str_copy); i++)
506 if (!isdigit((unsigned char) str_copy[i]) && reading_digit)
508 /* the token is finished */
509 token[token_count][1] = i - 1;
513 else if (isdigit((unsigned char) str_copy[i]) && !reading_digit)
515 /* we have found a token */
516 token[token_count][0] = i;
522 * we're at the end of the input string, but maybe we are still reading a
527 token[token_count][1] = i - 1;
535 * not all tokens found, no way to find 2 missing tokens with string
539 errno = PGTYPES_DATE_ERR_ENOTDMY;
543 if (token_count != 3)
546 * not all tokens found but we may find another one with string
547 * matches by testing for the months names and months abbreviations
549 char *month_lower_tmp = pgtypes_alloc(PGTYPES_DATE_MONTH_MAXLENGTH);
556 if (!month_lower_tmp)
558 /* free variables we alloc'ed before */
562 list = pgtypes_date_months;
563 for (i = 0; list[i]; i++)
565 for (j = 0; j < PGTYPES_DATE_MONTH_MAXLENGTH; j++)
567 month_lower_tmp[j] = (char) pg_tolower((unsigned char) list[i][j]);
568 if (!month_lower_tmp[j])
570 /* properly terminated */
574 if ((start_pos = strstr(str_copy, month_lower_tmp)))
576 offset = start_pos - str_copy;
579 * sort the new token into the numeric tokens, shift them if
582 if (offset < token[0][0])
584 token[2][0] = token[1][0];
585 token[2][1] = token[1][1];
586 token[1][0] = token[0][0];
587 token[1][1] = token[0][1];
590 else if (offset < token[1][0])
592 token[2][0] = token[1][0];
593 token[2][1] = token[1][1];
598 token[token_count][0] = offset;
599 token[token_count][1] = offset + strlen(month_lower_tmp) - 1;
602 * the value is the index of the month in the array of months
603 * + 1 (January is month 0)
605 token_values[token_count] = i + 1;
611 * evil[tm] hack: if we read the pgtypes_date_months and haven't
612 * found a match, reset list to point to pgtypes_date_months_short
613 * and reset the counter variable i
615 if (list == pgtypes_date_months)
617 if (list[i + 1] == NULL)
626 free(month_lower_tmp);
628 errno = PGTYPES_DATE_ERR_ENOTDMY;
633 * here we found a month. token[token_count] and
634 * token_values[token_count] reflect the month's details.
636 * only the month can be specified with a literal. Here we can do a
637 * quick check if the month is at the right position according to the
638 * format string because we can check if the token that we expect to
639 * be the month is at the position of the only token that already has
640 * a value. If we wouldn't check here we could say "December 4 1990"
641 * with a fmt string of "dd mm yy" for 12 April 1990.
643 if (fmt_token_order[token_count] != 'm')
645 /* deal with the error later on */
646 token_values[token_count] = -1;
648 free(month_lower_tmp);
651 /* terminate the tokens with ASCII-0 and get their values */
652 for (i = 0; i < 3; i++)
654 *(str_copy + token[i][1] + 1) = '\0';
655 /* A month already has a value set, check for token_value == -1 */
656 if (token_values[i] == -1)
659 token_values[i] = strtol(str_copy + token[i][0], (char **) NULL, 10);
660 /* strtol sets errno in case of an error */
662 token_values[i] = -1;
664 if (fmt_token_order[i] == 'd')
665 tm.tm_mday = token_values[i];
666 else if (fmt_token_order[i] == 'm')
667 tm.tm_mon = token_values[i];
668 else if (fmt_token_order[i] == 'y')
669 tm.tm_year = token_values[i];
673 if (tm.tm_mday < 1 || tm.tm_mday > 31)
675 errno = PGTYPES_DATE_BAD_DAY;
679 if (tm.tm_mon < 1 || tm.tm_mon > MONTHS_PER_YEAR)
681 errno = PGTYPES_DATE_BAD_MONTH;
685 if (tm.tm_mday == 31 && (tm.tm_mon == 4 || tm.tm_mon == 6 || tm.tm_mon == 9 || tm.tm_mon == 11))
687 errno = PGTYPES_DATE_BAD_DAY;
691 if (tm.tm_mon == 2 && tm.tm_mday > 29)
693 errno = PGTYPES_DATE_BAD_DAY;
697 /* XXX: DBCENTURY ? */
699 *d = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - date2j(2000, 1, 1);