silverstripe-framework/model/fieldtypes/Date.php
Damian Mooyman 9872fbef4d API Refactor CompositeDBField into an abstract class
API Refactor ClassName into DBClassName
API Update PolymorphicForeignKey to use new CompositeDBField and DBClassName

CompositeDBField is now an interface to nested fields on an underlying dataobject, allowing field manipulation to be performed at the field and dataobject level without having to synchronise them manually.
2015-09-22 10:28:07 +12:00

413 lines
12 KiB
PHP

<?php
/**
* 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 Date 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 = SS_Datetime::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 = SS_Datetime::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 = SS_Datetime::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) < SS_Datetime::now()->Format('U');
}
/**
* Returns true if date is in the future.
* @return boolean
*/
public function InFuture() {
return strtotime($this->value) > SS_Datetime::now()->Format('U');
}
/**
* Returns true if date is today.
* @return boolean
*/
public function IsToday() {
return (date('Y-m-d', strtotime($this->value)) == SS_Datetime::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;
}
}