php – DateTime Extension for entering historical dates, i.e. birthdays

This class is intended to fulfill these requirements:

  1. Output in Y-m-d H:i:s format
  2. Allow dates to be entered as m-d-Y or m-d-y
  3. Complain if an invalid birthdate is entered (i.e., in the future or otherwise)
  4. Allow simpler, friendlier coding without typing a lengthy DateTime instantiation
<?php
/**
 * CustomDateTime with historical conversion and statically called methods
 *
 * European order d-m-y is not supported, and assumed to be m-d-Y.  
 * (converted to American m/d/y)
 * 
 * Adds `historical()` method which throws a range exception if a date is not 
 * in the past. YY dates are forced to the past.
 * 
 * Adds several static methods for easier writing and less cognitive effort
 * (CustomDateTime::now() vs (new DateTim())->format('Y-m-d H:i:s'))
 *
 */
class CustomDateTime extends DateTime 
{

    /**
    * Holds the original constructor string
    * @var string
    */
    public $time;

    /**
     * __construct accepts exactly the same parameters as the DateTime object
     *
     * European order d-m-y is not supported, and m-d-Y is converted to m/d/Y.
     * 
     * @param string            $time     Just about any English textual datetime description
     * @param DateTimeZone|null $timezone A DateTimeZone object representing the timezone of $time.
     */
    public function __construct(string $time="now", DateTimeZone $timezone = null) 
    {
        $this->time = str_replace(
          ('-','.'), 
          '/', 
          substr($time, 0, 10)
        ) . substr($time, 10 );
        parent::__construct($this->time, $timezone);
    }

    /**
     * Convert to Historical Date (static method)
     *
     * Static method to access instantiated version (historical())
     * 
     * @param  string $date m/d/Y, Y-m-d, '', null
     * @return string       Y-m-d or null if no value given
     */
    public static function asHistorical($date) : ?string 
    {
        if(empty($date)) { return null; }
        return (new CustomDateTime($date))->historical()->format('Y-m-d');
    }

    /**
     * Historical (instantiated) 
     * 
     * Originally was intended to prevent birthdates in the future.
     *
     * Silently corrects 2 digit year to a date in the past
     * Throws `RangeException` if date is more than 100 years in the future.
     *
     * @return CustomDateTime instance, which allows chaining of methods
     */
    public function historical() : CustomDateTime
    {
        $now = (new DateTime())->format('Y-m-d');

        // if a two-digit year was converted to the future, subtract 100 years
        if(
            preg_match("@^(0-9){1,2}/(0-9){1,2}/(0-9){2}$@", $this->time) &&
            $this->format('Y-m-d') > $now
        ) {
            $this->modify('-100 years');
        }

        if( $this->format('Y-m-d') > $now) {
            throw new RangeException('Date is too far in the future');
        }

        return $this;
    }

    /**
     * Return Database Safe date
     * 
     * This avoids passing '' to database, replacing it with null
     *
     * @param  string $date m/d/Y, Y-m-d, '', null
     * @return string       Y-m-d or null if no value given
     */
    public static function dbSafe($date) : ?string
    {
        if(empty($date)) { return null; }
        return (new CustomDateTime($date))->format('Y-m-d');
    }


    public static function now() : string
    {
        return (new DateTime())->format('Y-m-d H:i:s');
    }

    public static function today() : string
    {
        return (new DateTime())->format('Y-m-d');
    }
}
?>
Examples:


Short version: historical date... 3-4-30 = <?= CustomDateTime::asHistorical('3-4-30') ?>

Long version: historical date.... 3-4-30 = <?= (new CustomDateTime('3-4-30'))->historical()->format('Y-m-d') ?>


DateTime uses European format.... 3-4-30 = <?= (new DateTime('3-4-30'))->format('m/d/Y') ?>

DateTime splits century.......... 3-4-30 = <?= (new DateTime('3/4/30'))->format('m/d/Y') ?>


Bonus:

Easily get "now"................. <?= CustomDateTime::now() ?>