PGTYPESdate_defmt_asc(date * d, const char *fmt, char *str)
{
	/*
	 * token[2] = { 4,6 } means that token 2 starts at position 4 and ends at
	 * (including) position 6
	 */
	int			token[3][2];
	int			token_values[3] = {-1, -1, -1};
	char	   *fmt_token_order;
	char	   *fmt_ystart,
			   *fmt_mstart,
			   *fmt_dstart;
	unsigned int i;
	int			reading_digit;
	int			token_count;
	char	   *str_copy;
	struct tm	tm;

	tm.tm_year = tm.tm_mon = tm.tm_mday = 0;	/* keep compiler quiet */

	if (!d || !str || !fmt)
	{
		errno = PGTYPES_DATE_ERR_EARGS;
		return -1;
	}

	/* analyze the fmt string */
	fmt_ystart = strstr(fmt, "yy");
	fmt_mstart = strstr(fmt, "mm");
	fmt_dstart = strstr(fmt, "dd");

	if (!fmt_ystart || !fmt_mstart || !fmt_dstart)
	{
		errno = PGTYPES_DATE_ERR_EARGS;
		return -1;
	}

	if (fmt_ystart < fmt_mstart)
	{
		/* y m */
		if (fmt_dstart < fmt_ystart)
		{
			/* d y m */
			fmt_token_order = "dym";
		}
		else if (fmt_dstart > fmt_mstart)
		{
			/* y m d */
			fmt_token_order = "ymd";
		}
		else
		{
			/* y d m */
			fmt_token_order = "ydm";
		}
	}
	else
	{
		/* fmt_ystart > fmt_mstart */
		/* m y */
		if (fmt_dstart < fmt_mstart)
		{
			/* d m y */
			fmt_token_order = "dmy";
		}
		else if (fmt_dstart > fmt_ystart)
		{
			/* m y d */
			fmt_token_order = "myd";
		}
		else
		{
			/* m d y */
			fmt_token_order = "mdy";
		}
	}

	/*
	 * handle the special cases where there is no delimiter between the
	 * digits. If we see this:
	 *
	 * only digits, 6 or 8 bytes then it might be ddmmyy and ddmmyyyy (or
	 * similar)
	 *
	 * we reduce it to a string with delimiters and continue processing
	 */

	/* check if we have only digits */
	reading_digit = 1;
	for (i = 0; str[i]; i++)
	{
		if (!isdigit((unsigned char) str[i]))
		{
			reading_digit = 0;
			break;
		}
	}
	if (reading_digit)
	{
		int			frag_length[3];
		int			target_pos;

		i = strlen(str);
		if (i != 8 && i != 6)
		{
			errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
			return -1;
		}
		/* okay, this really is the special case */

		/*
		 * as long as the string, one additional byte for the terminator and 2
		 * for the delimiters between the 3 fiedls
		 */
		str_copy = pgtypes_alloc(strlen(str) + 1 + 2);
		if (!str_copy)
			return -1;

		/* determine length of the fragments */
		if (i == 6)
		{
			frag_length[0] = 2;
			frag_length[1] = 2;
			frag_length[2] = 2;
		}
		else
		{
			if (fmt_token_order[0] == 'y')
			{
				frag_length[0] = 4;
				frag_length[1] = 2;
				frag_length[2] = 2;
			}
			else if (fmt_token_order[1] == 'y')
			{
				frag_length[0] = 2;
				frag_length[1] = 4;
				frag_length[2] = 2;
			}
			else
			{
				frag_length[0] = 2;
				frag_length[1] = 2;
				frag_length[2] = 4;
			}
		}
		target_pos = 0;

		/*
		 * XXX: Here we could calculate the positions of the tokens and save
		 * the for loop down there where we again check with isdigit() for
		 * digits.
		 */
		for (i = 0; i < 3; i++)
		{
			int			start_pos = 0;

			if (i >= 1)
				start_pos += frag_length[0];
			if (i == 2)
				start_pos += frag_length[1];

			strncpy(str_copy + target_pos, str + start_pos,
					frag_length[i]);
			target_pos += frag_length[i];
			if (i != 2)
			{
				str_copy[target_pos] = ' ';
				target_pos++;
			}
		}
		str_copy[target_pos] = '\0';
	}
	else
	{
		str_copy = pgtypes_strdup(str);
		if (!str_copy)
			return -1;

		/* convert the whole string to lower case */
		for (i = 0; str_copy[i]; i++)
			str_copy[i] = (char) pg_tolower((unsigned char) str_copy[i]);
	}

	/* look for numerical tokens */
	reading_digit = 0;
	token_count = 0;
	for (i = 0; i < strlen(str_copy); i++)
	{
		if (!isdigit((unsigned char) str_copy[i]) && reading_digit)
		{
			/* the token is finished */
			token[token_count][1] = i - 1;
			reading_digit = 0;
			token_count++;
		}
		else if (isdigit((unsigned char) str_copy[i]) && !reading_digit)
		{
			/* we have found a token */
			token[token_count][0] = i;
			reading_digit = 1;
		}
	}

	/*
	 * we're at the end of the input string, but maybe we are still reading a
	 * number...
	 */
	if (reading_digit)
	{
		token[token_count][1] = i - 1;
		token_count++;
	}


	if (token_count < 2)
	{
		/*
		 * not all tokens found, no way to find 2 missing tokens with string
		 * matches
		 */
		free(str_copy);
		errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
		return -1;
	}

	if (token_count != 3)
	{
		/*
		 * not all tokens found but we may find another one with string
		 * matches by testing for the months names and months abbreviations
		 */
		char	   *month_lower_tmp = pgtypes_alloc(PGTYPES_DATE_MONTH_MAXLENGTH);
		char	   *start_pos;
		int			j;
		int			offset;
		int			found = 0;
		char	  **list;

		if (!month_lower_tmp)
		{
			/* free variables we alloc'ed before */
			free(str_copy);
			return -1;
		}
		list = pgtypes_date_months;
		for (i = 0; list[i]; i++)
		{
			for (j = 0; j < PGTYPES_DATE_MONTH_MAXLENGTH; j++)
			{
				month_lower_tmp[j] = (char) pg_tolower((unsigned char) list[i][j]);
				if (!month_lower_tmp[j])
				{
					/* properly terminated */
					break;
				}
			}
			if ((start_pos = strstr(str_copy, month_lower_tmp)))
			{
				offset = start_pos - str_copy;

				/*
				 * sort the new token into the numeric tokens, shift them if
				 * necessary
				 */
				if (offset < token[0][0])
				{
					token[2][0] = token[1][0];
					token[2][1] = token[1][1];
					token[1][0] = token[0][0];
					token[1][1] = token[0][1];
					token_count = 0;
				}
				else if (offset < token[1][0])
				{
					token[2][0] = token[1][0];
					token[2][1] = token[1][1];
					token_count = 1;
				}
				else
					token_count = 2;
				token[token_count][0] = offset;
				token[token_count][1] = offset + strlen(month_lower_tmp) - 1;

				/*
				 * the value is the index of the month in the array of months
				 * + 1 (January is month 0)
				 */
				token_values[token_count] = i + 1;
				found = 1;
				break;
			}

			/*
			 * evil[tm] hack: if we read the pgtypes_date_months and haven't
			 * found a match, reset list to point to pgtypes_date_months_short
			 * and reset the counter variable i
			 */
			if (list == pgtypes_date_months)
			{
				if (list[i + 1] == NULL)
				{
					list = months;
					i = -1;
				}
			}
		}
		if (!found)
		{
			free(month_lower_tmp);
			free(str_copy);
			errno = PGTYPES_DATE_ERR_ENOTDMY;
			return -1;
		}

		/*
		 * here we found a month. token[token_count] and
		 * token_values[token_count] reflect the month's details.
		 *
		 * only the month can be specified with a literal. Here we can do a
		 * quick check if the month is at the right position according to the
		 * format string because we can check if the token that we expect to
		 * be the month is at the position of the only token that already has
		 * a value. If we wouldn't check here we could say "December 4 1990"
		 * with a fmt string of "dd mm yy" for 12 April 1990.
		 */
		if (fmt_token_order[token_count] != 'm')
		{
			/* deal with the error later on */
			token_values[token_count] = -1;
		}
		free(month_lower_tmp);
	}

	/* terminate the tokens with ASCII-0 and get their values */
	for (i = 0; i < 3; i++)
	{
		*(str_copy + token[i][1] + 1) = '\0';
		/* A month already has a value set, check for token_value == -1 */
		if (token_values[i] == -1)
		{
			errno = 0;
			token_values[i] = strtol(str_copy + token[i][0], (char **) NULL, 10);
			/* strtol sets errno in case of an error */
			if (errno)
				token_values[i] = -1;
		}
		if (fmt_token_order[i] == 'd')
			tm.tm_mday = token_values[i];
		else if (fmt_token_order[i] == 'm')
			tm.tm_mon = token_values[i];
		else if (fmt_token_order[i] == 'y')
			tm.tm_year = token_values[i];
	}
	free(str_copy);

	if (tm.tm_mday < 1 || tm.tm_mday > 31)
	{
		errno = PGTYPES_DATE_BAD_DAY;
		return -1;
	}

	if (tm.tm_mon < 1 || tm.tm_mon > MONTHS_PER_YEAR)
	{
		errno = PGTYPES_DATE_BAD_MONTH;
		return -1;
	}

	if (tm.tm_mday == 31 && (tm.tm_mon == 4 || tm.tm_mon == 6 || tm.tm_mon == 9 || tm.tm_mon == 11))
	{
		errno = PGTYPES_DATE_BAD_DAY;
		return -1;
	}

	if (tm.tm_mon == 2 && tm.tm_mday > 29)
	{
		errno = PGTYPES_DATE_BAD_DAY;
		return -1;
	}

	*d = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - date2j(2000, 1, 1);

	return 0;
}