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