Date and time validation

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
MichaelR
Forum Contributor
Posts: 148
Joined: Sat Jan 03, 2009 3:27 pm

Date and time validation

Post by MichaelR »

Here's a class to allow a little more flexibility than the native function checkdate().

Code: Select all


    class DateValidator {

      private $_date = array();
      private $_lost;

      public function __construct($date = null, $lost = false) {

        $this->_lost = $lost;

        switch (true) {

          case (strpos($date, '/')):
            $date = explode('/', $date);
            break;
          case (strpos($date, '-')):
            $date = explode('-', $date);
            break;
          case (strpos($date, '.')):
            $date = explode('.', $date);
            break;

        }

        if (!isset($date[2][1]) || isset($date[0][2])) {
          $this->_date = $date;
        }

        else {
          $this->_date = array_reverse($date);
        }

      }

      public function isValidDay() {

        if (!isset($this->_date[2]) || !ctype_digit($this->_date[2]) || $this->_date[2] < 1 || $this->_date[2] > 31) {
          return false;
        }

        return true;

      }

      public function isValidMonth() {

        if (!isset($this->_date[1]) || !ctype_digit($this->_date[1]) || $this->_date[1] < 1 || $this->_date[1] > 12) {
          return false;
        }

        return true;

      }

      public function isValidYear() {

        if (!isset($this->_date[0]) || !ctype_digit($this->_date[0]) || $this->_date[0] == 0) {
          return false;
        }

        if ($this->_date[0] == 0) {
          return false;
        }

        return true;

      }

      public function isValidDate() {

        if (!$this->isValidDay() || !$this->isValidMonth() || !$this->isValidYear()) {
          return false;
        }

        if (isset($this->_date[3])) {
          return false;
        }

        if ($this->_date[2] == 31 && in_array($this->_date[1], array(4, 6, 9, 11))) {
          return false;
        }

        if ($this->_date[2] == 30 && $this->_date[1] == 2) {
          return false;
        }

        if ($this->_date[2] == 29 && $this->_date[1] == 2) {

          if (!is_int($this->_date[0] / 4)) {
            return false;
          }

          if (!is_int($this->_date[0] / 400) && is_int($this->_date[0] / 100)) {
            return false;
          }

        }

        if ($this->_lost && $this->_date[0] == 1752 && $this->_date[1] == 9 && $this->_date[2] > 2 && $this->_date[2] < 14) {
          return false;
        }

        return true;

      }

      public function returnDate() {

        if (!$this->isValidDate()) {
          return '';
        }

        if (!isset($this->_date[2][1])) {
          $this->_date[2] = '0' . $this->_date[2];
        }

        if (!isset($this->_date[1][1])) {
          $this->_date[1] = '0' . $this->_date[1];
        }

        return implode('-', $this->_date);

      }

    }

Acceptable date formats:

Code: Select all

01/12/2000
1/12/2000
2000/01/31
2000/1/31
1-1-2000
01-01-2000
2000-01-31
2000-1-31
01.12.2000
1.12.2000
2000.01.31
2000.1.31
1 1 2000
01 01 2000
2000 01 31
2000 1 31
Example:

Code: Select all


// Input

$validDate = new DateValidator('30/02/2001');

var_dump($validDate->isValidDay());
var_dump($validDate->isValidMonth());
var_dump($validDate->isValidYear());
var_dump($validDate->isValidDate());

$validDate = new DateValidator("1 1 2001');

echo $validDate->returnDate(); // Returns date in ISO 8601 format (YYYY-MM-DD)

// Output

bool(true) // 1 - 31 allowed (00 - 09 as well)
bool(true) // 1 - 12 allowed (00 - 09 as well)
bool(true) // 0001 - 9999 allowed (must be four digits to distinguish days from years)
bool(false) // 30 February is an invalid date

2001-01-01

Just for fun, if you pass as the second parameter a true value then any date between 03/09/1752 and 13/09/1752 inclusive will return false. This is because when the Julian calendar was switched to the Gregorian calendar in Great Britain, 11 days needed to be removed to establish accuracy. They went to sleep on Wednesday September 2nd and woke up the following day on Thursday September 14th.

And here's a class for time validation:

Code: Select all


    class TimeValidator {

      private $_time = array();

      public function __construct($time = null) {

        switch (true) {

          case (strpos($time, ':')):
            $this->_time = explode(':', $time);
            break;
          case (strpos($time, '-')):
            $this->_time = explode('-', $time);
            break;
          case (strpos($time, '.')):
            $this->_time = explode('.', $time);
            break;
          case (strpos($time, ' ')):
            $this->_time = explode(' ', $time);
            break;

        }

      }

      public function isValidSecond() {

        if (!isset($this->_time[2]) || !ctype_digit($this->_time[2]) || $this->_time[2] > 59) {
          return false;
        }

        return true;

      }

      public function isValidMinute() {

        if (!isset($this->_time[1]) || !ctype_digit($this->_time[1]) || $this->_time[1] > 59) {
          return false;
        }

        return true;

      }

      public function isValidHour() {

        if (!isset($this->_time[0]) || !ctype_digit($this->_time[0]) || $this->_time[0] > 24) {
          return false;
        }

        return true;

      }

      public function isValidTime() {

        if (!$this->isValidSecond() || !$this->isValidMinute() || !$this->isValidHour()) {
          return false;
        }

        if ($this->_time[0] == 24 && ($this->_time[1] != 0 || $this->_time[2] != 0)) {
          return false;
        }

        return true;

      }

      public function returnTime() {

        if (!$this->isValidTime()) {
          return '';
        }

        if (!isset($this->_time[0][1])) {
          $this->_time[0] = '0' . $this->_time[0];
        }

        if (!isset($this->_time[1][1])) {
          $this->_time[1] = '0' . $this->_time[1];
        }

        if (!isset($this->_time[2][1])) {
          $this->_time[2] = '0' . $this->_time[2];
        }

        return implode(':', $this->_time);

      }

    }

This works in pretty much the same way but with the methods isValidSecond(), isValidMinute(), isValidHour(), and isValidTime(). The time must be entered in the order hour, minute, second, and can be separated with either a colon, a dash-minus, a dot, or a space. An hour of 24 is allowed only if the minutes and seconds are both (0)0. The method returnTime() will return the time in the format HH:MM:SS.
Last edited by MichaelR on Fri Oct 22, 2010 4:40 am, edited 2 times in total.
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: Date and time validation

Post by josh »

Lots of literals that aren't informative. '400' should be replaced with a descriptive constant Could use unit tests.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: Date and time validation

Post by VladSun »

How about using strtotime() to do all of this?
There are 10 types of people in this world, those who understand binary and those who don't
MichaelR
Forum Contributor
Posts: 148
Joined: Sat Jan 03, 2009 3:27 pm

Re: Date and time validation

Post by MichaelR »

VladSun wrote:How about using strtotime() to do all of this?

Code: Select all


// Input

echo date('Y-m-d', strtotime('13 December 1901'));

// Outputs

1970-01-01
josh wrote:Lots of literals that aren't informative. '400' should be replaced with a descriptive constant Could use unit tests.
Sorry. I thought they were kind of obvious in context. I'll change them to variables if you think it's more suitable. And I'll add some tests.
Post Reply