Line data Source code
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 0 : PGTYPESdate_new(void)
16 : {
17 0 : date *result;
18 :
19 0 : result = (date *) pgtypes_alloc(sizeof(date));
20 : /* result can be NULL if we run out of memory */
21 0 : return result;
22 0 : }
23 :
24 : void
25 0 : PGTYPESdate_free(date * d)
26 : {
27 0 : free(d);
28 0 : }
29 :
30 : date
31 0 : PGTYPESdate_from_timestamp(timestamp dt)
32 : {
33 0 : date dDate;
34 :
35 0 : dDate = 0; /* suppress compiler warning */
36 :
37 0 : if (!TIMESTAMP_NOT_FINITE(dt))
38 : {
39 : /* Microseconds to days */
40 0 : dDate = (dt / USECS_PER_DAY);
41 0 : }
42 :
43 0 : return dDate;
44 0 : }
45 :
46 : date
47 0 : PGTYPESdate_from_asc(char *str, char **endptr)
48 : {
49 0 : date dDate;
50 0 : fsec_t fsec;
51 0 : struct tm tt,
52 0 : *tm = &tt;
53 0 : int dtype;
54 0 : int nf;
55 0 : char *field[MAXDATEFIELDS];
56 0 : int ftype[MAXDATEFIELDS];
57 0 : char lowstr[MAXDATELEN + MAXDATEFIELDS];
58 0 : char *realptr;
59 0 : char **ptr = (endptr != NULL) ? endptr : &realptr;
60 :
61 0 : bool EuroDates = false;
62 :
63 0 : errno = 0;
64 0 : if (strlen(str) > MAXDATELEN)
65 : {
66 0 : errno = PGTYPES_DATE_BAD_DATE;
67 0 : return INT_MIN;
68 : }
69 :
70 0 : if (ParseDateTime(str, lowstr, field, ftype, &nf, ptr) != 0 ||
71 0 : DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, EuroDates) != 0)
72 : {
73 0 : errno = PGTYPES_DATE_BAD_DATE;
74 0 : return INT_MIN;
75 : }
76 :
77 0 : switch (dtype)
78 : {
79 : case DTK_DATE:
80 : break;
81 :
82 : case DTK_EPOCH:
83 0 : if (GetEpochTime(tm) < 0)
84 : {
85 0 : errno = PGTYPES_DATE_BAD_DATE;
86 0 : return INT_MIN;
87 : }
88 0 : break;
89 :
90 : default:
91 0 : errno = PGTYPES_DATE_BAD_DATE;
92 0 : return INT_MIN;
93 : }
94 :
95 0 : dDate = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1));
96 :
97 0 : return dDate;
98 0 : }
99 :
100 : char *
101 0 : PGTYPESdate_to_asc(date dDate)
102 : {
103 0 : struct tm tt,
104 0 : *tm = &tt;
105 0 : char buf[MAXDATELEN + 1];
106 0 : int DateStyle = 1;
107 0 : bool EuroDates = false;
108 :
109 0 : j2date(dDate + date2j(2000, 1, 1), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
110 0 : EncodeDateOnly(tm, DateStyle, buf, EuroDates);
111 0 : return pgtypes_strdup(buf);
112 0 : }
113 :
114 : void
115 0 : PGTYPESdate_julmdy(date jd, int *mdy)
116 : {
117 0 : int y,
118 : m,
119 : d;
120 :
121 0 : j2date((int) (jd + date2j(2000, 1, 1)), &y, &m, &d);
122 0 : mdy[0] = m;
123 0 : mdy[1] = d;
124 0 : mdy[2] = y;
125 0 : }
126 :
127 : void
128 0 : PGTYPESdate_mdyjul(int *mdy, date * jdate)
129 : {
130 : /* month is mdy[0] */
131 : /* day is mdy[1] */
132 : /* year is mdy[2] */
133 :
134 0 : *jdate = (date) (date2j(mdy[2], mdy[0], mdy[1]) - date2j(2000, 1, 1));
135 0 : }
136 :
137 : int
138 0 : PGTYPESdate_dayofweek(date dDate)
139 : {
140 : /*
141 : * Sunday: 0 Monday: 1 Tuesday: 2 Wednesday: 3 Thursday: 4
142 : * Friday: 5 Saturday: 6
143 : */
144 0 : return (int) (dDate + date2j(2000, 1, 1) + 1) % 7;
145 : }
146 :
147 : void
148 0 : PGTYPESdate_today(date * d)
149 : {
150 0 : struct tm ts;
151 :
152 0 : GetCurrentDateTime(&ts);
153 0 : if (errno == 0)
154 0 : *d = date2j(ts.tm_year, ts.tm_mon, ts.tm_mday) - date2j(2000, 1, 1);
155 0 : }
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 0 : 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 0 : union un_fmt_comb replace_val;
204 0 : int replace_type;
205 :
206 0 : int i;
207 0 : int dow;
208 0 : char *start_pattern;
209 0 : struct tm tm;
210 :
211 : /* copy the string over */
212 0 : strcpy(outbuf, fmtstring);
213 :
214 : /* get the date */
215 0 : j2date(dDate + date2j(2000, 1, 1), &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
216 0 : dow = PGTYPESdate_dayofweek(dDate);
217 :
218 0 : for (i = 0; mapping[i].format != NULL; i++)
219 : {
220 0 : while ((start_pattern = strstr(outbuf, mapping[i].format)) != NULL)
221 : {
222 0 : switch (mapping[i].component)
223 : {
224 : case PGTYPES_FMTDATE_DOW_LITERAL_SHORT:
225 0 : replace_val.str_val = pgtypes_date_weekdays_short[dow];
226 0 : replace_type = PGTYPES_TYPE_STRING_CONSTANT;
227 0 : break;
228 : case PGTYPES_FMTDATE_DAY_DIGITS_LZ:
229 0 : replace_val.uint_val = tm.tm_mday;
230 0 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
231 0 : break;
232 : case PGTYPES_FMTDATE_MONTH_LITERAL_SHORT:
233 0 : replace_val.str_val = months[tm.tm_mon - 1];
234 0 : replace_type = PGTYPES_TYPE_STRING_CONSTANT;
235 0 : break;
236 : case PGTYPES_FMTDATE_MONTH_DIGITS_LZ:
237 0 : replace_val.uint_val = tm.tm_mon;
238 0 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
239 0 : break;
240 : case PGTYPES_FMTDATE_YEAR_DIGITS_LONG:
241 0 : replace_val.uint_val = tm.tm_year;
242 0 : replace_type = PGTYPES_TYPE_UINT_4_LZ;
243 0 : break;
244 : case PGTYPES_FMTDATE_YEAR_DIGITS_SHORT:
245 0 : replace_val.uint_val = tm.tm_year % 100;
246 0 : replace_type = PGTYPES_TYPE_UINT_2_LZ;
247 0 : break;
248 : default:
249 :
250 : /*
251 : * should not happen, set something anyway
252 : */
253 0 : replace_val.str_val = " ";
254 0 : replace_type = PGTYPES_TYPE_STRING_CONSTANT;
255 0 : }
256 0 : switch (replace_type)
257 : {
258 : case PGTYPES_TYPE_STRING_MALLOCED:
259 : case PGTYPES_TYPE_STRING_CONSTANT:
260 0 : memcpy(start_pattern, replace_val.str_val,
261 : strlen(replace_val.str_val));
262 0 : if (replace_type == PGTYPES_TYPE_STRING_MALLOCED)
263 0 : free(replace_val.str_val);
264 0 : break;
265 : case PGTYPES_TYPE_UINT:
266 : {
267 0 : char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
268 :
269 0 : if (!t)
270 0 : return -1;
271 0 : snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
272 0 : "%u", replace_val.uint_val);
273 0 : memcpy(start_pattern, t, strlen(t));
274 0 : free(t);
275 0 : }
276 0 : break;
277 : case PGTYPES_TYPE_UINT_2_LZ:
278 : {
279 0 : char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
280 :
281 0 : if (!t)
282 0 : return -1;
283 0 : snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
284 0 : "%02u", replace_val.uint_val);
285 0 : memcpy(start_pattern, t, strlen(t));
286 0 : free(t);
287 0 : }
288 0 : break;
289 : case PGTYPES_TYPE_UINT_4_LZ:
290 : {
291 0 : char *t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
292 :
293 0 : if (!t)
294 0 : return -1;
295 0 : snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
296 0 : "%04u", replace_val.uint_val);
297 0 : memcpy(start_pattern, t, strlen(t));
298 0 : free(t);
299 0 : }
300 0 : 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 0 : break;
308 : }
309 : }
310 0 : }
311 0 : return 0;
312 0 : }
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 0 : 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 0 : int token[3][2];
337 0 : int token_values[3] = {-1, -1, -1};
338 0 : const char *fmt_token_order;
339 0 : const char *fmt_ystart,
340 : *fmt_mstart,
341 : *fmt_dstart;
342 0 : unsigned int i;
343 0 : int reading_digit;
344 0 : int token_count;
345 0 : char *str_copy;
346 0 : struct tm tm;
347 :
348 0 : tm.tm_year = tm.tm_mon = tm.tm_mday = 0; /* keep compiler quiet */
349 :
350 0 : if (!d || !str || !fmt)
351 : {
352 0 : errno = PGTYPES_DATE_ERR_EARGS;
353 0 : return -1;
354 : }
355 :
356 : /* analyze the fmt string */
357 0 : fmt_ystart = strstr(fmt, "yy");
358 0 : fmt_mstart = strstr(fmt, "mm");
359 0 : fmt_dstart = strstr(fmt, "dd");
360 :
361 0 : if (!fmt_ystart || !fmt_mstart || !fmt_dstart)
362 : {
363 0 : errno = PGTYPES_DATE_ERR_EARGS;
364 0 : return -1;
365 : }
366 :
367 0 : if (fmt_ystart < fmt_mstart)
368 : {
369 : /* y m */
370 0 : if (fmt_dstart < fmt_ystart)
371 : {
372 : /* d y m */
373 0 : fmt_token_order = "dym";
374 0 : }
375 0 : else if (fmt_dstart > fmt_mstart)
376 : {
377 : /* y m d */
378 0 : fmt_token_order = "ymd";
379 0 : }
380 : else
381 : {
382 : /* y d m */
383 0 : fmt_token_order = "ydm";
384 : }
385 0 : }
386 : else
387 : {
388 : /* fmt_ystart > fmt_mstart */
389 : /* m y */
390 0 : if (fmt_dstart < fmt_mstart)
391 : {
392 : /* d m y */
393 0 : fmt_token_order = "dmy";
394 0 : }
395 0 : else if (fmt_dstart > fmt_ystart)
396 : {
397 : /* m y d */
398 0 : fmt_token_order = "myd";
399 0 : }
400 : else
401 : {
402 : /* m d y */
403 0 : 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 0 : reading_digit = 1;
419 0 : for (i = 0; str[i]; i++)
420 : {
421 0 : if (!isdigit((unsigned char) str[i]))
422 : {
423 0 : reading_digit = 0;
424 0 : break;
425 : }
426 0 : }
427 0 : if (reading_digit)
428 : {
429 0 : int frag_length[3];
430 0 : int target_pos;
431 :
432 0 : i = strlen(str);
433 0 : if (i != 8 && i != 6)
434 : {
435 0 : errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
436 0 : 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 0 : str_copy = pgtypes_alloc(strlen(str) + 1 + 2);
445 0 : if (!str_copy)
446 0 : return -1;
447 :
448 : /* determine length of the fragments */
449 0 : if (i == 6)
450 : {
451 0 : frag_length[0] = 2;
452 0 : frag_length[1] = 2;
453 0 : frag_length[2] = 2;
454 0 : }
455 : else
456 : {
457 0 : if (fmt_token_order[0] == 'y')
458 : {
459 0 : frag_length[0] = 4;
460 0 : frag_length[1] = 2;
461 0 : frag_length[2] = 2;
462 0 : }
463 0 : else if (fmt_token_order[1] == 'y')
464 : {
465 0 : frag_length[0] = 2;
466 0 : frag_length[1] = 4;
467 0 : frag_length[2] = 2;
468 0 : }
469 : else
470 : {
471 0 : frag_length[0] = 2;
472 0 : frag_length[1] = 2;
473 0 : frag_length[2] = 4;
474 : }
475 : }
476 0 : 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 0 : for (i = 0; i < 3; i++)
484 : {
485 0 : int start_pos = 0;
486 :
487 0 : if (i >= 1)
488 0 : start_pos += frag_length[0];
489 0 : if (i == 2)
490 0 : start_pos += frag_length[1];
491 :
492 0 : strncpy(str_copy + target_pos, str + start_pos,
493 : frag_length[i]);
494 0 : target_pos += frag_length[i];
495 0 : if (i != 2)
496 : {
497 0 : str_copy[target_pos] = ' ';
498 0 : target_pos++;
499 0 : }
500 0 : }
501 0 : str_copy[target_pos] = '\0';
502 0 : }
503 : else
504 : {
505 0 : str_copy = pgtypes_strdup(str);
506 0 : if (!str_copy)
507 0 : return -1;
508 :
509 : /* convert the whole string to lower case */
510 0 : for (i = 0; str_copy[i]; i++)
511 0 : str_copy[i] = (char) pg_tolower((unsigned char) str_copy[i]);
512 : }
513 :
514 : /* look for numerical tokens */
515 0 : reading_digit = 0;
516 0 : token_count = 0;
517 0 : for (i = 0; i < strlen(str_copy); i++)
518 : {
519 0 : if (!isdigit((unsigned char) str_copy[i]) && reading_digit)
520 : {
521 : /* the token is finished */
522 0 : token[token_count][1] = i - 1;
523 0 : reading_digit = 0;
524 0 : token_count++;
525 0 : }
526 0 : else if (isdigit((unsigned char) str_copy[i]) && !reading_digit)
527 : {
528 : /* we have found a token */
529 0 : token[token_count][0] = i;
530 0 : reading_digit = 1;
531 0 : }
532 0 : }
533 :
534 : /*
535 : * we're at the end of the input string, but maybe we are still reading a
536 : * number...
537 : */
538 0 : if (reading_digit)
539 : {
540 0 : token[token_count][1] = i - 1;
541 0 : token_count++;
542 0 : }
543 :
544 :
545 0 : if (token_count < 2)
546 : {
547 : /*
548 : * not all tokens found, no way to find 2 missing tokens with string
549 : * matches
550 : */
551 0 : free(str_copy);
552 0 : errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
553 0 : return -1;
554 : }
555 :
556 0 : 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 0 : char *month_lower_tmp = pgtypes_alloc(PGTYPES_DATE_MONTH_MAXLENGTH);
563 0 : char *start_pos;
564 0 : int j;
565 0 : int offset;
566 0 : int found = 0;
567 0 : char **list;
568 :
569 0 : if (!month_lower_tmp)
570 : {
571 : /* free variables we alloc'ed before */
572 0 : free(str_copy);
573 0 : return -1;
574 : }
575 0 : list = pgtypes_date_months;
576 0 : for (i = 0; list[i]; i++)
577 : {
578 0 : for (j = 0; j < PGTYPES_DATE_MONTH_MAXLENGTH; j++)
579 : {
580 0 : month_lower_tmp[j] = (char) pg_tolower((unsigned char) list[i][j]);
581 0 : if (!month_lower_tmp[j])
582 : {
583 : /* properly terminated */
584 0 : break;
585 : }
586 0 : }
587 0 : if ((start_pos = strstr(str_copy, month_lower_tmp)))
588 : {
589 0 : offset = start_pos - str_copy;
590 :
591 : /*
592 : * sort the new token into the numeric tokens, shift them if
593 : * necessary
594 : */
595 0 : if (offset < token[0][0])
596 : {
597 0 : token[2][0] = token[1][0];
598 0 : token[2][1] = token[1][1];
599 0 : token[1][0] = token[0][0];
600 0 : token[1][1] = token[0][1];
601 0 : token_count = 0;
602 0 : }
603 0 : else if (offset < token[1][0])
604 : {
605 0 : token[2][0] = token[1][0];
606 0 : token[2][1] = token[1][1];
607 0 : token_count = 1;
608 0 : }
609 : else
610 0 : token_count = 2;
611 0 : token[token_count][0] = offset;
612 0 : 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 0 : token_values[token_count] = i + 1;
619 0 : found = 1;
620 0 : 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 0 : if (list == pgtypes_date_months)
629 : {
630 0 : if (list[i + 1] == NULL)
631 : {
632 0 : list = months;
633 0 : i = -1;
634 0 : }
635 0 : }
636 0 : }
637 0 : if (!found)
638 : {
639 0 : free(month_lower_tmp);
640 0 : free(str_copy);
641 0 : errno = PGTYPES_DATE_ERR_ENOTDMY;
642 0 : 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 0 : if (fmt_token_order[token_count] != 'm')
657 : {
658 : /* deal with the error later on */
659 0 : token_values[token_count] = -1;
660 0 : }
661 0 : free(month_lower_tmp);
662 0 : }
663 :
664 : /* terminate the tokens with ASCII-0 and get their values */
665 0 : for (i = 0; i < 3; i++)
666 : {
667 0 : *(str_copy + token[i][1] + 1) = '\0';
668 : /* A month already has a value set, check for token_value == -1 */
669 0 : if (token_values[i] == -1)
670 : {
671 0 : errno = 0;
672 0 : token_values[i] = strtol(str_copy + token[i][0], (char **) NULL, 10);
673 : /* strtol sets errno in case of an error */
674 0 : if (errno)
675 0 : token_values[i] = -1;
676 0 : }
677 0 : if (fmt_token_order[i] == 'd')
678 0 : tm.tm_mday = token_values[i];
679 0 : else if (fmt_token_order[i] == 'm')
680 0 : tm.tm_mon = token_values[i];
681 0 : else if (fmt_token_order[i] == 'y')
682 0 : tm.tm_year = token_values[i];
683 0 : }
684 0 : free(str_copy);
685 :
686 0 : if (tm.tm_mday < 1 || tm.tm_mday > 31)
687 : {
688 0 : errno = PGTYPES_DATE_BAD_DAY;
689 0 : return -1;
690 : }
691 :
692 0 : if (tm.tm_mon < 1 || tm.tm_mon > MONTHS_PER_YEAR)
693 : {
694 0 : errno = PGTYPES_DATE_BAD_MONTH;
695 0 : return -1;
696 : }
697 :
698 0 : if (tm.tm_mday == 31 && (tm.tm_mon == 4 || tm.tm_mon == 6 || tm.tm_mon == 9 || tm.tm_mon == 11))
699 : {
700 0 : errno = PGTYPES_DATE_BAD_DAY;
701 0 : return -1;
702 : }
703 :
704 0 : if (tm.tm_mon == 2 && tm.tm_mday > 29)
705 : {
706 0 : errno = PGTYPES_DATE_BAD_DAY;
707 0 : return -1;
708 : }
709 :
710 0 : *d = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - date2j(2000, 1, 1);
711 :
712 0 : return 0;
713 0 : }
|