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