1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Represents a date field. |
4
|
|
|
* The field currently supports New Zealand date format (DD/MM/YYYY), |
5
|
|
|
* or an ISO 8601 formatted date (YYYY-MM-DD). |
6
|
|
|
* Alternatively you can set a timestamp that is evaluated through |
7
|
|
|
* PHP's built-in date() function according to your system locale. |
8
|
|
|
* |
9
|
|
|
* Example definition via {@link DataObject::$db}: |
10
|
|
|
* <code> |
11
|
|
|
* static $db = array( |
12
|
|
|
* "Expires" => "Date", |
13
|
|
|
* ); |
14
|
|
|
* </code> |
15
|
|
|
* |
16
|
|
|
* @todo Add localization support, see http://open.silverstripe.com/ticket/2931 |
17
|
|
|
* |
18
|
|
|
* @package framework |
19
|
|
|
* @subpackage model |
20
|
|
|
*/ |
21
|
|
|
class Date extends DBField { |
22
|
|
|
|
23
|
|
|
public function setValue($value, $record = null) { |
24
|
|
View Code Duplication |
if($value === false || $value === null || (is_string($value) && !strlen($value))) { |
|
|
|
|
25
|
|
|
// don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be |
26
|
|
|
// saved as NULL in database |
27
|
|
|
$this->value = null; |
28
|
|
|
return; |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
// @todo This needs tidy up (what if you only specify a month and a year, for example?) |
32
|
|
|
if(is_array($value)) { |
33
|
|
|
if(!empty($value['Day']) && !empty($value['Month']) && !empty($value['Year'])) { |
34
|
|
|
$this->value = $value['Year'] . '-' . $value['Month'] . '-' . $value['Day']; |
35
|
|
|
return; |
36
|
|
|
} else { |
37
|
|
|
// return nothing (so checks below don't fail on an empty array) |
38
|
|
|
return null; |
39
|
|
|
} |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
// Default to NZ date format - strtotime expects a US date |
43
|
|
View Code Duplication |
if(preg_match('#^([0-9]+)/([0-9]+)/([0-9]+)$#', $value, $parts)) { |
|
|
|
|
44
|
|
|
$value = "$parts[2]/$parts[1]/$parts[3]"; |
45
|
|
|
} |
46
|
|
|
|
47
|
|
|
if(is_numeric($value)) { |
48
|
|
|
$this->value = date('Y-m-d', $value); |
49
|
|
View Code Duplication |
} elseif(is_string($value)) { |
|
|
|
|
50
|
|
|
try{ |
51
|
|
|
$date = new DateTime($value); |
52
|
|
|
$this->value = $date->Format('Y-m-d'); |
53
|
|
|
return; |
54
|
|
|
}catch(Exception $e){ |
55
|
|
|
$this->value = null; |
56
|
|
|
return; |
57
|
|
|
} |
58
|
|
|
} |
59
|
|
|
} |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Returns the date in the format dd/mm/yy |
63
|
|
|
*/ |
64
|
|
|
public function Nice() { |
65
|
|
|
if($this->value) return $this->Format('d/m/Y'); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Returns the date in US format: “01/18/2006” |
70
|
|
|
*/ |
71
|
|
|
public function NiceUS() { |
72
|
|
|
if($this->value) return $this->Format('m/d/Y'); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Returns the year from the given date |
77
|
|
|
*/ |
78
|
|
|
public function Year() { |
79
|
|
|
if($this->value) return $this->Format('Y'); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Returns the Full day, of the given date. |
84
|
|
|
*/ |
85
|
|
|
public function Day(){ |
86
|
|
|
if($this->value) return $this->Format('l'); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Returns a full textual representation of a month, such as January. |
91
|
|
|
*/ |
92
|
|
|
public function Month() { |
93
|
|
|
if($this->value) return $this->Format('F'); |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* Returns the short version of the month such as Jan |
98
|
|
|
*/ |
99
|
|
|
public function ShortMonth() { |
100
|
|
|
if($this->value) return $this->Format('M'); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Returns the day of the month. |
105
|
|
|
* @param boolean $includeOrdinals Include ordinal suffix to day, e.g. "th" or "rd" |
|
|
|
|
106
|
|
|
* @return string |
107
|
|
|
*/ |
108
|
|
|
public function DayOfMonth($includeOrdinal = false) { |
109
|
|
|
if($this->value) { |
110
|
|
|
$format = 'j'; |
111
|
|
|
if ($includeOrdinal) $format .= 'S'; |
112
|
|
|
return $this->Format($format); |
113
|
|
|
} |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Returns the date in the format 24 December 2006 |
118
|
|
|
*/ |
119
|
|
|
public function Long() { |
120
|
|
|
if($this->value) return $this->Format('j F Y'); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Returns the date in the format 24 Dec 2006 |
125
|
|
|
*/ |
126
|
|
|
public function Full() { |
127
|
|
|
if($this->value) return $this->Format('j M Y'); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Return the date using a particular formatting string. |
132
|
|
|
* |
133
|
|
|
* @param string $format Format code string. e.g. "d M Y" (see http://php.net/date) |
134
|
|
|
* @return string The date in the requested format |
135
|
|
|
*/ |
136
|
|
|
public function Format($format) { |
137
|
|
|
if($this->value){ |
138
|
|
|
$date = new DateTime($this->value); |
139
|
|
|
return $date->Format($format); |
140
|
|
|
} |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Return the date formatted using the given strftime formatting string. |
145
|
|
|
* |
146
|
|
|
* strftime obeys the current LC_TIME/LC_ALL when printing lexical values |
147
|
|
|
* like day- and month-names |
148
|
|
|
*/ |
149
|
|
|
public function FormatI18N($formattingString) { |
150
|
|
|
if($this->value) { |
151
|
|
|
return strftime($formattingString, strtotime($this->value)); |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Return a date formatted as per a CMS user's settings. |
157
|
|
|
* |
158
|
|
|
* @param Member $member |
159
|
|
|
* @return boolean | string A date formatted as per user-defined settings. |
160
|
|
|
*/ |
161
|
|
|
public function FormatFromSettings($member = null) { |
162
|
|
|
require_once 'Zend/Date.php'; |
163
|
|
|
|
164
|
|
|
if(!$member) { |
165
|
|
|
if(!Member::currentUserID()) { |
166
|
|
|
return false; |
167
|
|
|
} |
168
|
|
|
$member = Member::currentUser(); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
$formatD = $member->getDateFormat(); |
172
|
|
|
$zendDate = new Zend_Date($this->getValue(), 'y-MM-dd'); |
173
|
|
|
|
174
|
|
|
return $zendDate->toString($formatD); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/* |
178
|
|
|
* Return a string in the form "12 - 16 Sept" or "12 Aug - 16 Sept" |
179
|
|
|
* @param Date $otherDateObj Another date object specifying the end of the range |
180
|
|
|
* @param boolean $includeOrdinals Include ordinal suffix to day, e.g. "th" or "rd" |
181
|
|
|
* @return string |
182
|
|
|
*/ |
183
|
|
|
public function RangeString($otherDateObj, $includeOrdinals = false) { |
184
|
|
|
$d1 = $this->DayOfMonth($includeOrdinals); |
185
|
|
|
$d2 = $otherDateObj->DayOfMonth($includeOrdinals); |
186
|
|
|
$m1 = $this->ShortMonth(); |
187
|
|
|
$m2 = $otherDateObj->ShortMonth(); |
188
|
|
|
$y1 = $this->Year(); |
189
|
|
|
$y2 = $otherDateObj->Year(); |
190
|
|
|
|
191
|
|
|
if($y1 != $y2) return "$d1 $m1 $y1 - $d2 $m2 $y2"; |
192
|
|
|
else if($m1 != $m2) return "$d1 $m1 - $d2 $m2 $y1"; |
193
|
|
|
else return "$d1 - $d2 $m1 $y1"; |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
public function Rfc822() { |
197
|
|
|
if($this->value) return date('r', strtotime($this->value)); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
public function Rfc2822() { |
201
|
|
|
if($this->value) return date('Y-m-d H:i:s', strtotime($this->value)); |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
public function Rfc3339() { |
205
|
|
|
$timestamp = ($this->value) ? strtotime($this->value) : false; |
206
|
|
|
if(!$timestamp) return false; |
|
|
|
|
207
|
|
|
|
208
|
|
|
$date = date('Y-m-d\TH:i:s', $timestamp); |
209
|
|
|
|
210
|
|
|
$matches = array(); |
211
|
|
|
if(preg_match('/^([\-+])(\d{2})(\d{2})$/', date('O', $timestamp), $matches)) { |
212
|
|
|
$date .= $matches[1].$matches[2].':'.$matches[3]; |
213
|
|
|
} else { |
214
|
|
|
$date .= 'Z'; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
return $date; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Returns the number of seconds/minutes/hours/days or months since the timestamp. |
222
|
|
|
* |
223
|
|
|
* @param boolean $includeSeconds Show seconds, or just round to "less than a minute". |
224
|
|
|
* @param int $significance Minimum significant value of X for "X units ago" to display |
225
|
|
|
* @return String |
226
|
|
|
*/ |
227
|
|
|
public function Ago($includeSeconds = true, $significance = 2) { |
228
|
|
|
if($this->value) { |
229
|
|
|
$time = SS_Datetime::now()->Format('U'); |
230
|
|
|
if(strtotime($this->value) == $time || $time > strtotime($this->value)) { |
231
|
|
|
return _t( |
232
|
|
|
'Date.TIMEDIFFAGO', |
233
|
|
|
"{difference} ago", |
234
|
|
|
'Natural language time difference, e.g. 2 hours ago', |
235
|
|
|
array('difference' => $this->TimeDiff($includeSeconds, $significance)) |
|
|
|
|
236
|
|
|
); |
237
|
|
|
} else { |
238
|
|
|
return _t( |
239
|
|
|
'Date.TIMEDIFFIN', |
240
|
|
|
"in {difference}", |
241
|
|
|
'Natural language time difference, e.g. in 2 hours', |
242
|
|
|
array('difference' => $this->TimeDiff($includeSeconds, $significance)) |
|
|
|
|
243
|
|
|
); |
244
|
|
|
} |
245
|
|
|
} |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* @param boolean $includeSeconds Show seconds, or just round to "less than a minute". |
250
|
|
|
* @param int $significance Minimum significant value of X for "X units ago" to display |
251
|
|
|
* @return string |
252
|
|
|
*/ |
253
|
|
|
public function TimeDiff($includeSeconds = true, $significance = 2) { |
254
|
|
|
if(!$this->value) return false; |
255
|
|
|
|
256
|
|
|
$time = SS_Datetime::now()->Format('U'); |
257
|
|
|
$ago = abs($time - strtotime($this->value)); |
258
|
|
|
if($ago < 60 && !$includeSeconds) { |
259
|
|
|
return _t('Date.LessThanMinuteAgo', 'less than a minute'); |
260
|
|
|
} elseif($ago < $significance * 60 && $includeSeconds) { |
261
|
|
|
return $this->TimeDiffIn('seconds'); |
262
|
|
|
} elseif($ago < $significance * 3600) { |
263
|
|
|
return $this->TimeDiffIn('minutes'); |
264
|
|
|
} elseif($ago < $significance * 86400) { |
265
|
|
|
return $this->TimeDiffIn('hours'); |
266
|
|
|
} elseif($ago < $significance * 86400 * 30) { |
267
|
|
|
return $this->TimeDiffIn('days'); |
268
|
|
|
} elseif($ago < $significance * 86400 * 365) { |
269
|
|
|
return $this->TimeDiffIn('months'); |
270
|
|
|
} else { |
271
|
|
|
return $this->TimeDiffIn('years'); |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Gets the time difference, but always returns it in a certain format |
277
|
|
|
* |
278
|
|
|
* @param string $format The format, could be one of these: |
279
|
|
|
* 'seconds', 'minutes', 'hours', 'days', 'months', 'years'. |
280
|
|
|
* @return string The resulting formatted period |
281
|
|
|
*/ |
282
|
|
|
public function TimeDiffIn($format) { |
283
|
|
|
if(!$this->value) return false; |
284
|
|
|
|
285
|
|
|
$time = SS_Datetime::now()->Format('U'); |
286
|
|
|
$ago = abs($time - strtotime($this->value)); |
287
|
|
|
|
288
|
|
|
switch($format) { |
289
|
|
|
case "seconds": |
290
|
|
|
$span = $ago; |
291
|
|
|
return ($span != 1) ? "{$span} "._t("Date.SECS", "secs") : "{$span} "._t("Date.SEC", "sec"); |
292
|
|
|
|
293
|
|
|
case "minutes": |
294
|
|
|
$span = round($ago/60); |
295
|
|
|
return ($span != 1) ? "{$span} "._t("Date.MINS", "mins") : "{$span} "._t("Date.MIN", "min"); |
296
|
|
|
|
297
|
|
|
case "hours": |
298
|
|
|
$span = round($ago/3600); |
299
|
|
|
return ($span != 1) ? "{$span} "._t("Date.HOURS", "hours") : "{$span} "._t("Date.HOUR", "hour"); |
300
|
|
|
|
301
|
|
|
case "days": |
302
|
|
|
$span = round($ago/86400); |
303
|
|
|
return ($span != 1) ? "{$span} "._t("Date.DAYS", "days") : "{$span} "._t("Date.DAY", "day"); |
304
|
|
|
|
305
|
|
|
case "months": |
306
|
|
|
$span = round($ago/86400/30); |
307
|
|
|
return ($span != 1) ? "{$span} "._t("Date.MONTHS", "months") : "{$span} "._t("Date.MONTH", "month"); |
308
|
|
|
|
309
|
|
|
case "years": |
310
|
|
|
$span = round($ago/86400/365); |
311
|
|
|
return ($span != 1) ? "{$span} "._t("Date.YEARS", "years") : "{$span} "._t("Date.YEAR", "year"); |
312
|
|
|
} |
313
|
|
|
} |
314
|
|
|
|
315
|
|
|
public function requireField() { |
316
|
|
|
$parts=Array('datatype'=>'date', 'arrayValue'=>$this->arrayValue); |
317
|
|
|
$values=Array('type'=>'date', 'parts'=>$parts); |
318
|
|
|
DB::require_field($this->tableName, $this->name, $values); |
|
|
|
|
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* Returns true if date is in the past. |
323
|
|
|
* @return boolean |
324
|
|
|
*/ |
325
|
|
|
public function InPast() { |
326
|
|
|
return strtotime($this->value) < SS_Datetime::now()->Format('U'); |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
/** |
330
|
|
|
* Returns true if date is in the future. |
331
|
|
|
* @return boolean |
332
|
|
|
*/ |
333
|
|
|
public function InFuture() { |
334
|
|
|
return strtotime($this->value) > SS_Datetime::now()->Format('U'); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* Returns true if date is today. |
339
|
|
|
* @return boolean |
340
|
|
|
*/ |
341
|
|
|
public function IsToday() { |
342
|
|
|
return (date('Y-m-d', strtotime($this->value)) == SS_Datetime::now()->Format('Y-m-d')); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Returns a date suitable for insertion into a URL and use by the system. |
347
|
|
|
*/ |
348
|
|
|
public function URLDate() { |
349
|
|
|
return date('Y-m-d', strtotime($this->value)); |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
|
353
|
|
|
public function days_between($fyear, $fmonth, $fday, $tyear, $tmonth, $tday){ |
354
|
|
|
return abs((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, $tmonth, $tday, $tyear))/(60*60*24)); |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
public function day_before($fyear, $fmonth, $fday){ |
358
|
|
|
return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-1,$fyear)); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
public function next_day($fyear, $fmonth, $fday){ |
362
|
|
|
return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday+1,$fyear)); |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
public function weekday($fyear, $fmonth, $fday){ // 0 is a Monday |
366
|
|
|
return (((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, 7, 17, 2006))/(60*60*24))+700000) % 7; |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
public function prior_monday($fyear, $fmonth, $fday){ |
370
|
|
|
return date ("Y-m-d", mktime (0,0,0,$fmonth,$fday-$this->weekday($fyear, $fmonth, $fday),$fyear)); |
371
|
|
|
} |
372
|
|
|
|
373
|
|
|
/** |
374
|
|
|
* Return the nearest date in the past, based on day and month. |
375
|
|
|
* Automatically attaches the correct year. |
376
|
|
|
* |
377
|
|
|
* This is useful for determining a financial year start or end date. |
378
|
|
|
* |
379
|
|
|
* @param $fmonth int The number of the month (e.g. 3 is March, 4 is April) |
380
|
|
|
* @param $fday int The day of the month |
381
|
|
|
* @param $fyear int Determine historical value |
382
|
|
|
* @return string Date in YYYY-MM-DD format |
383
|
|
|
*/ |
384
|
|
|
public static function past_date($fmonth, $fday = 1, $fyear = null) { |
385
|
|
|
if(!$fyear) $fyear = date('Y'); |
386
|
|
|
$fday = (int) $fday; |
387
|
|
|
$fmonth = (int) $fmonth; |
388
|
|
|
$fyear = (int) $fyear; |
389
|
|
|
|
390
|
|
|
$pastDate = mktime(0, 0, 0, $fmonth, $fday, $fyear); |
391
|
|
|
$curDate = mktime(0, 0, 0, date('m'), date('d'), $fyear); |
392
|
|
|
|
393
|
|
|
if($pastDate < $curDate) { |
394
|
|
|
return date('Y-m-d', mktime(0, 0, 0, $fmonth, $fday, $fyear)); |
395
|
|
|
} else { |
396
|
|
|
return date('Y-m-d', mktime(0, 0, 0, $fmonth, $fday, $fyear - 1)); |
397
|
|
|
} |
398
|
|
|
} |
399
|
|
|
|
400
|
|
View Code Duplication |
public function scaffoldFormField($title = null, $params = null) { |
|
|
|
|
401
|
|
|
$field = DateField::create($this->name, $title); |
402
|
|
|
|
403
|
|
|
// Show formatting hints for better usability |
404
|
|
|
$field->setDescription(sprintf( |
405
|
|
|
_t('FormField.Example', 'e.g. %s', 'Example format'), |
406
|
|
|
Convert::raw2xml(Zend_Date::now()->toString($field->getConfig('dateformat'))) |
407
|
|
|
)); |
408
|
|
|
$field->setAttribute('placeholder', $field->getConfig('dateformat')); |
409
|
|
|
|
410
|
|
|
return $field; |
411
|
|
|
} |
412
|
|
|
} |
413
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.