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