This tutorial is intended for developers who
want to take a user-supplied date and format it so that it can be added
to the database, and then to take a date from the database and format
for display to the user.
A Class for Validating and Formatting Dates
Intended Audience This tutorial is intended for developers who want to
take a user-supplied date and format it so that it can be added to the
database, and then to take a date from the database and format for
display to the user.
Prerequisites
It is assumed you have basic knowledge of PHP. This tutorial will cover some of the following areas in PHP:
• Class/Object functions.
• Simple Array functions.
• Simple regular expressions.
• Date functions (checkdate, GregoriantoJD, JDtoGregorian).
• String functions (strtolower, ucfirst, strlen, substr, list, split).
Defining the Class to Handle Dates
Class Variables
We must start by defining our date class and the class variables which
we would like to persist between one function call and the next:
class DateClass
{
var $monthalpha; // array of 3-character month names
var $internaldate; // date as held in the database (yyyymmdd)
var $externaldate; // date as shown to the user (dd Mmm yyyy)
var $errors; // error messages
Class Constructor
This is followed by what is known as the 'class constructor', a
function which is called automatically when an instance of this class
is created. In PHP4 the name of the constructor is the same as the
class name. It is used here to construct an array of month names. Note
that we are forcing the index number to start at 1 instead of the
default of 0.
function DateClass ()
{
$this-<monthalpha = array(1=<'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
} // DateClass
Accept date from the User
The first method (function) in this class will accept input from the
user, validate it, and format it ready for writing to the database. The
user may input a date in a number of different ways, so we will use
regular expressions to help us decipher the user's input.
With regular expressions we can supply a pattern with any number of
component parts, and if the input string matches the pattern each part
of the input which matched a part of the pattern is placed in an output
array.
The first regular expression will look for input in the format
d(d)?m(m)?y(yyy) (1 or 2 digits, a separator, 1 or 2 digits, a
separator, then 1-4 digits). For a separator I am allowing any
character which is not a number or a letter.
function getInternalDate ($input)
{
$pattern = '(^[0-9]{1,2})' // 1 or 2 digits
.'([^0-9a-zA-Z])' // not alpha or numeric
.'([0-9]{1,2})' // 1 or 2 digits
.'([^0-9a-zA-Z])' // not alpha or numeric
.'([0-9]{1,4}$)'; // 1 to 4 digits
if (ereg($pattern, $input, $regs)) {
$result = $this-<verifyDate($regs[1],$regs[3],$regs[5]);
return $result;
} // if
If this fails the next regular expression will look for input in the format d(d)?MMM?y(yyy).
$pattern = '(^[0-9]{1,2})' // 1 or 2 digits
.'([^0-9a-zA-Z])' // not alpha or numeric
.'([a-zA-Z]{1,})' // 1 or more alpha
.'([^0-9a-zA-Z])' // not alpha or numeric
.'([0-9]{1,4}$)'; // 1 to 4 digits
if (ereg($pattern, $input, $regs)) {
$result = $this->verifyDate($regs[1],$regs[3],$regs[5]);
return $result;
} // if
The next regular expression will look for input in the format d(d)?MMM?y(yyy).
$pattern = '(^[0-9]{1,2})' // 1 or 2 digits
.'([a-zA-Z]{1,})' // 1 or more alpha
.'([0-9]{1,4}$)'; // 1 to 4 digits
if (ereg($pattern, $input, $regs)) {
$result = $this->verifyDate($regs[1],$regs[2],$regs[3]);
return $result;
} // if
The next regular expression will look for input in the format MMM?d(d)?y(yyy).
$pattern = '(^[a-zA-Z]{1,})' // 1 or more alpha
.'([^0-9a-zA-Z])' // not alpha or numeric
.'([0-9]{1,2})' // 1 or 2 digits
.'([^0-9a-zA-Z])' // not alpha or numeric
.'([0-9]{1,4}$)'; // 1 to 4 digits
if (ereg($pattern, $input, $regs)) {
$result = $this->verifyDate($regs[3],$regs[1],$regs[5]);
return $result;
} // if
The next regular expression will look for input in the format MMMddyyyy.
$pattern = '(^[a-zA-Z]{1,})' // 1 or more alpha
.'([0-9]{2})' // 2 digits
.'([0-9]{4}$)'; // 4 digits
if (ereg($pattern, $input, $regs)) {
$result = $this->verifyDate($regs[2],$regs[1],$regs[3]);
return $result;
} // if
The next regular expression will look for input in the format yyyy?m(m)?d(d).
$pattern = '(^[0-9]{4})' // 4 digits
.'([^0-9a-zA-Z])' // not alpha or numeric
.'([0-9]{1,2})' // 1 or 2 digits
.'([^0-9a-zA-Z])' // not alpha or numeric
.'([0-9]{1,2}$)'; // 1 to 2 digits
if (ereg($pattern, $input, $regs)) {
$result = $this->verifyDate($regs[5],$regs[3],$regs[1]);
return $result;
} // if
The next regular expression will look for input in the format ddmmyyyy.
$pattern = '(^[0-9]{2})' // 2 digits
.'([0-9]{2})' // 2 digits
.'([0-9]{4}$)'; // 4 digits
if (ereg($pattern, $input, $regs)) {
$result = $this->verifyDate($regs[1],$regs[2],$regs[3]);
return $result;
} // if
The next regular expression will look for input in the format yyyy?MMM?d(d).
$pattern = '(^[0-9]{4})' // 4 digits
.'([^0-9a-zA-Z])' // not alpha or numeric
.'([a-zA-Z]{1,})' // 1 or more alpha
.'([^0-9a-zA-Z])' // not alpha or numeric
.'([0-9]{1,2}$)'; // 1 to 2 digits
if (ereg($pattern, $input, $regs)) {
$result = $this->verifyDate($regs[5],$regs[3],$regs[1]);
return $result;
} // if
If we get to this point in the code it means that
we have not found a match against any of those patterns, so we generate
an error message and return an empty result to the user.
$this->errors = 'This is not a valid date';
return FALSE;
} // getInternalDate
You may have noticed that after
matching the input to a pattern function called verifyDate was called.
Here is the contents of that function, with verifies that the component
parts actually constitute a valid date.
function verifyDate($day, $month, $year)
{
This first part will check for the month being supplied
as characters instead of numbers, and perform a lookup on the array of
month names created in the class constructor. Note that it uses
strtolower() to convert the user's input to lowercase, then ucfirst()
to make the first character uppercase. This is done to match the
contents of $this->monthalpha.
if (eregi('([a-z]{3})', $month)) {
$month = ucfirst(strtolower($month));
if (!$month = array_search($month, $this->monthalpha)) {
$this->errors = 'Month name is invalid';
return FALSE;
} // if
} // if
This next part will check that the year has 4 digits, filling in anything that is missing:
if (strlen($year) == 1) {
$year = '200' .$year;
} // if
if (strlen($year) == 2) {
$year = '20' .$year;
} // if
if (strlen($year) == 3) {
$year = '2' .$year;
} // if
Now we can use the PHP checkdate() function to
check that this is a valid date, and if it is we can change the format
to ccyy-mm-dd ready for writing to the database.
if (!checkdate($month, $day, $year)) {
$this->errors = 'This is not a valid date';
return FALSE;
} else {
if (strlen($day) < 2) {
$day = '0' .$day; // add leading zero
} // if
if (strlen($month) < 2) {
$month = '0' .$month; // add leading zero
} // if
$this->internaldate = $year .'-' .$month .'-' .$day;
return $this->internaldate;
} // if
return;
} // verifyDate
Display date to the User
This function will take a date from the database (although it could
come from other sources) and format it ready for display to the user.
function getExternalDate ($input)
{
This first part checks for input in the format yyyymmdd.
if (strlen($input) == 8) {
$pattern = '(^[0-9]{4})' // 4 digits (yyyy)
.'([0-9]{2})' // 2 digits (mm)
.'([0-9]{2}$)'; // 2 digits (dd)
if (ereg($pattern, $input, $regs)) {
if (!checkdate($regs[2], $regs[3], $regs[1])) {
$this->errors = 'This is not a valid date';
return FALSE;
} else {
$monthnum = (int)$regs[2];
$this->externaldate = "$regs[3] " .$this->monthalpha[$monthnum] ." $regs[1]";
return $this->externaldate;
} // if
} // if
$this->errors = "Invalid date format: expected 'yyyymmdd'";
return FALSE;
} // if
If this does not find a match this next part checks for input in the format yyyy-mm-dd.
if (strlen($input) == 10) {
$pattern = '(^[0-9]{4})' // 4 digits (yyyy)
.'([^0-9])' // not a digit
.'([0-9]{2})' // 2 digits (mm)
.'([^0-9])' // not a digit
.'([0-9]{2}$)'; // 2 digits (dd)
if (ereg($pattern, $input, $regs)) {
if (!checkdate($regs[3], $regs[5], $regs[1])) {
$this->errors = 'This is not a valid date';
return FALSE;
} else {
$monthnum = (int)$regs[3];
$this->externaldate = "$regs[5] " .$this->monthalpha[$monthnum] ." $regs[1]";
return $this->externaldate;
} // if
} // if
$this->errors = "Invalid date format: expected 'dd-mm-yyyy'";
return FALSE;
} // if
If we have not found a match against any of these patterns then we
generate an error message before returning an empty result to the user.
$this->errors = 'This is not a valid date';
return $input;
} // getExternalDate
Incrementing or Decrementing a Date
There may come a time when you need to find a date which is several
days before or after a given date. This can be done by using the
GregoriantoJD() function to convert a Gregorian date to a Julian date
(the number of days since a base date, which is 4714 BC in this case),
add or subtract from the Julian day count, then use the JDtoGregorian()
function to convert from Julian back into Gregorian. This can be done
by adding another function to our date class. In the following code you
will notice that the new addDays() function makes use of the
getInternalDate() function to process the input and the
getExternalDate() function to format the output.
function addDays ($internaldate, $days)
// add a number of days (may be negative) to $internaldate (YYYY-MM-DD)
// and return the result in the same format
{
// ensure date is in internal format
$internaldate = $this->getInternalDate($internaldate);
// convert to the number of days since basedate (4714 BC)
$julian = GregoriantoJD(substr($internaldate,5,2)
,substr($internaldate,8,2)
,substr($internaldate,0,4));
$days = (int)$days;
$julian = $julian + $days;
// convert from Julian to Gregorian (format m/d/y)
$gregorian = JDtoGregorian($julian);
// split date into its component parts
list ($month, $day, $year) = split ('[/]', $gregorian);
// convert back into standard format
$result = $this->getInternaldate("$day/$month/$year");
return $result;
Using this Class in Your Code
In order to use any of the functions in this class you will need code similar to the following:
First you must create an object from the class (or instantiate a class instance in OOP speak):
require 'date.class.in';
$dateobj = new DateClass;
This code will take a date in external (user) format and convert it to
internal (database) format, with any error messages being inserted into
the $errors array.
if (!$internaldate = $dateobj->getInternalDate($_POST['date']) {
$errors = $dateobj->errors;
} // if
This code will take a date in internal (database) format and convert it
to external (user) format, with any error messages being inserted into
the $errors array.
if (!$externaldate = $dateobj->getExternalDate($internaldate]) {
$errors = $dateobj->errors;
} // if
This code will take a date and obtain the dates for the previous and next days.
$today = date('Y-m-d');
$tomorrow = $dateobj->addDays($today, +1);
$yesterday = $dateobj->addDays($today, -1);
Summary
It is always useful to have a standard way of validating and formatting
dates. Hopefully this tutorial has demonstrated how easily this can be
done by using functions defined within a date class. It should now be a
straightforward process for you to take this source code and customise
it for your own purposes.
|