Advanced zen date raw()

From Zen Cart(tm) Wiki
Jump to: navigation, search


This is a suggested code change written to allow more flexible entering of dates, primarily for the date of birth entry on customer registrations.


  • year can be specified by inserting 2 or 4 digits
  • month can be specified by inserting the abbreviated or full month name, or 1 or 2 digits
  • day can be specified by inserting 1 or 2 digits
  • you no longer need to separate parts with slashes
  • this function will also validate the date


Remember to set the correct date settings to get localised dates.


For this function to work as intended, you will especially need to properly set the constant DATE_FORMAT, defined in the same file as this function.


Available formatting characters: Y, y, F, M, m, n, d, j - see the manual entry for date() to learn about formatting.


As always when changing core files, you're highly advised to make use of the override system.


Copy includes/languages/<current language>.php to includes/languages/<current template>/<current language>.php and open the new file.


Replace the function zen_date_raw() with the following code:

# get a list of abbreviated and full month names
for ($i=1; $i < 13; $i++)
{
	$monthAbbrs[$i] = strtolower(strftime('%b',strtotime("2000-$i-1")));
	$monthNames[$i] = strtolower(strftime('%B',strtotime("2000-$i-1")));
}
# if DATE_FORMAT has not been defined, find it automatically by checking against the localised format
if (!defined('DATE_FORMAT'))
{
	$localFormat = strftime('%x', strtotime('2000-1-3'));
	$localFormat = str_replace(array('2000', '00', '01', '1', '03', '3'), array('Y', 'y', 'm', 'n', 'd', 'j'), $localFormat);
	$localFormat = str_replace($monthNames, 'F', $localFormat);
	$localFormat = str_replace($monthAbbrs, 'M', $localFormat);
	define('DATE_FORMAT', $localFormat);
}

/**
 * Convert a string to a valid date formatted as YYYYMMDD or DDMMYYYY.
 * Year can be specified using 2 or 4 digits.
 * Month can be specified using the abbreviated or full month name, or 1 or 2 digits.
 * Day can be specified using 1 or 2 digits.
 * Use setlocale() to get localised dates.
 *
 * @param string $date the date string to convert
 * @param bool $dmy return date formatted as DDMMYYYY if true, or YYYYMMDD otherwise
 * @return mixed string containing the formatted extracted date, or false if the string is not a valid date
 */
function zen_date_raw ($date, $dmy = false)
{
	$date = strtolower($date);
	$monthStrPos = false;
	# look for a full month name in the date string
	foreach ($GLOBALS['monthNames'] as $monthNum => $monthName)
	{
		$monthStrPos = strpos($date, $monthName);
		if ($monthStrPos !== false)
		{
			# replace the month name with the month number
			$date = substr_replace($date, $monthNum, $monthStrPos, strlen($monthName));
			break;
		}
	}
	if ($monthStrPos === false)
	# a full month name was not found in the date string, now look for abbreviations
	{
		foreach ($GLOBALS['monthAbbrs'] as $monthNum => $monthAbbr)
		{
			$monthStrPos = strpos($date, $monthAbbr);
			if ($monthStrPos !== false)
			{
				# replace the month abbreviation with the month number
				$date = substr_replace($date, $monthNum, $monthStrPos, strlen($monthAbbr));
				break;
			}
		}
	}
	# remove all non-numeric characters
	$date = preg_replace('/[^0-9]/', '', $date);
	# remove all characters except those allowed from the constant DATE_FORMAT
	$dformat = preg_replace('/[^YyFMmndj]/', '', DATE_FORMAT);
	# regex for day
	$dd = '([0-2][1-9]|[1-3][0-1]|[1-9])';
	# regex for month
	if ($monthStrPos !== false)
	{
		$mm = '(' . $monthNum . ')';
	}
	else
	{
		$mm = '(0?[1-9]|1[0-2])';
	}
	# regex for year
	$yyyy = '(([0-9]{2})?[0-9]{2})';
	# create the entire regex line and set the locations for day, month and year based on the date format
	if (preg_match('/(d|j)(F|M|m|n)(y|Y)/', $dformat))
	{
		$regexp = $dd . $mm . $yyyy;
		$d = '1';
		$m = '2';
		$y = '3';
	}
	elseif (preg_match('/(F|M|m|n)(d|j)(y|Y)/', $dformat))
	{
		$regexp = $mm . $dd . $yyyy;
		$d = '2';
		$m = '1';
		$y = '3';
	}
	elseif (preg_match('/(d|j)(y|Y)(F|M|m|n)/', $dformat))
	{
		$regexp = $dd . $yyyy . $mm;
		$d = '1';
		$m = '4';
		$y = '2';
	}
	elseif (preg_match('/(F|M|m|n)(y|Y)(d|j)/', $dformat))
	{
		$regexp = $mm . $yyyy . $dd;
		$d = '4';
		$m = '1';
		$y = '2';
	}
	elseif (preg_match('/(y|Y)(d|j)(F|M|m|n)/', $dformat))
	{
		$regexp = $yyyy . $dd . $mm;
		$d = '3';
		$m = '4';
		$y = '1';
	}
	else
	{
		$regexp = $yyyy . $mm . $dd;
		$d = '4';
		$m = '3';
		$y = '1';
	}
	# if regex doesn't match, return false
	if (!preg_match('/^' . $regexp . '$/', $date, $regs))
	{
		return false;
	}
	# If some values are too short, fix them
	# fix value of day
	if (strlen($regs[$d]) === 1)
	{
		$regs[$d] = '0' . $regs[$d];
	}
	# fix value of month
	if (strlen($regs[$m]) === 1)
	{
		$regs[$m] = '0' . $regs[$m];
	}
	# fix value of year
	if (strlen($regs[$y]) === 2)
	{
		$century = (int)substr(date('Y'), 0, 2);
		# If year is greater than the last 2 numbers of the current year, set century to the previous century
		if ($regs[$y] > date('y'))
		{
			$century--;
		}
		$regs[$y] = $century . $regs[$y];
	}
	# validate the date
	if (!checkdate($regs[$m], $regs[$d], $regs[$y]))
	{
		return false;
	}
	if ($dmy === true)
	{
		# return DDMMYYYY
		return $regs[$d] . $regs[$m] . $regs[$y];
	}
	# return YYYYMMDD
	return $regs[$y] . $regs[$m] . $regs[$d];
}