silverstripe-framework/ORM/FieldType/DBDate.php

437 lines
12 KiB
PHP

<?php
namespace SilverStripe\ORM\FieldType;
use Zend_Date;
use DateTime;
use DateField;
use Convert;
use Exception;
use SilverStripe\ORM\DB;
use SilverStripe\Security\Member;
/**
* 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 orm
*/
class DBDate extends DBField {
/**
* @config
* @see DBDateTime::nice_format
* @see Time::nice_format
*/
private static $nice_format = 'd/m/Y';
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 specified by the config value nice_format, or dd/mm/yy by default
*/
public function Nice() {
if($this->value) return $this->Format($this->config()->nice_format);
}
/**
* 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 bool $includeOrdinal 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
*
* @param string $formattingString
* @return string
*/
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 DBDate $otherDateObj Another date object specifying the end of the range
* @param bool $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;
}
}