1/* src/interfaces/ecpg/pgtypeslib/datetime.c */
2
3#include "postgres_fe.h"
4
5#include <time.h>
6#include <ctype.h>
7#include <limits.h>
8
9#include "pgtypeslib_extern.h"
10#include "dt.h"
11#include "pgtypes_error.h"
12#include "pgtypes_date.h"
13
14date *
15PGTYPESdate_new(void)
16{
17 date *result;
18
19 result = (date *) pgtypes_alloc(sizeof(date));
20 /* result can be NULL if we run out of memory */
21 return result;
22}
23
24void
25PGTYPESdate_free(date * d)
26{
27 free(d);
28}
29
30date
31PGTYPESdate_from_timestamp(timestamp dt)
32{
33 date dDate;
34
35 dDate = 0; /* suppress compiler warning */
36
37 if (!TIMESTAMP_NOT_FINITE(dt))
38 {
39 /* Microseconds to days */
40 dDate = (dt / USECS_PER_DAY);
41 }
42
43 return dDate;
44}
45
46date
47PGTYPESdate_from_asc(char *str, char **endptr)
48{
49 date dDate;
50 fsec_t fsec;
51 struct tm tt,
52 *tm = &tt;
53 int dtype;
54 int nf;
55 char *field[MAXDATEFIELDS];
56 int ftype[MAXDATEFIELDS];
57 char lowstr[MAXDATELEN + MAXDATEFIELDS];
58 char *realptr;
59 char **ptr = (endptr != NULL) ? endptr : &realptr;
60
61 bool EuroDates = false;
62
63 errno = 0;
64 if (strlen(str) > MAXDATELEN)
65 {
66 errno = PGTYPES_DATE_BAD_DATE;
67 return INT_MIN;
68 }
69
70 if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
71 DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, EuroDates) != 0)
72 {
73 errno = PGTYPES_DATE_BAD_DATE;
74 return INT_MIN;
75 }
76
77 switch (dtype)
78 {
79 case DTK_DATE:
80 break;
81
82 case DTK_EPOCH:
83 if (GetEpochTime(tm) < 0)
84 {
85 errno = PGTYPES_DATE_BAD_DATE;
86 return INT_MIN;
87 }
88 break;
89
90 default:
91 errno = PGTYPES_DATE_BAD_DATE;
92 return INT_MIN;
93 }
94
95 dDate = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1));
96
97 return dDate;
98}
99
100char *
101PGTYPESdate_to_asc(date dDate)
102{
103 struct tm tt,
104 *tm = &tt;
105 char buf[MAXDATELEN + 1];
106 int DateStyle = 1;
107 bool EuroDates = false;
108
109 j2date(dDate + date2j(2000, 1, 1), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
110 EncodeDateOnly(tm, DateStyle, buf, EuroDates);
111 return pgtypes_strdup(buf);
112}
113
114void
115PGTYPESdate_julmdy(date jd, int *mdy)
116{
117 int y,
118 m,
119 d;
120
121 j2date((int) (jd + date2j(2000, 1, 1)), &y, &m, &d);
122 mdy[0] = m;
123 mdy[1] = d;
124 mdy[2] = y;
125}
126
127void
128PGTYPESdate_mdyjul(int *mdy, date * jdate)
129{
130 /* month is mdy[0] */
131 /* day is mdy[1] */
132 /* year is mdy[2] */
133
134 *jdate = (date) (date2j(mdy[2], mdy[0], mdy[1]) - date2j(2000, 1, 1));
135}
136
137int
138PGTYPESdate_dayofweek(date dDate)
139{
140 /*
141 * Sunday: 0 Monday: 1 Tuesday: 2 Wednesday: 3 Thursday: 4
142 * Friday: 5 Saturday: 6
143 */
144 return (int) (dDate + date2j(2000, 1, 1) + 1) % 7;
145}
146
147void
148PGTYPESdate_today(date * d)
149{
150 struct tm ts;
151
152 GetCurrentDateTime(&ts);
153 if (errno == 0)
154 *d = date2j(ts.tm_year, ts.tm_mon, ts.tm_mday) - date2j(2000, 1, 1);
155 return;
156}
157
158#define PGTYPES_DATE_NUM_MAX_DIGITS 20 /* should suffice for most
159 * years... */
160
161#define PGTYPES_FMTDATE_DAY_DIGITS_LZ 1 /* LZ means "leading zeroes" */
162#define PGTYPES_FMTDATE_DOW_LITERAL_SHORT 2
163#define PGTYPES_FMTDATE_MONTH_DIGITS_LZ 3
164#define PGTYPES_FMTDATE_MONTH_LITERAL_SHORT 4
165#define PGTYPES_FMTDATE_YEAR_DIGITS_SHORT 5
166#define PGTYPES_FMTDATE_YEAR_DIGITS_LONG 6
167
168int
169PGTYPESdate_fmt_asc(date dDate, const char *fmtstring, char *outbuf)
170{
171 static struct
172 {
173 char *format;
174 int component;
175 } mapping[] =
176 {
177 /*
178 * format items have to be sorted according to their length, since the
179 * first pattern that matches gets replaced by its value
180 */
181 {
182 "ddd", PGTYPES_FMTDATE_DOW_LITERAL_SHORT
183 },
184 {
185 "dd", PGTYPES_FMTDATE_DAY_DIGITS_LZ
186 },
187 {
188 "mmm", PGTYPES_FMTDATE_MONTH_LITERAL_SHORT
189 },
190 {
191 "mm", PGTYPES_FMTDATE_MONTH_DIGITS_LZ
192 },
193 {
194 "yyyy", PGTYPES_FMTDATE_YEAR_DIGITS_LONG
195 },
196 {
197 "yy", PGTYPES_FMTDATE_YEAR_DIGITS_SHORT
198 },
199 {
200 NULL, 0
201 }
202 };
203
204 union un_fmt_comb replace_val;
205 int replace_type;
206
207 int i;
208 int dow;
209 char *start_pattern;
210 struct tm tm;
211
212 /* copy the string over */
213 strcpy(outbuf, fmtstring);
214
215 /* get the date */
216 j2date(dDate + date2j(2000, 1, 1), &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
217 dow = PGTYPESdate_dayofweek(dDate);
218
219 for (i = 0; mapping[i].format != NULL; i++)
220 {
221 while ((start_pattern = strstr(outbuf, mapping[i].format)) != NULL)
222 {
223 switch (mapping[i].component)
224 {
225 case PGTYPES_FMTDATE_DOW_LITERAL_SHORT:
226 replace_val.str_val = pgtypes_date_weekdays_short[dow];
227 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
228 break;
229 case PGTYPES_FMTDATE_DAY_DIGITS_LZ:
230 replace_val.uint_val = tm.tm_mday;
231 replace_type = PGTYPES_TYPE_UINT_2_LZ;
232 break;
233 case PGTYPES_FMTDATE_MONTH_LITERAL_SHORT:
234 replace_val.str_val = months[tm.tm_mon - 1];
235 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
236 break;
237 case PGTYPES_FMTDATE_MONTH_DIGITS_LZ:
238 replace_val.uint_val = tm.tm_mon;
239 replace_type = PGTYPES_TYPE_UINT_2_LZ;
240 break;
241 case PGTYPES_FMTDATE_YEAR_DIGITS_LONG:
242 replace_val.uint_val = tm.tm_year;
243 replace_type = PGTYPES_TYPE_UINT_4_LZ;
244 break;
245 case PGTYPES_FMTDATE_YEAR_DIGITS_SHORT:
246 replace_val.uint_val = tm.tm_year % 100;
247 replace_type = PGTYPES_TYPE_UINT_2_LZ;
248 break;
249 default:
250
251 /*
252 * should not happen, set something anyway
253 */
254 replace_val.str_val = " ";
255 replace_type = PGTYPES_TYPE_STRING_CONSTANT;
256 }
257 switch (replace_type)
258 {
259 case PGTYPES_TYPE_STRING_MALLOCED:
260 case PGTYPES_TYPE_STRING_CONSTANT:
261 memcpy(start_pattern, replace_val.str_val,
262 strlen(replace_val.str_val));
263 if (replace_type == PGTYPES_TYPE_STRING_MALLOCED)
264 free(replace_val.str_val);
265 break;
266 case PGTYPES_TYPE_UINT:
267 {
268 char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
269
270 if (!t)
271 return -1;
272 snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
273 "%u", replace_val.uint_val);
274 memcpy(start_pattern, t, strlen(t));
275 free(t);
276 }
277 break;
278 case PGTYPES_TYPE_UINT_2_LZ:
279 {
280 char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
281
282 if (!t)
283 return -1;
284 snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
285 "%02u", replace_val.uint_val);
286 memcpy(start_pattern, t, strlen(t));
287 free(t);
288 }
289 break;
290 case PGTYPES_TYPE_UINT_4_LZ:
291 {
292 char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
293
294 if (!t)
295 return -1;
296 snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
297 "%04u", replace_val.uint_val);
298 memcpy(start_pattern, t, strlen(t));
299 free(t);
300 }
301 break;
302 default:
303
304 /*
305 * doesn't happen (we set replace_type to
306 * PGTYPES_TYPE_STRING_CONSTANT in case of an error above)
307 */
308 break;
309 }
310 }
311 }
312 return 0;
313}
314
315
316/*
317 * PGTYPESdate_defmt_asc
318 *
319 * function works as follows:
320 * - first we analyze the parameters
321 * - if this is a special case with no delimiters, add delimiters
322 * - find the tokens. First we look for numerical values. If we have found
323 * less than 3 tokens, we check for the months' names and thereafter for
324 * the abbreviations of the months' names.
325 * - then we see which parameter should be the date, the month and the
326 * year and from these values we calculate the date
327 */
328
329#define PGTYPES_DATE_MONTH_MAXLENGTH 20 /* probably even less :-) */
330int
331PGTYPESdate_defmt_asc(date * d, const char *fmt, const char *str)
332{
333 /*
334 * token[2] = { 4,6 } means that token 2 starts at position 4 and ends at
335 * (including) position 6
336 */
337 int token[3][2];
338 int token_values[3] = {-1, -1, -1};
339 char *fmt_token_order;
340 char *fmt_ystart,
341 *fmt_mstart,
342 *fmt_dstart;
343 unsigned int i;
344 int reading_digit;
345 int token_count;
346 char *str_copy;
347 struct tm tm;
348
349 tm.tm_year = tm.tm_mon = tm.tm_mday = 0; /* keep compiler quiet */
350
351 if (!d || !str || !fmt)
352 {
353 errno = PGTYPES_DATE_ERR_EARGS;
354 return -1;
355 }
356
357 /* analyze the fmt string */
358 fmt_ystart = strstr(fmt, "yy");
359 fmt_mstart = strstr(fmt, "mm");
360 fmt_dstart = strstr(fmt, "dd");
361
362 if (!fmt_ystart || !fmt_mstart || !fmt_dstart)
363 {
364 errno = PGTYPES_DATE_ERR_EARGS;
365 return -1;
366 }
367
368 if (fmt_ystart < fmt_mstart)
369 {
370 /* y m */
371 if (fmt_dstart < fmt_ystart)
372 {
373 /* d y m */
374 fmt_token_order = "dym";
375 }
376 else if (fmt_dstart > fmt_mstart)
377 {
378 /* y m d */
379 fmt_token_order = "ymd";
380 }
381 else
382 {
383 /* y d m */
384 fmt_token_order = "ydm";
385 }
386 }
387 else
388 {
389 /* fmt_ystart > fmt_mstart */
390 /* m y */
391 if (fmt_dstart < fmt_mstart)
392 {
393 /* d m y */
394 fmt_token_order = "dmy";
395 }
396 else if (fmt_dstart > fmt_ystart)
397 {
398 /* m y d */
399 fmt_token_order = "myd";
400 }
401 else
402 {
403 /* m d y */
404 fmt_token_order = "mdy";
405 }
406 }
407
408 /*
409 * handle the special cases where there is no delimiter between the
410 * digits. If we see this:
411 *
412 * only digits, 6 or 8 bytes then it might be ddmmyy and ddmmyyyy (or
413 * similar)
414 *
415 * we reduce it to a string with delimiters and continue processing
416 */
417
418 /* check if we have only digits */
419 reading_digit = 1;
420 for (i = 0; str[i]; i++)
421 {
422 if (!isdigit((unsigned char) str[i]))
423 {
424 reading_digit = 0;
425 break;
426 }
427 }
428 if (reading_digit)
429 {
430 int frag_length[3];
431 int target_pos;
432
433 i = strlen(str);
434 if (i != 8 && i != 6)
435 {
436 errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
437 return -1;
438 }
439 /* okay, this really is the special case */
440
441 /*
442 * as long as the string, one additional byte for the terminator and 2
443 * for the delimiters between the 3 fields
444 */
445 str_copy = pgtypes_alloc(strlen(str) + 1 + 2);
446 if (!str_copy)
447 return -1;
448
449 /* determine length of the fragments */
450 if (i == 6)
451 {
452 frag_length[0] = 2;
453 frag_length[1] = 2;
454 frag_length[2] = 2;
455 }
456 else
457 {
458 if (fmt_token_order[0] == 'y')
459 {
460 frag_length[0] = 4;
461 frag_length[1] = 2;
462 frag_length[2] = 2;
463 }
464 else if (fmt_token_order[1] == 'y')
465 {
466 frag_length[0] = 2;
467 frag_length[1] = 4;
468 frag_length[2] = 2;
469 }
470 else
471 {
472 frag_length[0] = 2;
473 frag_length[1] = 2;
474 frag_length[2] = 4;
475 }
476 }
477 target_pos = 0;
478
479 /*
480 * XXX: Here we could calculate the positions of the tokens and save
481 * the for loop down there where we again check with isdigit() for
482 * digits.
483 */
484 for (i = 0; i < 3; i++)
485 {
486 int start_pos = 0;
487
488 if (i >= 1)
489 start_pos += frag_length[0];
490 if (i == 2)
491 start_pos += frag_length[1];
492
493 strncpy(str_copy + target_pos, str + start_pos,
494 frag_length[i]);
495 target_pos += frag_length[i];
496 if (i != 2)
497 {
498 str_copy[target_pos] = ' ';
499 target_pos++;
500 }
501 }
502 str_copy[target_pos] = '\0';
503 }
504 else
505 {
506 str_copy = pgtypes_strdup(str);
507 if (!str_copy)
508 return -1;
509
510 /* convert the whole string to lower case */
511 for (i = 0; str_copy[i]; i++)
512 str_copy[i] = (char) pg_tolower((unsigned char) str_copy[i]);
513 }
514
515 /* look for numerical tokens */
516 reading_digit = 0;
517 token_count = 0;
518 for (i = 0; i < strlen(str_copy); i++)
519 {
520 if (!isdigit((unsigned char) str_copy[i]) && reading_digit)
521 {
522 /* the token is finished */
523 token[token_count][1] = i - 1;
524 reading_digit = 0;
525 token_count++;
526 }
527 else if (isdigit((unsigned char) str_copy[i]) && !reading_digit)
528 {
529 /* we have found a token */
530 token[token_count][0] = i;
531 reading_digit = 1;
532 }
533 }
534
535 /*
536 * we're at the end of the input string, but maybe we are still reading a
537 * number...
538 */
539 if (reading_digit)
540 {
541 token[token_count][1] = i - 1;
542 token_count++;
543 }
544
545
546 if (token_count < 2)
547 {
548 /*
549 * not all tokens found, no way to find 2 missing tokens with string
550 * matches
551 */
552 free(str_copy);
553 errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
554 return -1;
555 }
556
557 if (token_count != 3)
558 {
559 /*
560 * not all tokens found but we may find another one with string
561 * matches by testing for the months names and months abbreviations
562 */
563 char *month_lower_tmp = pgtypes_alloc(PGTYPES_DATE_MONTH_MAXLENGTH);
564 char *start_pos;
565 int j;
566 int offset;
567 int found = 0;
568 char **list;
569
570 if (!month_lower_tmp)
571 {
572 /* free variables we alloc'ed before */
573 free(str_copy);
574 return -1;
575 }
576 list = pgtypes_date_months;
577 for (i = 0; list[i]; i++)
578 {
579 for (j = 0; j < PGTYPES_DATE_MONTH_MAXLENGTH; j++)
580 {
581 month_lower_tmp[j] = (char) pg_tolower((unsigned char) list[i][j]);
582 if (!month_lower_tmp[j])
583 {
584 /* properly terminated */
585 break;
586 }
587 }
588 if ((start_pos = strstr(str_copy, month_lower_tmp)))
589 {
590 offset = start_pos - str_copy;
591
592 /*
593 * sort the new token into the numeric tokens, shift them if
594 * necessary
595 */
596 if (offset < token[0][0])
597 {
598 token[2][0] = token[1][0];
599 token[2][1] = token[1][1];
600 token[1][0] = token[0][0];
601 token[1][1] = token[0][1];
602 token_count = 0;
603 }
604 else if (offset < token[1][0])
605 {
606 token[2][0] = token[1][0];
607 token[2][1] = token[1][1];
608 token_count = 1;
609 }
610 else
611 token_count = 2;
612 token[token_count][0] = offset;
613 token[token_count][1] = offset + strlen(month_lower_tmp) - 1;
614
615 /*
616 * the value is the index of the month in the array of months
617 * + 1 (January is month 0)
618 */
619 token_values[token_count] = i + 1;
620 found = 1;
621 break;
622 }
623
624 /*
625 * evil[tm] hack: if we read the pgtypes_date_months and haven't
626 * found a match, reset list to point to pgtypes_date_months_short
627 * and reset the counter variable i
628 */
629 if (list == pgtypes_date_months)
630 {
631 if (list[i + 1] == NULL)
632 {
633 list = months;
634 i = -1;
635 }
636 }
637 }
638 if (!found)
639 {
640 free(month_lower_tmp);
641 free(str_copy);
642 errno = PGTYPES_DATE_ERR_ENOTDMY;
643 return -1;
644 }
645
646 /*
647 * here we found a month. token[token_count] and
648 * token_values[token_count] reflect the month's details.
649 *
650 * only the month can be specified with a literal. Here we can do a
651 * quick check if the month is at the right position according to the
652 * format string because we can check if the token that we expect to
653 * be the month is at the position of the only token that already has
654 * a value. If we wouldn't check here we could say "December 4 1990"
655 * with a fmt string of "dd mm yy" for 12 April 1990.
656 */
657 if (fmt_token_order[token_count] != 'm')
658 {
659 /* deal with the error later on */
660 token_values[token_count] = -1;
661 }
662 free(month_lower_tmp);
663 }
664
665 /* terminate the tokens with ASCII-0 and get their values */
666 for (i = 0; i < 3; i++)
667 {
668 *(str_copy + token[i][1] + 1) = '\0';
669 /* A month already has a value set, check for token_value == -1 */
670 if (token_values[i] == -1)
671 {
672 errno = 0;
673 token_values[i] = strtol(str_copy + token[i][0], (char **) NULL, 10);
674 /* strtol sets errno in case of an error */
675 if (errno)
676 token_values[i] = -1;
677 }
678 if (fmt_token_order[i] == 'd')
679 tm.tm_mday = token_values[i];
680 else if (fmt_token_order[i] == 'm')
681 tm.tm_mon = token_values[i];
682 else if (fmt_token_order[i] == 'y')
683 tm.tm_year = token_values[i];
684 }
685 free(str_copy);
686
687 if (tm.tm_mday < 1 || tm.tm_mday > 31)
688 {
689 errno = PGTYPES_DATE_BAD_DAY;
690 return -1;
691 }
692
693 if (tm.tm_mon < 1 || tm.tm_mon > MONTHS_PER_YEAR)
694 {
695 errno = PGTYPES_DATE_BAD_MONTH;
696 return -1;
697 }
698
699 if (tm.tm_mday == 31 && (tm.tm_mon == 4 || tm.tm_mon == 6 || tm.tm_mon == 9 || tm.tm_mon == 11))
700 {
701 errno = PGTYPES_DATE_BAD_DAY;
702 return -1;
703 }
704
705 if (tm.tm_mon == 2 && tm.tm_mday > 29)
706 {
707 errno = PGTYPES_DATE_BAD_DAY;
708 return -1;
709 }
710
711 *d = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - date2j(2000, 1, 1);
712
713 return 0;
714}
715