This class is intended to fulfill these requirements:
- Output in Y-m-d H:i:s format
- Allow dates to be entered as m-d-Y or m-d-y
- Complain if an invalid birthdate is entered (i.e., in the future or otherwise)
- 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() ?>