mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-08-28 13:36:12 +02:00
API: Deprecate SS_Datetime. The DBField subclasses are have all been renamed to start with “DB” and be in the SilverStripe\Model\FieldType namespace. To keep DataObject definitions concise, the original short variations of their names are preserved as service definitions. Most of the field generation code doesn’t need to change, but where field classes are referenced directly, changes will be needed. SS_Datetime, which is commonly referenced outside the model system itself, has been preserved as a subclass of DBDatetime. This has been marked as deprecated and can be removed in SilverStripe 5. A few places that referred to $db and $casting values weren’t using the Injector to instantiate the relevant classes. This meant that the remapping we have created as part of moving classes into a namespace didn’t work.
424 lines
12 KiB
PHP
424 lines
12 KiB
PHP
<?php
|
|
|
|
namespace SilverStripe\Model\FieldType;
|
|
|
|
use DB;
|
|
use Member;
|
|
use Zend_Date;
|
|
use DateTime;
|
|
use DateField;
|
|
use Convert;
|
|
use Exception;
|
|
|
|
/**
|
|
* Represents a date field.
|
|
* The field currently supports New Zealand date format (DD/MM/YYYY),
|
|
* or an ISO 8601 formatted date (YYYY-MM-DD).
|
|
* Alternatively you can set a timestamp that is evaluated through
|
|
* PHP's built-in date() function according to your system locale.
|
|
*
|
|
* Example definition via {@link DataObject::$db}:
|
|
* <code>
|
|
* static $db = array(
|
|
* "Expires" => "Date",
|
|
* );
|
|
* </code>
|
|
*
|
|
* @todo Add localization support, see http://open.silverstripe.com/ticket/2931
|
|
*
|
|
* @package framework
|
|
* @subpackage model
|
|
*/
|
|
class DBDate extends DBField {
|
|
|
|
public function setValue($value, $record = null, $markChanged = true) {
|
|
if($value === false || $value === null || (is_string($value) && !strlen($value))) {
|
|
// don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be
|
|
// saved as NULL in database
|
|
$this->value = null;
|
|
return;
|
|
}
|
|
|
|
// @todo This needs tidy up (what if you only specify a month and a year, for example?)
|
|
if(is_array($value)) {
|
|
if(!empty($value['Day']) && !empty($value['Month']) && !empty($value['Year'])) {
|
|
$this->value = $value['Year'] . '-' . $value['Month'] . '-' . $value['Day'];
|
|
return;
|
|
} else {
|
|
// return nothing (so checks below don't fail on an empty array)
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Default to NZ date format - strtotime expects a US date
|
|
if(preg_match('#^([0-9]+)/([0-9]+)/([0-9]+)$#', $value, $parts)) {
|
|
$value = "$parts[2]/$parts[1]/$parts[3]";
|
|
}
|
|
|
|
if(is_numeric($value)) {
|
|
$this->value = date('Y-m-d', $value);
|
|
} elseif(is_string($value)) {
|
|
try{
|
|
$date = new DateTime($value);
|
|
$this->value = $date->Format('Y-m-d');
|
|
return;
|
|
}catch(Exception $e){
|
|
$this->value = null;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the date in the format dd/mm/yy
|
|
*/
|
|
public function Nice() {
|
|
if($this->value) return $this->Format('d/m/Y');
|
|
}
|
|
|
|
/**
|
|
* Returns the date in US format: “01/18/2006”
|
|
*/
|
|
public function NiceUS() {
|
|
if($this->value) return $this->Format('m/d/Y');
|
|
}
|
|
|
|
/**
|
|
* Returns the year from the given date
|
|
*/
|
|
public function Year() {
|
|
if($this->value) return $this->Format('Y');
|
|
}
|
|
|
|
/**
|
|
* Returns the Full day, of the given date.
|
|
*/
|
|
public function Day(){
|
|
if($this->value) return $this->Format('l');
|
|
}
|
|
|
|
/**
|
|
* Returns a full textual representation of a month, such as January.
|
|
*/
|
|
public function Month() {
|
|
if($this->value) return $this->Format('F');
|
|
}
|
|
|
|
/**
|
|
* Returns the short version of the month such as Jan
|
|
*/
|
|
public function ShortMonth() {
|
|
if($this->value) return $this->Format('M');
|
|
}
|
|
|
|
/**
|
|
* Returns the day of the month.
|
|
* @param boolean $includeOrdinals Include ordinal suffix to day, e.g. "th" or "rd"
|
|
* @return string
|
|
*/
|
|
public function DayOfMonth($includeOrdinal = false) {
|
|
if($this->value) {
|
|
$format = 'j';
|
|
if ($includeOrdinal) $format .= 'S';
|
|
return $this->Format($format);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the date in the format 24 December 2006
|
|
*/
|
|
public function Long() {
|
|
if($this->value) return $this->Format('j F Y');
|
|
}
|
|
|
|
/**
|
|
* Returns the date in the format 24 Dec 2006
|
|
*/
|
|
public function Full() {
|
|
if($this->value) return $this->Format('j M Y');
|
|
}
|
|
|
|
/**
|
|
* Return the date using a particular formatting string.
|
|
*
|
|
* @param string $format Format code string. e.g. "d M Y" (see http://php.net/date)
|
|
* @return string The date in the requested format
|
|
*/
|
|
public function Format($format) {
|
|
if($this->value){
|
|
$date = new DateTime($this->value);
|
|
return $date->Format($format);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the date formatted using the given strftime formatting string.
|
|
*
|
|
* strftime obeys the current LC_TIME/LC_ALL when printing lexical values
|
|
* like day- and month-names
|
|
*/
|
|
public function FormatI18N($formattingString) {
|
|
if($this->value) {
|
|
return strftime($formattingString, strtotime($this->value));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a date formatted as per a CMS user's settings.
|
|
*
|
|
* @param Member $member
|
|
* @return boolean | string A date formatted as per user-defined settings.
|
|
*/
|
|
public function FormatFromSettings($member = null) {
|
|
require_once 'Zend/Date.php';
|
|
|
|
if(!$member) {
|
|
if(!Member::currentUserID()) {
|
|
return false;
|
|
}
|
|
$member = Member::currentUser();
|
|
}
|
|
|
|
$formatD = $member->getDateFormat();
|
|
$zendDate = new Zend_Date($this->getValue(), 'y-MM-dd');
|
|
|
|
return $zendDate->toString($formatD);
|
|
}
|
|
|
|
/*
|
|
* Return a string in the form "12 - 16 Sept" or "12 Aug - 16 Sept"
|
|
* @param Date $otherDateObj Another date object specifying the end of the range
|
|
* @param boolean $includeOrdinals Include ordinal suffix to day, e.g. "th" or "rd"
|
|
* @return string
|
|
*/
|
|
public function RangeString($otherDateObj, $includeOrdinals = false) {
|
|
$d1 = $this->DayOfMonth($includeOrdinals);
|
|
$d2 = $otherDateObj->DayOfMonth($includeOrdinals);
|
|
$m1 = $this->ShortMonth();
|
|
$m2 = $otherDateObj->ShortMonth();
|
|
$y1 = $this->Year();
|
|
$y2 = $otherDateObj->Year();
|
|
|
|
if($y1 != $y2) return "$d1 $m1 $y1 - $d2 $m2 $y2";
|
|
else if($m1 != $m2) return "$d1 $m1 - $d2 $m2 $y1";
|
|
else return "$d1 - $d2 $m1 $y1";
|
|
}
|
|
|
|
public function Rfc822() {
|
|
if($this->value) return date('r', strtotime($this->value));
|
|
}
|
|
|
|
public function Rfc2822() {
|
|
if($this->value) return date('Y-m-d H:i:s', strtotime($this->value));
|
|
}
|
|
|
|
public function Rfc3339() {
|
|
$timestamp = ($this->value) ? strtotime($this->value) : false;
|
|
if(!$timestamp) return false;
|
|
|
|
$date = date('Y-m-d\TH:i:s', $timestamp);
|
|
|
|
$matches = array();
|
|
if(preg_match('/^([\-+])(\d{2})(\d{2})$/', date('O', $timestamp), $matches)) {
|
|
$date .= $matches[1].$matches[2].':'.$matches[3];
|
|
} else {
|
|
$date .= 'Z';
|
|
}
|
|
|
|
return $date;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of seconds/minutes/hours/days or months since the timestamp.
|
|
*
|
|
* @param boolean $includeSeconds Show seconds, or just round to "less than a minute".
|
|
* @param int $significance Minimum significant value of X for "X units ago" to display
|
|
* @return String
|
|
*/
|
|
public function Ago($includeSeconds = true, $significance = 2) {
|
|
if($this->value) {
|
|
$time = DBDatetime::now()->Format('U');
|
|
if(strtotime($this->value) == $time || $time > strtotime($this->value)) {
|
|
return _t(
|
|
'Date.TIMEDIFFAGO',
|
|
"{difference} ago",
|
|
'Natural language time difference, e.g. 2 hours ago',
|
|
array('difference' => $this->TimeDiff($includeSeconds, $significance))
|
|
);
|
|
} else {
|
|
return _t(
|
|
'Date.TIMEDIFFIN',
|
|
"in {difference}",
|
|
'Natural language time difference, e.g. in 2 hours',
|
|
array('difference' => $this->TimeDiff($includeSeconds, $significance))
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param boolean $includeSeconds Show seconds, or just round to "less than a minute".
|
|
* @param int $significance Minimum significant value of X for "X units ago" to display
|
|
* @return string
|
|
*/
|
|
public function TimeDiff($includeSeconds = true, $significance = 2) {
|
|
if(!$this->value) return false;
|
|
|
|
$time = DBDatetime::now()->Format('U');
|
|
$ago = abs($time - strtotime($this->value));
|
|
if($ago < 60 && !$includeSeconds) {
|
|
return _t('Date.LessThanMinuteAgo', 'less than a minute');
|
|
} elseif($ago < $significance * 60 && $includeSeconds) {
|
|
return $this->TimeDiffIn('seconds');
|
|
} elseif($ago < $significance * 3600) {
|
|
return $this->TimeDiffIn('minutes');
|
|
} elseif($ago < $significance * 86400) {
|
|
return $this->TimeDiffIn('hours');
|
|
} elseif($ago < $significance * 86400 * 30) {
|
|
return $this->TimeDiffIn('days');
|
|
} elseif($ago < $significance * 86400 * 365) {
|
|
return $this->TimeDiffIn('months');
|
|
} else {
|
|
return $this->TimeDiffIn('years');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the time difference, but always returns it in a certain format
|
|
*
|
|
* @param string $format The format, could be one of these:
|
|
* 'seconds', 'minutes', 'hours', 'days', 'months', 'years'.
|
|
* @return string The resulting formatted period
|
|
*/
|
|
public function TimeDiffIn($format) {
|
|
if(!$this->value) return false;
|
|
|
|
$time = DBDatetime::now()->Format('U');
|
|
$ago = abs($time - strtotime($this->value));
|
|
|
|
switch($format) {
|
|
case "seconds":
|
|
$span = $ago;
|
|
return ($span != 1) ? "{$span} "._t("Date.SECS", "secs") : "{$span} "._t("Date.SEC", "sec");
|
|
|
|
case "minutes":
|
|
$span = round($ago/60);
|
|
return ($span != 1) ? "{$span} "._t("Date.MINS", "mins") : "{$span} "._t("Date.MIN", "min");
|
|
|
|
case "hours":
|
|
$span = round($ago/3600);
|
|
return ($span != 1) ? "{$span} "._t("Date.HOURS", "hours") : "{$span} "._t("Date.HOUR", "hour");
|
|
|
|
case "days":
|
|
$span = round($ago/86400);
|
|
return ($span != 1) ? "{$span} "._t("Date.DAYS", "days") : "{$span} "._t("Date.DAY", "day");
|
|
|
|
case "months":
|
|
$span = round($ago/86400/30);
|
|
return ($span != 1) ? "{$span} "._t("Date.MONTHS", "months") : "{$span} "._t("Date.MONTH", "month");
|
|
|
|
case "years":
|
|
$span = round($ago/86400/365);
|
|
return ($span != 1) ? "{$span} "._t("Date.YEARS", "years") : "{$span} "._t("Date.YEAR", "year");
|
|
}
|
|
}
|
|
|
|
public function requireField() {
|
|
$parts=Array('datatype'=>'date', 'arrayValue'=>$this->arrayValue);
|
|
$values=Array('type'=>'date', 'parts'=>$parts);
|
|
DB::require_field($this->tableName, $this->name, $values);
|
|
}
|
|
|
|
/**
|
|
* Returns true if date is in the past.
|
|
* @return boolean
|
|
*/
|
|
public function InPast() {
|
|
return strtotime($this->value) < DBDatetime::now()->Format('U');
|
|
}
|
|
|
|
/**
|
|
* Returns true if date is in the future.
|
|
* @return boolean
|
|
*/
|
|
public function InFuture() {
|
|
return strtotime($this->value) > DBDatetime::now()->Format('U');
|
|
}
|
|
|
|
/**
|
|
* Returns true if date is today.
|
|
* @return boolean
|
|
*/
|
|
public function IsToday() {
|
|
return (date('Y-m-d', strtotime($this->value)) == DBDatetime::now()->Format('Y-m-d'));
|
|
}
|
|
|
|
/**
|
|
* Returns a date suitable for insertion into a URL and use by the system.
|
|
*/
|
|
public function URLDate() {
|
|
return date('Y-m-d', strtotime($this->value));
|
|
}
|
|
|
|
|
|
public function days_between($fyear, $fmonth, $fday, $tyear, $tmonth, $tday){
|
|
return abs((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, $tmonth, $tday, $tyear))/(60*60*24));
|
|
}
|
|
|
|
public function day_before($fyear, $fmonth, $fday){
|
|
return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-1,$fyear));
|
|
}
|
|
|
|
public function next_day($fyear, $fmonth, $fday){
|
|
return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday+1,$fyear));
|
|
}
|
|
|
|
public function weekday($fyear, $fmonth, $fday){ // 0 is a Monday
|
|
return (((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, 7, 17, 2006))/(60*60*24))+700000) % 7;
|
|
}
|
|
|
|
public function prior_monday($fyear, $fmonth, $fday){
|
|
return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-$this->weekday($fyear, $fmonth, $fday),$fyear));
|
|
}
|
|
|
|
/**
|
|
* Return the nearest date in the past, based on day and month.
|
|
* Automatically attaches the correct year.
|
|
*
|
|
* This is useful for determining a financial year start or end date.
|
|
*
|
|
* @param $fmonth int The number of the month (e.g. 3 is March, 4 is April)
|
|
* @param $fday int The day of the month
|
|
* @param $fyear int Determine historical value
|
|
* @return string Date in YYYY-MM-DD format
|
|
*/
|
|
public static function past_date($fmonth, $fday = 1, $fyear = null) {
|
|
if(!$fyear) $fyear = date('Y');
|
|
$fday = (int) $fday;
|
|
$fmonth = (int) $fmonth;
|
|
$fyear = (int) $fyear;
|
|
|
|
$pastDate = mktime(0, 0, 0, $fmonth, $fday, $fyear);
|
|
$curDate = mktime(0, 0, 0, date('m'), date('d'), $fyear);
|
|
|
|
if($pastDate < $curDate) {
|
|
return date('Y-m-d', mktime(0, 0, 0, $fmonth, $fday, $fyear));
|
|
} else {
|
|
return date('Y-m-d', mktime(0, 0, 0, $fmonth, $fday, $fyear - 1));
|
|
}
|
|
}
|
|
|
|
public function scaffoldFormField($title = null, $params = null) {
|
|
$field = DateField::create($this->name, $title);
|
|
|
|
// Show formatting hints for better usability
|
|
$field->setDescription(sprintf(
|
|
_t('FormField.Example', 'e.g. %s', 'Example format'),
|
|
Convert::raw2xml(Zend_Date::now()->toString($field->getConfig('dateformat')))
|
|
));
|
|
$field->setAttribute('placeholder', $field->getConfig('dateformat'));
|
|
|
|
return $field;
|
|
}
|
|
}
|