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