class BackdropDateTime extends DateTime {
public $granularity = array();
public $errors = array();
public $dateOnly;
public $timeOnly;
public $originalTime;
protected static $allgranularity = array(
'year',
'month',
'day',
'hour',
'minute',
'second',
'timezone'
);
public function __toString() {
return $this->format(DATE_FORMAT_DATETIME) . ' ' . $this->getTimeZone()->getName();
}
public function __construct($time = 'now', $tz = NULL, $format = NULL) {
$this->timeOnly = FALSE;
$this->dateOnly = FALSE;
$this->originalTime = $time;
if (!empty($tz) && !is_object($tz)) {
$tz = new DateTimeZone($tz);
}
elseif (empty($tz)) {
$tz = date_default_timezone_object();
}
if (is_numeric($time) && (empty($format) || $format == 'U')) {
$time = "@" . $time;
$date = new BackdropDateTime($time, 'UTC');
if ($tz->getName() != 'UTC') {
$date->setTimezone($tz);
}
$time = $date->format(DATE_FORMAT_DATETIME);
$format = DATE_FORMAT_DATETIME;
$this->addGranularity('timezone');
}
elseif (is_array($time)) {
if (empty($time['year']) && empty($time['month']) && empty($time['day'])) {
$this->timeOnly = TRUE;
}
if (empty($time['hour']) && empty($time['minute']) && empty($time['second'])) {
$this->dateOnly = TRUE;
}
$this->errors = $this->arrayErrors($time);
$time = $this->toISO($time, TRUE);
$format = NULL;
}
else {
$time = date_make_iso_valid($time);
}
$successfully_parsed = FALSE;
if (!empty($format)) {
$arg = self::$allgranularity;
$element = array_pop($arg);
while (!$this->parse($time, $tz, $format) && $element != 'year') {
$element = array_pop($arg);
$format = date_limit_format($format, $arg);
}
$successfully_parsed = empty($this->errors);
if (isset($this->errors['invalid'])) {
unset($this->errors['invalid']);
}
}
if (!$successfully_parsed && is_string($time)) {
$time = str_replace("GMT-", "-", $time);
$time = str_replace("GMT+", "+", $time);
if (strtotime($time) !== FALSE) {
parent::__construct($time, $tz);
}
else {
$this->errors['date'] = t('The date "!date" does not match the expected format.', array('!date' => $time));
return;
}
if (empty($this->granularity)) {
$this->setGranularityFromTime($time, $tz);
}
}
if (!$this->getTimezone() || !preg_match('/[a-zA-Z]/', $this->getTimezone()->getName())) {
if (preg_match('/[a-zA-Z]/', $tz->getName())) {
$this->setTimezone($tz);
}
else {
$this->setTimezone(new DateTimeZone("UTC"));
$this->errors['timezone'] = t('No valid timezone name was provided.');
}
}
}
public function merge(BackdropDateTime $other) {
$other_tz = $other->getTimezone();
$this_tz = $this->getTimezone();
$use_tz = ($this->hasGranularity('timezone') || !$other->hasGranularity('timezone')) ? $this_tz : $other_tz;
$this2 = clone $this;
$this2->setTimezone($use_tz);
$other->setTimezone($use_tz);
$val = $this2->toArray(TRUE);
$otherval = $other->toArray();
foreach (self::$allgranularity as $g) {
if ($other->hasGranularity($g) && !$this2->hasGranularity($g)) {
$this2->addGranularity($g);
$val[$g] = $otherval[$g];
}
}
$other->setTimezone($other_tz);
$this2->setDate($val['year'], $val['month'], $val['day']);
$this2->setTime($val['hour'], $val['minute'], $val['second']);
return $this2;
}
public function setTimezone($timezone, $force = FALSE) {
if (!$this->hasTime() || !$this->hasGranularity('timezone') || $force) {
$arr = $this->toArray(TRUE);
parent::setTimezone($timezone);
$this->setDate($arr['year'], $arr['month'], $arr['day']);
$this->setTime($arr['hour'], $arr['minute'], $arr['second']);
$this->addGranularity('timezone');
return $this;
}
return parent::setTimezone($timezone);
}
public function format($format, $force = FALSE) {
if (!empty($this->errors)) {
return FALSE;
}
return parent::format($force ? $format : date_limit_format($format, $this->granularity));
}
public function addGranularity($g) {
$this->granularity[] = $g;
$this->granularity = array_unique($this->granularity);
}
public function removeGranularity($g) {
if (($key = array_search($g, $this->granularity)) !== FALSE) {
unset($this->granularity[$key]);
}
}
public function hasGranularity($g = NULL) {
if ($g === NULL) {
$last = TRUE;
foreach (self::$allgranularity as $arg) {
if ($arg == 'timezone') {
continue;
}
if (in_array($arg, $this->granularity) && !$last) {
return FALSE;
}
$last = in_array($arg, $this->granularity);
}
return in_array('year', $this->granularity);
}
if (is_array($g)) {
foreach ($g as $gran) {
if (!in_array($gran, $this->granularity)) {
return FALSE;
}
}
return TRUE;
}
return in_array($g, $this->granularity);
}
public function validGranularity($granularity = NULL, $flexible = FALSE) {
$true = $this->hasGranularity() && (!$granularity || $flexible || $this->hasGranularity($granularity));
if (!$true && $granularity) {
foreach ((array) $granularity as $part) {
if (!$this->hasGranularity($part) && in_array($part, array(
'second',
'minute',
'hour',
'day',
'month',
'year')
)) {
switch ($part) {
case 'second':
$this->errors[$part] = t('The second is missing.');
break;
case 'minute':
$this->errors[$part] = t('The minute is missing.');
break;
case 'hour':
$this->errors[$part] = t('The hour is missing.');
break;
case 'day':
$this->errors[$part] = t('The day is missing.');
break;
case 'month':
$this->errors[$part] = t('The month is missing.');
break;
case 'year':
$this->errors[$part] = t('The year is missing.');
break;
}
}
}
}
return $true;
}
public function hasTime() {
return $this->hasGranularity('hour');
}
public function limitGranularity($granularity) {
foreach ($this->granularity as $key => $val) {
if ($val != 'timezone' && !in_array($val, $granularity)) {
unset($this->granularity[$key]);
}
}
}
protected function setGranularityFromTime($time, $tz) {
$this->granularity = array();
$temp = date_parse($time);
if ($time == 'now') {
$this->granularity = array(
'year',
'month',
'day',
'hour',
'minute',
'second',
);
}
else {
foreach (self::$allgranularity as $g) {
if ((isset($temp[$g]) && is_numeric($temp[$g])) || ($g == 'timezone' && (isset($temp['zone_type']) && $temp['zone_type'] > 0))) {
$this->granularity[] = $g;
}
}
}
if ($tz) {
$this->addGranularity('timezone');
}
}
protected function parse($date, $tz, $format) {
$array = date_format_patterns();
foreach ($array as $key => $value) {
$patterns[] = "`(^|[^\\\\\\\\])" . $key . "`";
$repl1[] = '${1}(.)';
$repl2[] = '${1}(' . $value . ')';
}
$patterns[] = "`\\\\\\\\([" . implode(array_keys($array)) . "])`";
$repl1[] = '${1}';
$repl2[] = '${1}';
$format_regexp = preg_quote($format);
$regex1 = preg_replace($patterns, $repl1, $format_regexp, 1);
$regex1 = str_replace('A', '(.)', $regex1);
$regex1 = str_replace('a', '(.)', $regex1);
preg_match('`^' . $regex1 . '$`', stripslashes($format), $letters);
array_shift($letters);
$regex2 = preg_replace($patterns, $repl2, $format_regexp, 1);
$regex2 = str_replace('A', '(AM|PM)', $regex2);
$regex2 = str_replace('a', '(am|pm)', $regex2);
preg_match('`^' . $regex2 . '$`u', $date, $values);
array_shift($values);
if (count($letters) != count($values)) {
$this->errors['invalid'] = t('The value @date does not match the expected format.', array('@date' => $date));
return FALSE;
}
$this->granularity = array();
$final_date = array(
'hour' => 0,
'minute' => 0,
'second' => 0,
'month' => 1,
'day' => 1,
'year' => 0,
);
foreach ($letters as $i => $letter) {
$value = $values[$i];
switch ($letter) {
case 'd':
case 'j':
$final_date['day'] = intval($value);
$this->addGranularity('day');
break;
case 'n':
case 'm':
$final_date['month'] = intval($value);
$this->addGranularity('month');
break;
case 'F':
$array_month_long = array_flip(date_month_names());
$final_date['month'] = array_key_exists($value, $array_month_long) ? $array_month_long[$value] : -1;
$this->addGranularity('month');
break;
case 'M':
$array_month = array_flip(date_month_names_abbr());
$final_date['month'] = array_key_exists($value, $array_month) ? $array_month[$value] : -1;
$this->addGranularity('month');
break;
case 'Y':
$final_date['year'] = $value;
$this->addGranularity('year');
if (strlen($value) < 4) {
$this->errors['year'] = t('The year is invalid. Please check that entry includes four digits.');
}
break;
case 'y':
$year = $value;
$final_date['year'] = str_pad($year, 4, substr(date("Y"), 0, 2), STR_PAD_LEFT);
$this->addGranularity('year');
break;
case 'a':
case 'A':
$ampm = strtolower($value);
break;
case 'g':
case 'h':
case 'G':
case 'H':
$final_date['hour'] = intval($value);
$this->addGranularity('hour');
break;
case 'i':
$final_date['minute'] = intval($value);
$this->addGranularity('minute');
break;
case 's':
$final_date['second'] = intval($value);
$this->addGranularity('second');
break;
case 'U':
parent::__construct($value, $tz ? $tz : new DateTimeZone("UTC"));
$this->addGranularity('year');
$this->addGranularity('month');
$this->addGranularity('day');
$this->addGranularity('hour');
$this->addGranularity('minute');
$this->addGranularity('second');
return $this;
}
}
if (isset($ampm) && $ampm == 'pm' && $final_date['hour'] < 12) {
$final_date['hour'] += 12;
}
elseif (isset($ampm) && $ampm == 'am' && $final_date['hour'] == 12) {
$final_date['hour'] -= 12;
}
parent::__construct('', $tz ? $tz : new DateTimeZone("UTC"));
if ($tz) {
$this->addGranularity('timezone');
}
$final_date['year'] = intval($final_date['year']);
$this->errors += $this->arrayErrors($final_date);
$granularity = backdrop_map_assoc($this->granularity);
if (empty($final_date['year']) && empty($final_date['month']) && empty($final_date['day'])) {
$this->timeOnly = TRUE;
}
elseif (empty($this->errors)) {
if (empty($granularity['month'])) {
$final_date['month'] = 1;
}
if (empty($granularity['day'])) {
$final_date['day'] = 1;
}
$this->setDate($final_date['year'], $final_date['month'], $final_date['day']);
}
if (!isset($final_date['hour']) && !isset($final_date['minute']) && !isset($final_date['second'])) {
$this->dateOnly = TRUE;
}
elseif (empty($this->errors)) {
$this->setTime($final_date['hour'], $final_date['minute'], $final_date['second']);
}
return $this;
}
public function toArray($force = FALSE) {
return array(
'year' => $this->format('Y', $force),
'month' => $this->format('n', $force),
'day' => $this->format('j', $force),
'hour' => intval($this->format('H', $force)),
'minute' => intval($this->format('i', $force)),
'second' => intval($this->format('s', $force)),
'timezone' => $this->format('e', $force),
);
}
public function toISO($arr, $full = FALSE) {
if ($full) {
$arr += array(
'year' => 0,
'month' => 1,
'day' => 1,
'hour' => 0,
'minute' => 0,
'second' => 0,
);
}
else {
$arr += array(
'year' => '',
'month' => '',
'day' => '',
'hour' => '',
'minute' => '',
'second' => '',
);
}
$datetime = '';
if ($arr['year'] !== '') {
$datetime = date_pad(intval($arr['year']), 4);
if ($full || $arr['month'] !== '') {
$datetime .= '-' . date_pad(intval($arr['month']));
if ($full || $arr['day'] !== '') {
$datetime .= '-' . date_pad(intval($arr['day']));
}
}
}
if ($arr['hour'] !== '') {
$datetime .= $datetime ? 'T' : '';
$datetime .= date_pad(intval($arr['hour']));
if ($full || $arr['minute'] !== '') {
$datetime .= ':' . date_pad(intval($arr['minute']));
if ($full || $arr['second'] !== '') {
$datetime .= ':' . date_pad(intval($arr['second']));
}
}
}
return $datetime;
}
public function setFuzzyDate($date, $format = NULL, $default = 'first') {
$timezone = $this->getTimeZone() ? $this->getTimeZone()->getName() : NULL;
$comp = new BackdropDateTime($date, $timezone, $format);
$arr = $comp->toArray(TRUE);
foreach ($arr as $key => $value) {
$arr[$key] = $this->forceValid($key, intval($value), $default, $arr['month'], $arr['year']);
}
$this->setDate($arr['year'], $arr['month'], $arr['day']);
$this->setTime($arr['hour'], $arr['minute'], $arr['second']);
}
protected function forceValid($part, $value, $default = 'first', $month = NULL, $year = NULL) {
$now = date_now();
switch ($part) {
case 'year':
$fallback = $now->format('Y');
return !is_int($value) || empty($value) || $value < DATE_MIN_YEAR || $value > DATE_MAX_YEAR ? $fallback : $value;
case 'month':
$fallback = $default == 'first' ? 1 : $now->format('n');
return !is_int($value) || empty($value) || $value <= 0 || $value > 12 ? $fallback : $value;
case 'day':
$fallback = $default == 'first' ? 1 : $now->format('j');
$max_day = isset($year) && isset($month) ? @date_days_in_month($year, $month) : 31;
return !is_int($value) || empty($value) || $value <= 0 || $value > $max_day ? $fallback : $value;
case 'hour':
$fallback = $default == 'first' ? 0 : $now->format('G');
return !is_int($value) || $value < 0 || $value > 23 ? $fallback : $value;
case 'minute':
$fallback = $default == 'first' ? 0 : $now->format('i');
return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value;
case 'second':
$fallback = $default == 'first' ? 0 : $now->format('s');
return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value;
}
return (int) $value;
}
public function arrayErrors($arr) {
$errors = array();
$now = date_now();
$default_month = !empty($arr['month']) ? $arr['month'] : $now->format('n');
$default_year = !empty($arr['year']) ? $arr['year'] : $now->format('Y');
$this->granularity = array();
foreach ($arr as $part => $value) {
if (is_numeric($value)) {
$this->addGranularity($part);
}
$value = intval($value);
if (!empty($value) && $this->forceValid($part, $value, 'now', $default_month, $default_year) != $value) {
switch ($part) {
case 'year':
$errors['year'] = t('The year is invalid.');
break;
case 'month':
$errors['month'] = t('The month is invalid.');
break;
case 'day':
$errors['day'] = t('The day is invalid.');
break;
case 'hour':
$errors['hour'] = t('The hour is invalid.');
break;
case 'minute':
$errors['minute'] = t('The minute is invalid.');
break;
case 'second':
$errors['second'] = t('The second is invalid.');
break;
}
}
}
if ($this->hasTime()) {
$this->addGranularity('timezone');
}
return $errors;
}
public function difference($date2_in, $measure = 'seconds', $absolute = TRUE) {
$date1 = clone($this);
$date2 = clone($date2_in);
if (is_object($date1) && is_object($date2)) {
$diff = date_format($date2, 'U') - date_format($date1, 'U');
if ($diff == 0) {
return 0;
}
elseif ($diff < 0 && $absolute) {
$temp = $date2;
$date2 = $date1;
$date1 = $temp;
$diff = date_format($date2, 'U') - date_format($date1, 'U');
}
$year_diff = intval(date_format($date2, 'Y') - date_format($date1, 'Y'));
switch ($measure) {
case 'seconds':
return $diff;
case 'minutes':
return $diff / 60;
case 'hours':
return $diff / 3600;
case 'years':
return $year_diff;
case 'months':
$format = 'n';
$item1 = date_format($date1, $format);
$item2 = date_format($date2, $format);
if ($year_diff == 0) {
return intval($item2 - $item1);
}
elseif ($year_diff < 0) {
$item_diff = 0 - $item1;
$item_diff -= intval((abs($year_diff) - 1) * 12);
return $item_diff - (12 - $item2);
}
else {
$item_diff = 12 - $item1;
$item_diff += intval(($year_diff - 1) * 12);
return $item_diff + $item2;
}
break;
case 'days':
$format = 'z';
$item1 = date_format($date1, $format);
$item2 = date_format($date2, $format);
if ($year_diff == 0) {
return intval($item2 - $item1);
}
elseif ($year_diff < 0) {
$item_diff = 0 - $item1;
for ($i = 1; $i < abs($year_diff); $i++) {
date_modify($date1, '-1 year');
$item_diff -= date_days_in_year($date1);
}
return $item_diff - (date_days_in_year($date2) - $item2);
}
else {
$item_diff = date_days_in_year($date1) - $item1;
for ($i = 1; $i < $year_diff; $i++) {
date_modify($date1, '+1 year');
$item_diff += date_days_in_year($date1);
}
return $item_diff + $item2;
}
break;
case 'weeks':
$week_diff = date_format($date2, 'W') - date_format($date1, 'W');
$year_diff = date_format($date2, 'o') - date_format($date1, 'o');
$sign = ($year_diff < 0) ? -1 : 1;
for ($i = 1; $i <= abs($year_diff); $i++) {
date_modify($date1, (($sign > 0) ? '+' : '-') . '1 year');
$week_diff += (date_iso_weeks_in_year($date1) * $sign);
}
return $week_diff;
}
}
return NULL;
}
}