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