1
|
<?php
|
2
|
|
3
|
/**
|
4
|
* @file
|
5
|
* This module will make the date API available to other modules.
|
6
|
* Designed to provide a light but flexible assortment of functions
|
7
|
* and constants, with more functionality in additional files that
|
8
|
* are not loaded unless other modules specifically include them.
|
9
|
*/
|
10
|
|
11
|
/**
|
12
|
* Set up some constants.
|
13
|
*
|
14
|
* Includes standard date types, format strings, strict regex strings for ISO
|
15
|
* and DATETIME formats (seconds are optional).
|
16
|
*
|
17
|
* The loose regex will find any variety of ISO date and time, with or
|
18
|
* without time, with or without dashes and colons separating the elements,
|
19
|
* and with either a 'T' or a space separating date and time.
|
20
|
*/
|
21
|
define('DATE_ISO', 'date');
|
22
|
define('DATE_UNIX', 'datestamp');
|
23
|
define('DATE_DATETIME', 'datetime');
|
24
|
define('DATE_ARRAY', 'array');
|
25
|
define('DATE_OBJECT', 'object');
|
26
|
define('DATE_ICAL', 'ical');
|
27
|
|
28
|
define('DATE_FORMAT_ISO', "Y-m-d\TH:i:s");
|
29
|
define('DATE_FORMAT_UNIX', "U");
|
30
|
define('DATE_FORMAT_DATETIME', "Y-m-d H:i:s");
|
31
|
define('DATE_FORMAT_ICAL', "Ymd\THis");
|
32
|
define('DATE_FORMAT_ICAL_DATE', "Ymd");
|
33
|
define('DATE_FORMAT_DATE', 'Y-m-d');
|
34
|
|
35
|
define('DATE_REGEX_ISO', '/(\d{4})?(-(\d{2}))?(-(\d{2}))?([T\s](\d{2}))?(:(\d{2}))?(:(\d{2}))?/');
|
36
|
define('DATE_REGEX_DATETIME', '/(\d{4})-(\d{2})-(\d{2})\s(\d{2}):(\d{2}):?(\d{2})?/');
|
37
|
define('DATE_REGEX_LOOSE', '/(\d{4})-?(\d{1,2})-?(\d{1,2})([T\s]?(\d{2}):?(\d{2}):?(\d{2})?(\.\d+)?(Z|[\+\-]\d{2}:?\d{2})?)?/');
|
38
|
define('DATE_REGEX_ICAL_DATE', '/(\d{4})(\d{2})(\d{2})/');
|
39
|
define('DATE_REGEX_ICAL_DATETIME', '/(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z)?/');
|
40
|
|
41
|
/**
|
42
|
* Core DateTime extension module used for as many date operations as possible.
|
43
|
*/
|
44
|
|
45
|
/**
|
46
|
* Implements hook_help().
|
47
|
*/
|
48
|
function date_help($path, $arg) {
|
49
|
switch ($path) {
|
50
|
case 'admin/help#date':
|
51
|
$output = '';
|
52
|
$messages = date_api_status();
|
53
|
$output = '<h2>Date API Status</h2>';
|
54
|
if (!empty($messages['success'])) {
|
55
|
$output .= '<ul><li>' . implode('</li><li>', $messages['success']) . '</li></ul>';
|
56
|
}
|
57
|
if (!empty($messages['errors'])) {
|
58
|
$output .= '<h3>Errors</h3><ul class="error"><li>' . implode('</li><li>', $messages['errors']) . '</li></ul>';
|
59
|
}
|
60
|
|
61
|
if (module_exists('date_tools')) {
|
62
|
$output .= '<h3>Date Tools</h3>' . t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and with a date field.', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard')));
|
63
|
}
|
64
|
else {
|
65
|
$output .= '<h3>Date Tools</h3>' . t('Dates and calendars can be complicated to set up. If you enable the Date Tools module, it provides a Date Wizard that makes it easy to create a simple date content type with a date field.');
|
66
|
}
|
67
|
|
68
|
$output .= '<h2>More Information</h2><p>' . t('Complete documentation for the Date and Date API modules is available at <a href="@link">http://drupal.org/node/92460</a>.', array('@link' => 'http://drupal.org/node/262062')) . '</p>';
|
69
|
|
70
|
return $output;
|
71
|
|
72
|
}
|
73
|
}
|
74
|
|
75
|
/**
|
76
|
* Helper function to retun the status of required date variables.
|
77
|
*/
|
78
|
function date_api_status() {
|
79
|
$t = get_t();
|
80
|
|
81
|
$error_messages = array();
|
82
|
$success_messages = array();
|
83
|
|
84
|
$value = variable_get('date_default_timezone');
|
85
|
if (isset($value)) {
|
86
|
$success_messages[] = $t('The timezone has been set to <a href="@regional_settings">@timezone</a>.', array('@regional_settings' => url('admin/config/regional/settings'), '@timezone' => $value));
|
87
|
}
|
88
|
else {
|
89
|
$error_messages[] = $t('The Date API requires that you set up the <a href="@regional_settings">site timezone</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings')));
|
90
|
}
|
91
|
|
92
|
$value = variable_get('date_first_day');
|
93
|
if (isset($value)) {
|
94
|
$days = date_week_days();
|
95
|
$success_messages[] = $t('The first day of the week has been set to <a href="@regional_settings">@day</a>.', array('@regional_settings' => url('admin/config/regional/settings'), '@day' => $days[$value]));
|
96
|
}
|
97
|
else {
|
98
|
$error_messages[] = $t('The Date API requires that you set up the <a href="@regional_settings">site first day of week settings</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings')));
|
99
|
}
|
100
|
|
101
|
$value = variable_get('date_format_medium');
|
102
|
if (isset($value)) {
|
103
|
$now = date_now();
|
104
|
$success_messages[] = $t('The medium date format type has been set to @value. You may find it helpful to add new format types like Date, Time, Month, or Year, with appropriate formats, at <a href="@regional_date_time">Date and time</a> settings.', array('@value' => $now->format($value), '@regional_date_time' => url('admin/config/regional/date-time')));
|
105
|
}
|
106
|
else {
|
107
|
$error_messages[] = $t('The Date API requires that you set up the <a href="@regional_date_time">system date formats</a> to function correctly.', array('@regional_date_time' => url('admin/config/regional/date-time')));
|
108
|
}
|
109
|
|
110
|
return array('errors', $error_messages, 'success' => $success_messages);
|
111
|
|
112
|
}
|
113
|
|
114
|
/**
|
115
|
* Implements hook_menu().
|
116
|
*
|
117
|
* Creates a 'Date API' section on the administration page for Date
|
118
|
* modules to use for their configuration and settings.
|
119
|
*/
|
120
|
function date_api_menu() {
|
121
|
$items['admin/config/date'] = array(
|
122
|
'title' => 'Date API',
|
123
|
'description' => 'Settings for modules the use the Date API.',
|
124
|
'position' => 'left',
|
125
|
'weight' => -10,
|
126
|
'page callback' => 'system_admin_menu_block_page',
|
127
|
'access arguments' => array('administer site configuration'),
|
128
|
'file' => 'system.admin.inc',
|
129
|
'file path' => drupal_get_path('module', 'system'),
|
130
|
);
|
131
|
return $items;
|
132
|
}
|
133
|
|
134
|
/**
|
135
|
* Extend PHP DateTime class with granularity handling, merge functionality and
|
136
|
* slightly more flexible initialization parameters.
|
137
|
*
|
138
|
* This class is a Drupal independent extension of the >= PHP 5.2 DateTime
|
139
|
* class.
|
140
|
*
|
141
|
* @see FeedsDateTimeElement class
|
142
|
*/
|
143
|
class DateObject extends DateTime {
|
144
|
public $granularity = array();
|
145
|
public $errors = array();
|
146
|
protected static $allgranularity = array(
|
147
|
'year',
|
148
|
'month',
|
149
|
'day',
|
150
|
'hour',
|
151
|
'minute',
|
152
|
'second',
|
153
|
'timezone'
|
154
|
);
|
155
|
private $serializedTime;
|
156
|
private $serializedTimezone;
|
157
|
|
158
|
/**
|
159
|
* Prepares the object during serialization.
|
160
|
*
|
161
|
* We are extending a core class and core classes cannot be serialized.
|
162
|
*
|
163
|
* @return array
|
164
|
* Returns an array with the names of the variables that were serialized.
|
165
|
*
|
166
|
* @see http://bugs.php.net/41334
|
167
|
* @see http://bugs.php.net/39821
|
168
|
*/
|
169
|
public function __sleep() {
|
170
|
$this->serializedTime = $this->format('c');
|
171
|
$this->serializedTimezone = $this->getTimezone()->getName();
|
172
|
return array('serializedTime', 'serializedTimezone');
|
173
|
}
|
174
|
|
175
|
/**
|
176
|
* Re-builds the object using local variables.
|
177
|
*/
|
178
|
public function __wakeup() {
|
179
|
$this->__construct($this->serializedTime, new DateTimeZone($this->serializedTimezone));
|
180
|
}
|
181
|
|
182
|
/**
|
183
|
* Returns the date object as a string.
|
184
|
*
|
185
|
* @return string
|
186
|
* The date object formatted as a string.
|
187
|
*/
|
188
|
public function __toString() {
|
189
|
return $this->format(DATE_FORMAT_DATETIME) . ' ' . $this->getTimeZone()->getName();
|
190
|
}
|
191
|
|
192
|
/**
|
193
|
* Constructs a date object.
|
194
|
*
|
195
|
* @param string $time
|
196
|
* A date/time string or array. Defaults to 'now'.
|
197
|
* @param object|string|null $tz
|
198
|
* PHP DateTimeZone object, string or NULL allowed. Defaults to NULL.
|
199
|
* @param string $format
|
200
|
* PHP date() type format for parsing. Doesn't support timezones; if you
|
201
|
* have a timezone, send NULL and the default constructor method will
|
202
|
* hopefully parse it. $format is recommended in order to use negative or
|
203
|
* large years, which php's parser fails on.
|
204
|
*/
|
205
|
public function __construct($time = 'now', $tz = NULL, $format = NULL) {
|
206
|
$this->timeOnly = FALSE;
|
207
|
$this->dateOnly = FALSE;
|
208
|
|
209
|
// Store the raw time input so it is available for validation.
|
210
|
$this->originalTime = $time;
|
211
|
|
212
|
// Allow string timezones.
|
213
|
if (!empty($tz) && !is_object($tz)) {
|
214
|
$tz = new DateTimeZone($tz);
|
215
|
}
|
216
|
|
217
|
// Default to the site timezone when not explicitly provided.
|
218
|
elseif (empty($tz)) {
|
219
|
$tz = date_default_timezone_object();
|
220
|
}
|
221
|
// Special handling for Unix timestamps expressed in the local timezone.
|
222
|
// Create a date object in UTC and convert it to the local timezone. Don't
|
223
|
// try to turn things like '2010' with a format of 'Y' into a timestamp.
|
224
|
if (is_numeric($time) && (empty($format) || $format == 'U')) {
|
225
|
// Assume timestamp.
|
226
|
$time = "@" . $time;
|
227
|
$date = new DateObject($time, 'UTC');
|
228
|
if ($tz->getName() != 'UTC') {
|
229
|
$date->setTimezone($tz);
|
230
|
}
|
231
|
$time = $date->format(DATE_FORMAT_DATETIME);
|
232
|
$format = DATE_FORMAT_DATETIME;
|
233
|
$this->addGranularity('timezone');
|
234
|
}
|
235
|
elseif (is_array($time)) {
|
236
|
// Assume we were passed an indexed array.
|
237
|
if (empty($time['year']) && empty($time['month']) && empty($time['day'])) {
|
238
|
$this->timeOnly = TRUE;
|
239
|
}
|
240
|
if (empty($time['hour']) && empty($time['minute']) && empty($time['second'])) {
|
241
|
$this->dateOnly = TRUE;
|
242
|
}
|
243
|
$this->errors = $this->arrayErrors($time);
|
244
|
// Make this into an ISO date, forcing a full ISO date even if some values
|
245
|
// are missing.
|
246
|
$time = $this->toISO($time, TRUE);
|
247
|
// We checked for errors already, skip parsing the input values.
|
248
|
$format = NULL;
|
249
|
}
|
250
|
else {
|
251
|
// Make sure dates like 2010-00-00T00:00:00 get converted to
|
252
|
// 2010-01-01T00:00:00 before creating a date object
|
253
|
// to avoid unintended changes in the month or day.
|
254
|
$time = date_make_iso_valid($time);
|
255
|
}
|
256
|
|
257
|
// The parse function will also set errors on the date parts.
|
258
|
if (!empty($format)) {
|
259
|
$arg = self::$allgranularity;
|
260
|
$element = array_pop($arg);
|
261
|
while (!$this->parse($time, $tz, $format) && $element != 'year') {
|
262
|
$element = array_pop($arg);
|
263
|
$format = date_limit_format($format, $arg);
|
264
|
}
|
265
|
if ($element == 'year') {
|
266
|
return FALSE;
|
267
|
}
|
268
|
}
|
269
|
elseif (is_string($time)) {
|
270
|
// PHP < 5.3 doesn't like the GMT- notation for parsing timezones.
|
271
|
$time = str_replace("GMT-", "-", $time);
|
272
|
$time = str_replace("GMT+", "+", $time);
|
273
|
// We are going to let the parent dateObject do a best effort attempt to
|
274
|
// turn this string into a valid date. It might fail and we want to
|
275
|
// control the error messages.
|
276
|
try {
|
277
|
@parent::__construct($time, $tz);
|
278
|
}
|
279
|
catch (Exception $e) {
|
280
|
$this->errors['date'] = $e;
|
281
|
return;
|
282
|
}
|
283
|
if (empty($this->granularity)) {
|
284
|
$this->setGranularityFromTime($time, $tz);
|
285
|
}
|
286
|
}
|
287
|
|
288
|
// If we haven't got a valid timezone name yet, we need to set one or
|
289
|
// we will get undefined index errors.
|
290
|
// This can happen if $time had an offset or no timezone.
|
291
|
if (!$this->getTimezone() || !preg_match('/[a-zA-Z]/', $this->getTimezone()->getName())) {
|
292
|
|
293
|
// If the original $tz has a name, use it.
|
294
|
if (preg_match('/[a-zA-Z]/', $tz->getName())) {
|
295
|
$this->setTimezone($tz);
|
296
|
}
|
297
|
// We have no information about the timezone so must fallback to a default.
|
298
|
else {
|
299
|
$this->setTimezone(new DateTimeZone("UTC"));
|
300
|
$this->errors['timezone'] = t('No valid timezone name was provided.');
|
301
|
}
|
302
|
}
|
303
|
}
|
304
|
|
305
|
/**
|
306
|
* Merges two date objects together using the current date values as defaults.
|
307
|
*
|
308
|
* @param object $other
|
309
|
* Another date object to merge with.
|
310
|
*
|
311
|
* @return object
|
312
|
* A merged date object.
|
313
|
*/
|
314
|
public function merge(FeedsDateTime $other) {
|
315
|
$other_tz = $other->getTimezone();
|
316
|
$this_tz = $this->getTimezone();
|
317
|
// Figure out which timezone to use for combination.
|
318
|
$use_tz = ($this->hasGranularity('timezone') || !$other->hasGranularity('timezone')) ? $this_tz : $other_tz;
|
319
|
|
320
|
$this2 = clone $this;
|
321
|
$this2->setTimezone($use_tz);
|
322
|
$other->setTimezone($use_tz);
|
323
|
$val = $this2->toArray(TRUE);
|
324
|
$otherval = $other->toArray();
|
325
|
foreach (self::$allgranularity as $g) {
|
326
|
if ($other->hasGranularity($g) && !$this2->hasGranularity($g)) {
|
327
|
// The other class has a property we don't; steal it.
|
328
|
$this2->addGranularity($g);
|
329
|
$val[$g] = $otherval[$g];
|
330
|
}
|
331
|
}
|
332
|
$other->setTimezone($other_tz);
|
333
|
|
334
|
$this2->setDate($val['year'], $val['month'], $val['day']);
|
335
|
$this2->setTime($val['hour'], $val['minute'], $val['second']);
|
336
|
return $this2;
|
337
|
}
|
338
|
|
339
|
/**
|
340
|
* Sets the time zone for the current date.
|
341
|
*
|
342
|
* Overrides default DateTime function. Only changes output values if
|
343
|
* actually had time granularity. This should be used as a "converter" for
|
344
|
* output, to switch tzs.
|
345
|
*
|
346
|
* In order to set a timezone for a datetime that doesn't have such
|
347
|
* granularity, merge() it with one that does.
|
348
|
*
|
349
|
* @param object $tz
|
350
|
* A timezone object.
|
351
|
* @param bool $force
|
352
|
* Whether or not to skip a date with no time. Defaults to FALSE.
|
353
|
*/
|
354
|
public function setTimezone($tz, $force = FALSE) {
|
355
|
// PHP 5.2.6 has a fatal error when setting a date's timezone to itself.
|
356
|
// http://bugs.php.net/bug.php?id=45038
|
357
|
if (version_compare(PHP_VERSION, '5.2.7', '<') && $tz == $this->getTimezone()) {
|
358
|
$tz = new DateTimeZone($tz->getName());
|
359
|
}
|
360
|
|
361
|
if (!$this->hasTime() || !$this->hasGranularity('timezone') || $force) {
|
362
|
// This has no time or timezone granularity, so timezone doesn't mean
|
363
|
// much. We set the timezone using the method, which will change the
|
364
|
// day/hour, but then we switch back.
|
365
|
$arr = $this->toArray(TRUE);
|
366
|
parent::setTimezone($tz);
|
367
|
$this->setDate($arr['year'], $arr['month'], $arr['day']);
|
368
|
$this->setTime($arr['hour'], $arr['minute'], $arr['second']);
|
369
|
$this->addGranularity('timezone');
|
370
|
return;
|
371
|
}
|
372
|
return parent::setTimezone($tz);
|
373
|
}
|
374
|
|
375
|
/**
|
376
|
* Returns date formatted according to given format.
|
377
|
*
|
378
|
* Overrides base format function, formats this date according to its
|
379
|
* available granularity, unless $force'ed not to limit to granularity.
|
380
|
*
|
381
|
* @TODO Add translation into this so translated names will be provided.
|
382
|
*
|
383
|
* @param string $format
|
384
|
* A date format string.
|
385
|
* @param bool $force
|
386
|
* Whether or not to limit the granularity. Defaults to FALSE.
|
387
|
*
|
388
|
* @return string|false
|
389
|
* Returns the formatted date string on success or FALSE on failure.
|
390
|
*/
|
391
|
public function format($format, $force = FALSE) {
|
392
|
return parent::format($force ? $format : date_limit_format($format, $this->granularity));
|
393
|
}
|
394
|
|
395
|
/**
|
396
|
* Adds a granularity entry to the array.
|
397
|
*
|
398
|
* @param string $g
|
399
|
* A single date part.
|
400
|
*/
|
401
|
public function addGranularity($g) {
|
402
|
$this->granularity[] = $g;
|
403
|
$this->granularity = array_unique($this->granularity);
|
404
|
}
|
405
|
|
406
|
/**
|
407
|
* Removes a granularity entry from the array.
|
408
|
*
|
409
|
* @param string $g
|
410
|
* A single date part.
|
411
|
*/
|
412
|
public function removeGranularity($g) {
|
413
|
if (($key = array_search($g, $this->granularity)) !== FALSE) {
|
414
|
unset($this->granularity[$key]);
|
415
|
}
|
416
|
}
|
417
|
|
418
|
/**
|
419
|
* Checks granularity array for a given entry.
|
420
|
*
|
421
|
* @param array|null $g
|
422
|
* An array of date parts. Defaults to NULL.
|
423
|
*
|
424
|
* @returns bool
|
425
|
* TRUE if the date part is present in the date's granularity.
|
426
|
*/
|
427
|
public function hasGranularity($g = NULL) {
|
428
|
if ($g === NULL) {
|
429
|
// Just want to know if it has something valid means no lower
|
430
|
// granularities without higher ones.
|
431
|
$last = TRUE;
|
432
|
foreach (self::$allgranularity as $arg) {
|
433
|
if ($arg == 'timezone') {
|
434
|
continue;
|
435
|
}
|
436
|
if (in_array($arg, $this->granularity) && !$last) {
|
437
|
return FALSE;
|
438
|
}
|
439
|
$last = in_array($arg, $this->granularity);
|
440
|
}
|
441
|
return in_array('year', $this->granularity);
|
442
|
}
|
443
|
if (is_array($g)) {
|
444
|
foreach ($g as $gran) {
|
445
|
if (!in_array($gran, $this->granularity)) {
|
446
|
return FALSE;
|
447
|
}
|
448
|
}
|
449
|
return TRUE;
|
450
|
}
|
451
|
return in_array($g, $this->granularity);
|
452
|
}
|
453
|
|
454
|
/**
|
455
|
* Determines if a a date is valid for a given granularity.
|
456
|
*
|
457
|
* @param array|null $granularity
|
458
|
* An array of date parts. Defaults to NULL.
|
459
|
* @param bool $flexible
|
460
|
* TRUE if the granuliarty is flexible, FALSE otherwise. Defaults to FALSE.
|
461
|
*
|
462
|
* @return bool
|
463
|
* Whether a date is valid for a given granularity.
|
464
|
*/
|
465
|
public function validGranularity($granularity = NULL, $flexible = FALSE) {
|
466
|
$true = $this->hasGranularity() && (!$granularity || $flexible || $this->hasGranularity($granularity));
|
467
|
if (!$true && $granularity) {
|
468
|
foreach ((array) $granularity as $part) {
|
469
|
if (!$this->hasGranularity($part) && in_array($part, array(
|
470
|
'second',
|
471
|
'minute',
|
472
|
'hour',
|
473
|
'day',
|
474
|
'month',
|
475
|
'year')
|
476
|
)) {
|
477
|
switch ($part) {
|
478
|
case 'second':
|
479
|
$this->errors[$part] = t('The second is missing.');
|
480
|
break;
|
481
|
|
482
|
case 'minute':
|
483
|
$this->errors[$part] = t('The minute is missing.');
|
484
|
break;
|
485
|
|
486
|
case 'hour':
|
487
|
$this->errors[$part] = t('The hour is missing.');
|
488
|
break;
|
489
|
|
490
|
case 'day':
|
491
|
$this->errors[$part] = t('The day is missing.');
|
492
|
break;
|
493
|
|
494
|
case 'month':
|
495
|
$this->errors[$part] = t('The month is missing.');
|
496
|
break;
|
497
|
|
498
|
case 'year':
|
499
|
$this->errors[$part] = t('The year is missing.');
|
500
|
break;
|
501
|
}
|
502
|
}
|
503
|
}
|
504
|
}
|
505
|
return $true;
|
506
|
}
|
507
|
|
508
|
/**
|
509
|
* Returns whether this object has time set.
|
510
|
*
|
511
|
* Used primarily for timezone conversion and formatting.
|
512
|
*
|
513
|
* @return bool
|
514
|
* TRUE if the date contains time parts, FALSE otherwise.
|
515
|
*/
|
516
|
public function hasTime() {
|
517
|
return $this->hasGranularity('hour');
|
518
|
}
|
519
|
|
520
|
/**
|
521
|
* Returns whether the input values included a year.
|
522
|
*
|
523
|
* Useful to use pseudo date objects when we only are interested in the time.
|
524
|
*
|
525
|
* @todo $this->completeDate does not actually exist?
|
526
|
*/
|
527
|
public function completeDate() {
|
528
|
return $this->completeDate;
|
529
|
}
|
530
|
|
531
|
/**
|
532
|
* Removes unwanted date parts from a date.
|
533
|
*
|
534
|
* In common usage we should not unset timezone through this.
|
535
|
*
|
536
|
* @param array $granularity
|
537
|
* An array of date parts.
|
538
|
*/
|
539
|
public function limitGranularity($granularity) {
|
540
|
foreach ($this->granularity as $key => $val) {
|
541
|
if ($val != 'timezone' && !in_array($val, $granularity)) {
|
542
|
unset($this->granularity[$key]);
|
543
|
}
|
544
|
}
|
545
|
}
|
546
|
|
547
|
/**
|
548
|
* Determines the granularity of a date based on the constructor's arguments.
|
549
|
*
|
550
|
* @param string $time
|
551
|
* A date string.
|
552
|
* @param bool $tz
|
553
|
* TRUE if the date has a timezone, FALSE otherwise.
|
554
|
*/
|
555
|
protected function setGranularityFromTime($time, $tz) {
|
556
|
$this->granularity = array();
|
557
|
$temp = date_parse($time);
|
558
|
// Special case for 'now'.
|
559
|
if ($time == 'now') {
|
560
|
$this->granularity = array(
|
561
|
'year',
|
562
|
'month',
|
563
|
'day',
|
564
|
'hour',
|
565
|
'minute',
|
566
|
'second',
|
567
|
);
|
568
|
}
|
569
|
else {
|
570
|
// This PHP date_parse() method currently doesn't have resolution down to
|
571
|
// seconds, so if there is some time, all will be set.
|
572
|
foreach (self::$allgranularity as $g) {
|
573
|
if ((isset($temp[$g]) && is_numeric($temp[$g])) || ($g == 'timezone' && (isset($temp['zone_type']) && $temp['zone_type'] > 0))) {
|
574
|
$this->granularity[] = $g;
|
575
|
}
|
576
|
}
|
577
|
}
|
578
|
if ($tz) {
|
579
|
$this->addGranularity('timezone');
|
580
|
}
|
581
|
}
|
582
|
|
583
|
/**
|
584
|
* Converts a date string into a date object.
|
585
|
*
|
586
|
* @param string $date
|
587
|
* The date string to parse.
|
588
|
* @param object $tz
|
589
|
* A timezone object.
|
590
|
* @param string $format
|
591
|
* The date format string.
|
592
|
*
|
593
|
* @return object
|
594
|
* Returns the date object.
|
595
|
*/
|
596
|
protected function parse($date, $tz, $format) {
|
597
|
$array = date_format_patterns();
|
598
|
foreach ($array as $key => $value) {
|
599
|
// The letter with no preceding '\'.
|
600
|
$patterns[] = "`(^|[^\\\\\\\\])" . $key . "`";
|
601
|
// A single character.
|
602
|
$repl1[] = '${1}(.)';
|
603
|
// The.
|
604
|
$repl2[] = '${1}(' . $value . ')';
|
605
|
}
|
606
|
$patterns[] = "`\\\\\\\\([" . implode(array_keys($array)) . "])`";
|
607
|
$repl1[] = '${1}';
|
608
|
$repl2[] = '${1}';
|
609
|
|
610
|
$format_regexp = preg_quote($format);
|
611
|
|
612
|
// Extract letters.
|
613
|
$regex1 = preg_replace($patterns, $repl1, $format_regexp, 1);
|
614
|
$regex1 = str_replace('A', '(.)', $regex1);
|
615
|
$regex1 = str_replace('a', '(.)', $regex1);
|
616
|
preg_match('`^' . $regex1 . '$`', stripslashes($format), $letters);
|
617
|
array_shift($letters);
|
618
|
// Extract values.
|
619
|
$regex2 = preg_replace($patterns, $repl2, $format_regexp, 1);
|
620
|
$regex2 = str_replace('A', '(AM|PM)', $regex2);
|
621
|
$regex2 = str_replace('a', '(am|pm)', $regex2);
|
622
|
preg_match('`^' . $regex2 . '$`u', $date, $values);
|
623
|
array_shift($values);
|
624
|
// If we did not find all the values for the patterns in the format, abort.
|
625
|
if (count($letters) != count($values)) {
|
626
|
$this->errors['invalid'] = t('The value @date does not match the expected format.', array('@date' => $date));
|
627
|
return FALSE;
|
628
|
}
|
629
|
$this->granularity = array();
|
630
|
$final_date = array(
|
631
|
'hour' => 0,
|
632
|
'minute' => 0,
|
633
|
'second' => 0,
|
634
|
'month' => 1,
|
635
|
'day' => 1,
|
636
|
'year' => 0,
|
637
|
);
|
638
|
foreach ($letters as $i => $letter) {
|
639
|
$value = $values[$i];
|
640
|
switch ($letter) {
|
641
|
case 'd':
|
642
|
case 'j':
|
643
|
$final_date['day'] = intval($value);
|
644
|
$this->addGranularity('day');
|
645
|
break;
|
646
|
|
647
|
case 'n':
|
648
|
case 'm':
|
649
|
$final_date['month'] = intval($value);
|
650
|
$this->addGranularity('month');
|
651
|
break;
|
652
|
|
653
|
case 'F':
|
654
|
$array_month_long = array_flip(date_month_names());
|
655
|
$final_date['month'] = array_key_exists($value, $array_month_long) ? $array_month_long[$value] : -1;
|
656
|
$this->addGranularity('month');
|
657
|
break;
|
658
|
|
659
|
case 'M':
|
660
|
$array_month = array_flip(date_month_names_abbr());
|
661
|
$final_date['month'] = array_key_exists($value, $array_month) ? $array_month[$value] : -1;
|
662
|
$this->addGranularity('month');
|
663
|
break;
|
664
|
|
665
|
case 'Y':
|
666
|
$final_date['year'] = $value;
|
667
|
$this->addGranularity('year');
|
668
|
if (strlen($value) < 4) {
|
669
|
$this->errors['year'] = t('The year is invalid. Please check that entry includes four digits.');
|
670
|
}
|
671
|
break;
|
672
|
|
673
|
case 'y':
|
674
|
$year = $value;
|
675
|
// If no century, we add the current one ("06" => "2006").
|
676
|
$final_date['year'] = str_pad($year, 4, substr(date("Y"), 0, 2), STR_PAD_LEFT);
|
677
|
$this->addGranularity('year');
|
678
|
break;
|
679
|
|
680
|
case 'a':
|
681
|
case 'A':
|
682
|
$ampm = strtolower($value);
|
683
|
break;
|
684
|
|
685
|
case 'g':
|
686
|
case 'h':
|
687
|
case 'G':
|
688
|
case 'H':
|
689
|
$final_date['hour'] = intval($value);
|
690
|
$this->addGranularity('hour');
|
691
|
break;
|
692
|
|
693
|
case 'i':
|
694
|
$final_date['minute'] = intval($value);
|
695
|
$this->addGranularity('minute');
|
696
|
break;
|
697
|
|
698
|
case 's':
|
699
|
$final_date['second'] = intval($value);
|
700
|
$this->addGranularity('second');
|
701
|
break;
|
702
|
|
703
|
case 'U':
|
704
|
parent::__construct($value, $tz ? $tz : new DateTimeZone("UTC"));
|
705
|
$this->addGranularity('year');
|
706
|
$this->addGranularity('month');
|
707
|
$this->addGranularity('day');
|
708
|
$this->addGranularity('hour');
|
709
|
$this->addGranularity('minute');
|
710
|
$this->addGranularity('second');
|
711
|
return $this;
|
712
|
|
713
|
}
|
714
|
}
|
715
|
if (isset($ampm) && $ampm == 'pm' && $final_date['hour'] < 12) {
|
716
|
$final_date['hour'] += 12;
|
717
|
}
|
718
|
elseif (isset($ampm) && $ampm == 'am' && $final_date['hour'] == 12) {
|
719
|
$final_date['hour'] -= 12;
|
720
|
}
|
721
|
|
722
|
// Blank becomes current time, given TZ.
|
723
|
parent::__construct('', $tz ? $tz : new DateTimeZone("UTC"));
|
724
|
if ($tz) {
|
725
|
$this->addGranularity('timezone');
|
726
|
}
|
727
|
|
728
|
// SetDate expects an integer value for the year, results can be unexpected
|
729
|
// if we feed it something like '0100' or '0000'.
|
730
|
$final_date['year'] = intval($final_date['year']);
|
731
|
|
732
|
$this->errors += $this->arrayErrors($final_date);
|
733
|
$granularity = drupal_map_assoc($this->granularity);
|
734
|
|
735
|
// If the input value is '0000-00-00', PHP's date class will later
|
736
|
// incorrectly convert it to something like '-0001-11-30' if we do setDate()
|
737
|
// here. If we don't do setDate() here, it will default to the current date
|
738
|
// and we will lose any way to tell that there was no date in the orignal
|
739
|
// input values. So set a flag we can use later to tell that this date
|
740
|
// object was created using only time values, and that the date values are
|
741
|
// artifical.
|
742
|
if (empty($final_date['year']) && empty($final_date['month']) && empty($final_date['day'])) {
|
743
|
$this->timeOnly = TRUE;
|
744
|
}
|
745
|
elseif (empty($this->errors)) {
|
746
|
// setDate() expects a valid year, month, and day.
|
747
|
// Set some defaults for dates that don't use this to
|
748
|
// keep PHP from interpreting it as the last day of
|
749
|
// the previous month or last month of the previous year.
|
750
|
if (empty($granularity['month'])) {
|
751
|
$final_date['month'] = 1;
|
752
|
}
|
753
|
if (empty($granularity['day'])) {
|
754
|
$final_date['day'] = 1;
|
755
|
}
|
756
|
$this->setDate($final_date['year'], $final_date['month'], $final_date['day']);
|
757
|
}
|
758
|
|
759
|
if (!isset($final_date['hour']) && !isset($final_date['minute']) && !isset($final_date['second'])) {
|
760
|
$this->dateOnly = TRUE;
|
761
|
}
|
762
|
elseif (empty($this->errors)) {
|
763
|
$this->setTime($final_date['hour'], $final_date['minute'], $final_date['second']);
|
764
|
}
|
765
|
return $this;
|
766
|
}
|
767
|
|
768
|
/**
|
769
|
* Returns all standard date parts in an array.
|
770
|
*
|
771
|
* Will return '' for parts in which it lacks granularity.
|
772
|
*
|
773
|
* @param bool $force
|
774
|
* Whether or not to limit the granularity. Defaults to FALSE.
|
775
|
*
|
776
|
* @return array
|
777
|
* An array of formatted date part values, keyed by date parts.
|
778
|
*/
|
779
|
public function toArray($force = FALSE) {
|
780
|
return array(
|
781
|
'year' => $this->format('Y', $force),
|
782
|
'month' => $this->format('n', $force),
|
783
|
'day' => $this->format('j', $force),
|
784
|
'hour' => intval($this->format('H', $force)),
|
785
|
'minute' => intval($this->format('i', $force)),
|
786
|
'second' => intval($this->format('s', $force)),
|
787
|
'timezone' => $this->format('e', $force),
|
788
|
);
|
789
|
}
|
790
|
|
791
|
/**
|
792
|
* Creates an ISO date from an array of values.
|
793
|
*
|
794
|
* @param array $arr
|
795
|
* An array of date values keyed by date part.
|
796
|
* @param bool $full
|
797
|
* (optional) Whether to force a full date by filling in missing values.
|
798
|
* Defaults to FALSE.
|
799
|
*/
|
800
|
public function toISO($arr, $full = FALSE) {
|
801
|
// Add empty values to avoid errors. The empty values must create a valid
|
802
|
// date or we will get date slippage, i.e. a value of 2011-00-00 will get
|
803
|
// interpreted as November of 2010 by PHP.
|
804
|
if ($full) {
|
805
|
$arr += array(
|
806
|
'year' => 0,
|
807
|
'month' => 1,
|
808
|
'day' => 1,
|
809
|
'hour' => 0,
|
810
|
'minute' => 0,
|
811
|
'second' => 0,
|
812
|
);
|
813
|
}
|
814
|
else {
|
815
|
$arr += array(
|
816
|
'year' => '',
|
817
|
'month' => '',
|
818
|
'day' => '',
|
819
|
'hour' => '',
|
820
|
'minute' => '',
|
821
|
'second' => '',
|
822
|
);
|
823
|
}
|
824
|
$datetime = '';
|
825
|
if ($arr['year'] !== '') {
|
826
|
$datetime = date_pad(intval($arr['year']), 4);
|
827
|
if ($full || $arr['month'] !== '') {
|
828
|
$datetime .= '-' . date_pad(intval($arr['month']));
|
829
|
if ($full || $arr['day'] !== '') {
|
830
|
$datetime .= '-' . date_pad(intval($arr['day']));
|
831
|
}
|
832
|
}
|
833
|
}
|
834
|
if ($arr['hour'] !== '') {
|
835
|
$datetime .= $datetime ? 'T' : '';
|
836
|
$datetime .= date_pad(intval($arr['hour']));
|
837
|
if ($full || $arr['minute'] !== '') {
|
838
|
$datetime .= ':' . date_pad(intval($arr['minute']));
|
839
|
if ($full || $arr['second'] !== '') {
|
840
|
$datetime .= ':' . date_pad(intval($arr['second']));
|
841
|
}
|
842
|
}
|
843
|
}
|
844
|
return $datetime;
|
845
|
}
|
846
|
|
847
|
/**
|
848
|
* Forces an incomplete date to be valid.
|
849
|
*
|
850
|
* E.g., add a valid year, month, and day if only the time has been defined.
|
851
|
*
|
852
|
* @param array|string $date
|
853
|
* An array of date parts or a datetime string with values to be massaged
|
854
|
* into a valid date object.
|
855
|
* @param string $format
|
856
|
* (optional) The format of the date. Defaults to NULL.
|
857
|
* @param string $default
|
858
|
* (optional) If the fallback should use the first value of the date part,
|
859
|
* or the current value of the date part. Defaults to 'first'.
|
860
|
*/
|
861
|
public function setFuzzyDate($date, $format = NULL, $default = 'first') {
|
862
|
$timezone = $this->getTimeZone() ? $this->getTimeZone()->getName() : NULL;
|
863
|
$comp = new DateObject($date, $timezone, $format);
|
864
|
$arr = $comp->toArray(TRUE);
|
865
|
foreach ($arr as $key => $value) {
|
866
|
// Set to intval here and then test that it is still an integer.
|
867
|
// Needed because sometimes valid integers come through as strings.
|
868
|
$arr[$key] = $this->forceValid($key, intval($value), $default, $arr['month'], $arr['year']);
|
869
|
}
|
870
|
$this->setDate($arr['year'], $arr['month'], $arr['day']);
|
871
|
$this->setTime($arr['hour'], $arr['minute'], $arr['second']);
|
872
|
}
|
873
|
|
874
|
/**
|
875
|
* Converts a date part into something that will produce a valid date.
|
876
|
*
|
877
|
* @param string $part
|
878
|
* The date part.
|
879
|
* @param int $value
|
880
|
* The date value for this part.
|
881
|
* @param string $default
|
882
|
* (optional) If the fallback should use the first value of the date part,
|
883
|
* or the current value of the date part. Defaults to 'first'.
|
884
|
* @param int $month
|
885
|
* (optional) Used when the date part is less than 'month' to specify the
|
886
|
* date. Defaults to NULL.
|
887
|
* @param int $year
|
888
|
* (optional) Used when the date part is less than 'year' to specify the
|
889
|
* date. Defaults to NULL.
|
890
|
*
|
891
|
* @return int
|
892
|
* A valid date value.
|
893
|
*/
|
894
|
protected function forceValid($part, $value, $default = 'first', $month = NULL, $year = NULL) {
|
895
|
$now = date_now();
|
896
|
switch ($part) {
|
897
|
case 'year':
|
898
|
$fallback = $now->format('Y');
|
899
|
return !is_int($value) || empty($value) || $value < variable_get('date_min_year', 1) || $value > variable_get('date_max_year', 4000) ? $fallback : $value;
|
900
|
|
901
|
case 'month':
|
902
|
$fallback = $default == 'first' ? 1 : $now->format('n');
|
903
|
return !is_int($value) || empty($value) || $value <= 0 || $value > 12 ? $fallback : $value;
|
904
|
|
905
|
case 'day':
|
906
|
$fallback = $default == 'first' ? 1 : $now->format('j');
|
907
|
$max_day = isset($year) && isset($month) ? date_days_in_month($year, $month) : 31;
|
908
|
return !is_int($value) || empty($value) || $value <= 0 || $value > $max_day ? $fallback : $value;
|
909
|
|
910
|
case 'hour':
|
911
|
$fallback = $default == 'first' ? 0 : $now->format('G');
|
912
|
return !is_int($value) || $value < 0 || $value > 23 ? $fallback : $value;
|
913
|
|
914
|
case 'minute':
|
915
|
$fallback = $default == 'first' ? 0 : $now->format('i');
|
916
|
return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value;
|
917
|
|
918
|
case 'second':
|
919
|
$fallback = $default == 'first' ? 0 : $now->format('s');
|
920
|
return !is_int($value) || $value < 0 || $value > 59 ? $fallback : $value;
|
921
|
}
|
922
|
}
|
923
|
|
924
|
/**
|
925
|
* Finds possible errors in an array of date part values.
|
926
|
*
|
927
|
* The forceValid() function will change an invalid value to a valid one, so
|
928
|
* we just need to see if the value got altered.
|
929
|
*
|
930
|
* @param array $arr
|
931
|
* An array of date values, keyed by date part.
|
932
|
*
|
933
|
* @return array
|
934
|
* An array of error messages, keyed by date part.
|
935
|
*/
|
936
|
public function arrayErrors($arr) {
|
937
|
$errors = array();
|
938
|
$now = date_now();
|
939
|
$default_month = !empty($arr['month']) ? $arr['month'] : $now->format('n');
|
940
|
$default_year = !empty($arr['year']) ? $arr['year'] : $now->format('Y');
|
941
|
|
942
|
$this->granularity = array();
|
943
|
foreach ($arr as $part => $value) {
|
944
|
// Explicitly set the granularity to the values in the input array.
|
945
|
if (is_numeric($value)) {
|
946
|
$this->addGranularity($part);
|
947
|
}
|
948
|
// Avoid false errors when a numeric value is input as a string by casting
|
949
|
// as an integer.
|
950
|
$value = intval($value);
|
951
|
if (!empty($value) && $this->forceValid($part, $value, 'now', $default_month, $default_year) != $value) {
|
952
|
// Use a switch/case to make translation easier by providing a different
|
953
|
// message for each part.
|
954
|
switch ($part) {
|
955
|
case 'year':
|
956
|
$errors['year'] = t('The year is invalid.');
|
957
|
break;
|
958
|
|
959
|
case 'month':
|
960
|
$errors['month'] = t('The month is invalid.');
|
961
|
break;
|
962
|
|
963
|
case 'day':
|
964
|
$errors['day'] = t('The day is invalid.');
|
965
|
break;
|
966
|
|
967
|
case 'hour':
|
968
|
$errors['hour'] = t('The hour is invalid.');
|
969
|
break;
|
970
|
|
971
|
case 'minute':
|
972
|
$errors['minute'] = t('The minute is invalid.');
|
973
|
break;
|
974
|
|
975
|
case 'second':
|
976
|
$errors['second'] = t('The second is invalid.');
|
977
|
break;
|
978
|
}
|
979
|
}
|
980
|
}
|
981
|
if ($this->hasTime()) {
|
982
|
$this->addGranularity('timezone');
|
983
|
}
|
984
|
return $errors;
|
985
|
}
|
986
|
|
987
|
/**
|
988
|
* Computes difference between two days using a given measure.
|
989
|
*
|
990
|
* @param object $date2_in
|
991
|
* The stop date.
|
992
|
* @param string $measure
|
993
|
* (optional) A granularity date part. Defaults to 'seconds'.
|
994
|
* @param bool $absolute
|
995
|
* (optional) Indicate whether the absolute value of the difference should
|
996
|
* be returned or if the sign should be retained. Defaults to TRUE.
|
997
|
*/
|
998
|
public function difference($date2_in, $measure = 'seconds', $absolute = TRUE) {
|
999
|
// Create cloned objects or original dates will be impacted by the
|
1000
|
// date_modify() operations done in this code.
|
1001
|
$date1 = clone($this);
|
1002
|
$date2 = clone($date2_in);
|
1003
|
if (is_object($date1) && is_object($date2)) {
|
1004
|
$diff = date_format($date2, 'U') - date_format($date1, 'U');
|
1005
|
if ($diff == 0) {
|
1006
|
return 0;
|
1007
|
}
|
1008
|
elseif ($diff < 0 && $absolute) {
|
1009
|
// Make sure $date1 is the smaller date.
|
1010
|
$temp = $date2;
|
1011
|
$date2 = $date1;
|
1012
|
$date1 = $temp;
|
1013
|
$diff = date_format($date2, 'U') - date_format($date1, 'U');
|
1014
|
}
|
1015
|
$year_diff = intval(date_format($date2, 'Y') - date_format($date1, 'Y'));
|
1016
|
switch ($measure) {
|
1017
|
// The easy cases first.
|
1018
|
case 'seconds':
|
1019
|
return $diff;
|
1020
|
|
1021
|
case 'minutes':
|
1022
|
return $diff / 60;
|
1023
|
|
1024
|
case 'hours':
|
1025
|
return $diff / 3600;
|
1026
|
|
1027
|
case 'years':
|
1028
|
return $year_diff;
|
1029
|
|
1030
|
case 'months':
|
1031
|
$format = 'n';
|
1032
|
$item1 = date_format($date1, $format);
|
1033
|
$item2 = date_format($date2, $format);
|
1034
|
if ($year_diff == 0) {
|
1035
|
return intval($item2 - $item1);
|
1036
|
}
|
1037
|
elseif ($year_diff < 0) {
|
1038
|
$item_diff = 0 - $item1;
|
1039
|
$item_diff -= intval((abs($year_diff) - 1) * 12);
|
1040
|
return $item_diff - (12 - $item2);
|
1041
|
}
|
1042
|
else {
|
1043
|
$item_diff = 12 - $item1;
|
1044
|
$item_diff += intval(($year_diff - 1) * 12);
|
1045
|
return $item_diff + $item2;
|
1046
|
}
|
1047
|
break;
|
1048
|
|
1049
|
case 'days':
|
1050
|
$format = 'z';
|
1051
|
$item1 = date_format($date1, $format);
|
1052
|
$item2 = date_format($date2, $format);
|
1053
|
if ($year_diff == 0) {
|
1054
|
return intval($item2 - $item1);
|
1055
|
}
|
1056
|
elseif ($year_diff < 0) {
|
1057
|
$item_diff = 0 - $item1;
|
1058
|
for ($i = 1; $i < abs($year_diff); $i++) {
|
1059
|
date_modify($date1, '-1 year');
|
1060
|
$item_diff -= date_days_in_year($date1);
|
1061
|
}
|
1062
|
return $item_diff - (date_days_in_year($date2) - $item2);
|
1063
|
}
|
1064
|
else {
|
1065
|
$item_diff = date_days_in_year($date1) - $item1;
|
1066
|
for ($i = 1; $i < $year_diff; $i++) {
|
1067
|
date_modify($date1, '+1 year');
|
1068
|
$item_diff += date_days_in_year($date1);
|
1069
|
}
|
1070
|
return $item_diff + $item2;
|
1071
|
}
|
1072
|
break;
|
1073
|
|
1074
|
case 'weeks':
|
1075
|
$week_diff = date_format($date2, 'W') - date_format($date1, 'W');
|
1076
|
$year_diff = date_format($date2, 'o') - date_format($date1, 'o');
|
1077
|
|
1078
|
$sign = ($year_diff < 0) ? -1 : 1;
|
1079
|
|
1080
|
for ($i = 1; $i <= abs($year_diff); $i++) {
|
1081
|
date_modify($date1, (($sign > 0) ? '+' : '-') . '1 year');
|
1082
|
$week_diff += (date_iso_weeks_in_year($date1) * $sign);
|
1083
|
}
|
1084
|
return $week_diff;
|
1085
|
}
|
1086
|
}
|
1087
|
return NULL;
|
1088
|
}
|
1089
|
}
|
1090
|
|
1091
|
/**
|
1092
|
* Determines if the date element needs to be processed.
|
1093
|
*
|
1094
|
* Helper function to see if date element has been hidden by FAPI to see if it
|
1095
|
* needs to be processed or just pass the value through. This is needed since
|
1096
|
* normal date processing explands the date element into parts and then
|
1097
|
* reconstructs it, which is not needed or desirable if the field is hidden.
|
1098
|
*
|
1099
|
* @param array $element
|
1100
|
* The date element to check.
|
1101
|
*
|
1102
|
* @return bool
|
1103
|
* TRUE if the element is effectively hidden, FALSE otherwise.
|
1104
|
*/
|
1105
|
function date_hidden_element($element) {
|
1106
|
// @TODO What else needs to be tested to see if dates are hidden or disabled?
|
1107
|
if ((isset($element['#access']) && empty($element['#access']))
|
1108
|
|| !empty($element['#programmed'])
|
1109
|
|| in_array($element['#type'], array('hidden', 'value'))) {
|
1110
|
return TRUE;
|
1111
|
}
|
1112
|
return FALSE;
|
1113
|
}
|
1114
|
|
1115
|
/**
|
1116
|
* Helper function for getting the format string for a date type.
|
1117
|
*
|
1118
|
* @param string $type
|
1119
|
* A date type format name.
|
1120
|
*
|
1121
|
* @return string
|
1122
|
* A date type format, like 'Y-m-d H:i:s'.
|
1123
|
*/
|
1124
|
function date_type_format($type) {
|
1125
|
switch ($type) {
|
1126
|
case DATE_ISO:
|
1127
|
return DATE_FORMAT_ISO;
|
1128
|
|
1129
|
case DATE_UNIX:
|
1130
|
return DATE_FORMAT_UNIX;
|
1131
|
|
1132
|
case DATE_DATETIME:
|
1133
|
return DATE_FORMAT_DATETIME;
|
1134
|
|
1135
|
case DATE_ICAL:
|
1136
|
return DATE_FORMAT_ICAL;
|
1137
|
}
|
1138
|
}
|
1139
|
|
1140
|
/**
|
1141
|
* Constructs an untranslated array of month names.
|
1142
|
*
|
1143
|
* Needed for CSS, translation functions, strtotime(), and other places
|
1144
|
* that use the English versions of these words.
|
1145
|
*
|
1146
|
* @return array
|
1147
|
* An array of month names.
|
1148
|
*/
|
1149
|
function date_month_names_untranslated() {
|
1150
|
static $month_names;
|
1151
|
if (empty($month_names)) {
|
1152
|
$month_names = array(
|
1153
|
1 => 'January',
|
1154
|
2 => 'February',
|
1155
|
3 => 'March',
|
1156
|
4 => 'April',
|
1157
|
5 => 'May',
|
1158
|
6 => 'June',
|
1159
|
7 => 'July',
|
1160
|
8 => 'August',
|
1161
|
9 => 'September',
|
1162
|
10 => 'October',
|
1163
|
11 => 'November',
|
1164
|
12 => 'December',
|
1165
|
);
|
1166
|
}
|
1167
|
return $month_names;
|
1168
|
}
|
1169
|
|
1170
|
/**
|
1171
|
* Returns a translated array of month names.
|
1172
|
*
|
1173
|
* @param bool $required
|
1174
|
* (optional) If FALSE, the returned array will include a blank value.
|
1175
|
* Defaults to FALSE.
|
1176
|
*
|
1177
|
* @return array
|
1178
|
* An array of month names.
|
1179
|
*/
|
1180
|
function date_month_names($required = FALSE) {
|
1181
|
$month_names = array();
|
1182
|
foreach (date_month_names_untranslated() as $key => $month) {
|
1183
|
$month_names[$key] = t($month, array(), array('context' => 'Long month name'));
|
1184
|
}
|
1185
|
$none = array('' => '');
|
1186
|
return !$required ? $none + $month_names : $month_names;
|
1187
|
}
|
1188
|
|
1189
|
/**
|
1190
|
* Constructs a translated array of month name abbreviations.
|
1191
|
*
|
1192
|
* @param bool $required
|
1193
|
* (optional) If FALSE, the returned array will include a blank value.
|
1194
|
* Defaults to FALSE.
|
1195
|
* @param int $length
|
1196
|
* (optional) The length of the abbreviation. Defaults to 3.
|
1197
|
*
|
1198
|
* @return array
|
1199
|
* An array of month abbreviations.
|
1200
|
*/
|
1201
|
function date_month_names_abbr($required = FALSE, $length = 3) {
|
1202
|
$month_names = array();
|
1203
|
foreach (date_month_names_untranslated() as $key => $month) {
|
1204
|
if ($length == 3) {
|
1205
|
$month_names[$key] = t(substr($month, 0, $length), array());
|
1206
|
}
|
1207
|
else {
|
1208
|
$month_names[$key] = t(substr($month, 0, $length), array(), array('context' => 'month_abbr'));
|
1209
|
}
|
1210
|
}
|
1211
|
$none = array('' => '');
|
1212
|
return !$required ? $none + $month_names : $month_names;
|
1213
|
}
|
1214
|
|
1215
|
/**
|
1216
|
* Constructs an untranslated array of week days.
|
1217
|
*
|
1218
|
* Needed for CSS, translation functions, strtotime(), and other places
|
1219
|
* that use the English versions of these words.
|
1220
|
*
|
1221
|
* @param bool $refresh
|
1222
|
* (optional) Whether to refresh the list. Defaults to TRUE.
|
1223
|
*
|
1224
|
* @return array
|
1225
|
* An array of week day names
|
1226
|
*/
|
1227
|
function date_week_days_untranslated($refresh = TRUE) {
|
1228
|
static $weekdays;
|
1229
|
if ($refresh || empty($weekdays)) {
|
1230
|
$weekdays = array(
|
1231
|
'Sunday',
|
1232
|
'Monday',
|
1233
|
'Tuesday',
|
1234
|
'Wednesday',
|
1235
|
'Thursday',
|
1236
|
'Friday',
|
1237
|
'Saturday',
|
1238
|
);
|
1239
|
}
|
1240
|
return $weekdays;
|
1241
|
}
|
1242
|
|
1243
|
/**
|
1244
|
* Returns a translated array of week names.
|
1245
|
*
|
1246
|
* @param bool $required
|
1247
|
* (optional) If FALSE, the returned array will include a blank value.
|
1248
|
* Defaults to FALSE.
|
1249
|
*
|
1250
|
* @return array
|
1251
|
* An array of week day names
|
1252
|
*/
|
1253
|
function date_week_days($required = FALSE, $refresh = TRUE) {
|
1254
|
$weekdays = array();
|
1255
|
foreach (date_week_days_untranslated() as $key => $day) {
|
1256
|
$weekdays[$key] = t($day, array(), array('context' => ''));
|
1257
|
}
|
1258
|
$none = array('' => '');
|
1259
|
return !$required ? $none + $weekdays : $weekdays;
|
1260
|
}
|
1261
|
|
1262
|
/**
|
1263
|
* Constructs a translated array of week day abbreviations.
|
1264
|
*
|
1265
|
* @param bool $required
|
1266
|
* (optional) If FALSE, the returned array will include a blank value.
|
1267
|
* Defaults to FALSE.
|
1268
|
* @param bool $refresh
|
1269
|
* (optional) Whether to refresh the list. Defaults to TRUE.
|
1270
|
* @param int $length
|
1271
|
* (optional) The length of the abbreviation. Defaults to 3.
|
1272
|
*
|
1273
|
* @return array
|
1274
|
* An array of week day abbreviations
|
1275
|
*/
|
1276
|
function date_week_days_abbr($required = FALSE, $refresh = TRUE, $length = 3) {
|
1277
|
$weekdays = array();
|
1278
|
switch ($length) {
|
1279
|
case 1:
|
1280
|
$context = 'day_abbr1';
|
1281
|
break;
|
1282
|
|
1283
|
case 2:
|
1284
|
$context = 'day_abbr2';
|
1285
|
break;
|
1286
|
|
1287
|
default:
|
1288
|
$context = '';
|
1289
|
break;
|
1290
|
}
|
1291
|
foreach (date_week_days_untranslated() as $key => $day) {
|
1292
|
$weekdays[$key] = t(substr($day, 0, $length), array(), array('context' => $context));
|
1293
|
}
|
1294
|
$none = array('' => '');
|
1295
|
return !$required ? $none + $weekdays : $weekdays;
|
1296
|
}
|
1297
|
|
1298
|
/**
|
1299
|
* Reorders weekdays to match the first day of the week.
|
1300
|
*
|
1301
|
* @param array $weekdays
|
1302
|
* An array of weekdays.
|
1303
|
*
|
1304
|
* @return array
|
1305
|
* An array of weekdays reordered to match the first day of the week.
|
1306
|
*/
|
1307
|
function date_week_days_ordered($weekdays) {
|
1308
|
$first_day = variable_get('date_first_day', 0);
|
1309
|
if ($first_day > 0) {
|
1310
|
for ($i = 1; $i <= $first_day; $i++) {
|
1311
|
$last = array_shift($weekdays);
|
1312
|
array_push($weekdays, $last);
|
1313
|
}
|
1314
|
}
|
1315
|
return $weekdays;
|
1316
|
}
|
1317
|
|
1318
|
/**
|
1319
|
* Constructs an array of years.
|
1320
|
*
|
1321
|
* @param int $start
|
1322
|
* The start year in the array.
|
1323
|
* @param int $end
|
1324
|
* The end year in the array.
|
1325
|
* @param bool $required
|
1326
|
* (optional) If FALSE, the returned array will include a blank value.
|
1327
|
* Defaults to FALSE.
|
1328
|
*
|
1329
|
* @return array
|
1330
|
* An array of years in the selected range.
|
1331
|
*/
|
1332
|
function date_years($start = 0, $end = 0, $required = FALSE) {
|
1333
|
// Ensure $min and $max are valid values.
|
1334
|
if (empty($start)) {
|
1335
|
$start = intval(date('Y', REQUEST_TIME) - 3);
|
1336
|
}
|
1337
|
if (empty($end)) {
|
1338
|
$end = intval(date('Y', REQUEST_TIME) + 3);
|
1339
|
}
|
1340
|
$none = array(0 => '');
|
1341
|
return !$required ? $none + drupal_map_assoc(range($start, $end)) : drupal_map_assoc(range($start, $end));
|
1342
|
}
|
1343
|
|
1344
|
/**
|
1345
|
* Constructs an array of days in a month.
|
1346
|
*
|
1347
|
* @param bool $required
|
1348
|
* (optional) If FALSE, the returned array will include a blank value.
|
1349
|
* Defaults to FALSE.
|
1350
|
* @param int $month
|
1351
|
* (optional) The month in which to find the number of days.
|
1352
|
* @param int $year
|
1353
|
* (optional) The year in which to find the number of days.
|
1354
|
*
|
1355
|
* @return array
|
1356
|
* An array of days for the selected month.
|
1357
|
*/
|
1358
|
function date_days($required = FALSE, $month = NULL, $year = NULL) {
|
1359
|
// If we have a month and year, find the right last day of the month.
|
1360
|
if (!empty($month) && !empty($year)) {
|
1361
|
$date = new DateObject($year . '-' . $month . '-01 00:00:00', 'UTC');
|
1362
|
$max = $date->format('t');
|
1363
|
}
|
1364
|
// If there is no month and year given, default to 31.
|
1365
|
if (empty($max)) {
|
1366
|
$max = 31;
|
1367
|
}
|
1368
|
$none = array(0 => '');
|
1369
|
return !$required ? $none + drupal_map_assoc(range(1, $max)) : drupal_map_assoc(range(1, $max));
|
1370
|
}
|
1371
|
|
1372
|
/**
|
1373
|
* Constructs an array of hours.
|
1374
|
*
|
1375
|
* @param string $format
|
1376
|
* A date format string.
|
1377
|
* @param bool $required
|
1378
|
* (optional) If FALSE, the returned array will include a blank value.
|
1379
|
* Defaults to FALSE.
|
1380
|
*
|
1381
|
* @return array
|
1382
|
* An array of hours in the selected format.
|
1383
|
*/
|
1384
|
function date_hours($format = 'H', $required = FALSE) {
|
1385
|
$hours = array();
|
1386
|
if ($format == 'h' || $format == 'g') {
|
1387
|
$min = 1;
|
1388
|
$max = 12;
|
1389
|
}
|
1390
|
else {
|
1391
|
$min = 0;
|
1392
|
$max = 23;
|
1393
|
}
|
1394
|
for ($i = $min; $i <= $max; $i++) {
|
1395
|
$hours[$i] = $i < 10 && ($format == 'H' || $format == 'h') ? "0$i" : $i;
|
1396
|
}
|
1397
|
$none = array('' => '');
|
1398
|
return !$required ? $none + $hours : $hours;
|
1399
|
}
|
1400
|
|
1401
|
/**
|
1402
|
* Constructs an array of minutes.
|
1403
|
*
|
1404
|
* @param string $format
|
1405
|
* A date format string.
|
1406
|
* @param bool $required
|
1407
|
* (optional) If FALSE, the returned array will include a blank value.
|
1408
|
* Defaults to FALSE.
|
1409
|
*
|
1410
|
* @return array
|
1411
|
* An array of minutes in the selected format.
|
1412
|
*/
|
1413
|
function date_minutes($format = 'i', $required = FALSE, $increment = 1) {
|
1414
|
$minutes = array();
|
1415
|
// Ensure $increment has a value so we don't loop endlessly.
|
1416
|
if (empty($increment)) {
|
1417
|
$increment = 1;
|
1418
|
}
|
1419
|
for ($i = 0; $i < 60; $i += $increment) {
|
1420
|
$minutes[$i] = $i < 10 && $format == 'i' ? "0$i" : $i;
|
1421
|
}
|
1422
|
$none = array('' => '');
|
1423
|
return !$required ? $none + $minutes : $minutes;
|
1424
|
}
|
1425
|
|
1426
|
/**
|
1427
|
* Constructs an array of seconds.
|
1428
|
*
|
1429
|
* @param string $format
|
1430
|
* A date format string.
|
1431
|
* @param bool $required
|
1432
|
* (optional) If FALSE, the returned array will include a blank value.
|
1433
|
* Defaults to FALSE.
|
1434
|
*
|
1435
|
* @return array
|
1436
|
* An array of seconds in the selected format.
|
1437
|
*/
|
1438
|
function date_seconds($format = 's', $required = FALSE, $increment = 1) {
|
1439
|
$seconds = array();
|
1440
|
// Ensure $increment has a value so we don't loop endlessly.
|
1441
|
if (empty($increment)) {
|
1442
|
$increment = 1;
|
1443
|
}
|
1444
|
for ($i = 0; $i < 60; $i += $increment) {
|
1445
|
$seconds[$i] = $i < 10 && $format == 's' ? "0$i" : $i;
|
1446
|
}
|
1447
|
$none = array('' => '');
|
1448
|
return !$required ? $none + $seconds : $seconds;
|
1449
|
}
|
1450
|
|
1451
|
/**
|
1452
|
* Constructs an array of AM and PM options.
|
1453
|
*
|
1454
|
* @param bool $required
|
1455
|
* (optional) If FALSE, the returned array will include a blank value.
|
1456
|
* Defaults to FALSE.
|
1457
|
*
|
1458
|
* @return array
|
1459
|
* An array of AM and PM options.
|
1460
|
*/
|
1461
|
function date_ampm($required = FALSE) {
|
1462
|
$none = array('' => '');
|
1463
|
$ampm = array(
|
1464
|
'am' => t('am', array(), array('context' => 'ampm')),
|
1465
|
'pm' => t('pm', array(), array('context' => 'ampm')),
|
1466
|
);
|
1467
|
return !$required ? $none + $ampm : $ampm;
|
1468
|
}
|
1469
|
|
1470
|
/**
|
1471
|
* Constructs an array of regex replacement strings for date format elements.
|
1472
|
*
|
1473
|
* @param bool $strict
|
1474
|
* Whether or not to force 2 digits for elements that sometimes allow either
|
1475
|
* 1 or 2 digits.
|
1476
|
*
|
1477
|
* @return array
|
1478
|
* An array of date() format letters and their regex equivalents.
|
1479
|
*/
|
1480
|
function date_format_patterns($strict = FALSE) {
|
1481
|
return array(
|
1482
|
'd' => '\d{' . ($strict ? '2' : '1,2') . '}',
|
1483
|
'm' => '\d{' . ($strict ? '2' : '1,2') . '}',
|
1484
|
'h' => '\d{' . ($strict ? '2' : '1,2') . '}',
|
1485
|
'H' => '\d{' . ($strict ? '2' : '1,2') . '}',
|
1486
|
'i' => '\d{' . ($strict ? '2' : '1,2') . '}',
|
1487
|
's' => '\d{' . ($strict ? '2' : '1,2') . '}',
|
1488
|
'j' => '\d{1,2}',
|
1489
|
'N' => '\d',
|
1490
|
'S' => '\w{2}',
|
1491
|
'w' => '\d',
|
1492
|
'z' => '\d{1,3}',
|
1493
|
'W' => '\d{1,2}',
|
1494
|
'n' => '\d{1,2}',
|
1495
|
't' => '\d{2}',
|
1496
|
'L' => '\d',
|
1497
|
'o' => '\d{4}',
|
1498
|
'Y' => '-?\d{1,6}',
|
1499
|
'y' => '\d{2}',
|
1500
|
'B' => '\d{3}',
|
1501
|
'g' => '\d{1,2}',
|
1502
|
'G' => '\d{1,2}',
|
1503
|
'e' => '\w*',
|
1504
|
'I' => '\d',
|
1505
|
'T' => '\w*',
|
1506
|
'U' => '\d*',
|
1507
|
'z' => '[+-]?\d*',
|
1508
|
'O' => '[+-]?\d{4}',
|
1509
|
// Using S instead of w and 3 as well as 4 to pick up non-ASCII chars like
|
1510
|
// German umlaut. Per http://drupal.org/node/1101284, we may need as little
|
1511
|
// as 2 and as many as 5 characters in some languages.
|
1512
|
'D' => '\S{2,5}',
|
1513
|
'l' => '\S*',
|
1514
|
'M' => '\S{2,5}',
|
1515
|
'F' => '\S*',
|
1516
|
'P' => '[+-]?\d{2}\:\d{2}',
|
1517
|
'O' => '[+-]\d{4}',
|
1518
|
'c' => '(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-]?\d{2}\:\d{2})',
|
1519
|
'r' => '(\w{3}), (\d{2})\s(\w{3})\s(\d{2,4})\s(\d{2}):(\d{2}):(\d{2})([+-]?\d{4})?',
|
1520
|
);
|
1521
|
}
|
1522
|
|
1523
|
/**
|
1524
|
* Constructs an array of granularity options and their labels.
|
1525
|
*
|
1526
|
* @return array
|
1527
|
* An array of translated date parts, keyed by their machine name.
|
1528
|
*/
|
1529
|
function date_granularity_names() {
|
1530
|
return array(
|
1531
|
'year' => t('Year', array(), array('context' => 'datetime')),
|
1532
|
'month' => t('Month', array(), array('context' => 'datetime')),
|
1533
|
'day' => t('Day', array(), array('context' => 'datetime')),
|
1534
|
'hour' => t('Hour', array(), array('context' => 'datetime')),
|
1535
|
'minute' => t('Minute', array(), array('context' => 'datetime')),
|
1536
|
'second' => t('Second', array(), array('context' => 'datetime')),
|
1537
|
);
|
1538
|
}
|
1539
|
|
1540
|
/**
|
1541
|
* Sorts a granularity array.
|
1542
|
*
|
1543
|
* @param array $granularity
|
1544
|
* An array of date parts.
|
1545
|
*/
|
1546
|
function date_granularity_sorted($granularity) {
|
1547
|
return array_intersect(array(
|
1548
|
'year',
|
1549
|
'month',
|
1550
|
'day',
|
1551
|
'hour',
|
1552
|
'minute',
|
1553
|
'second',
|
1554
|
), $granularity);
|
1555
|
}
|
1556
|
|
1557
|
/**
|
1558
|
* Constructs an array of granularity based on a given precision.
|
1559
|
*
|
1560
|
* @param string $precision
|
1561
|
* A granularity item.
|
1562
|
*
|
1563
|
* @return array
|
1564
|
* A granularity array containing the given precision and all those above it.
|
1565
|
* For example, passing in 'month' will return array('year', 'month').
|
1566
|
*/
|
1567
|
function date_granularity_array_from_precision($precision) {
|
1568
|
$granularity_array = array('year', 'month', 'day', 'hour', 'minute', 'second');
|
1569
|
switch ($precision) {
|
1570
|
case 'year':
|
1571
|
return array_slice($granularity_array, -6, 1);
|
1572
|
|
1573
|
case 'month':
|
1574
|
return array_slice($granularity_array, -6, 2);
|
1575
|
|
1576
|
case 'day':
|
1577
|
return array_slice($granularity_array, -6, 3);
|
1578
|
|
1579
|
case 'hour':
|
1580
|
return array_slice($granularity_array, -6, 4);
|
1581
|
|
1582
|
case 'minute':
|
1583
|
return array_slice($granularity_array, -6, 5);
|
1584
|
|
1585
|
default:
|
1586
|
return $granularity_array;
|
1587
|
}
|
1588
|
}
|
1589
|
|
1590
|
/**
|
1591
|
* Give a granularity array, return the highest precision.
|
1592
|
*
|
1593
|
* @param array $granularity_array
|
1594
|
* An array of date parts.
|
1595
|
*
|
1596
|
* @return string
|
1597
|
* The most precise element in a granularity array.
|
1598
|
*/
|
1599
|
function date_granularity_precision($granularity_array) {
|
1600
|
$input = date_granularity_sorted($granularity_array);
|
1601
|
return array_pop($input);
|
1602
|
}
|
1603
|
|
1604
|
/**
|
1605
|
* Constructs a valid DATETIME format string for the granularity of an item.
|
1606
|
*
|
1607
|
* @todo This function is no longer used as of
|
1608
|
* http://drupalcode.org/project/date.git/commit/07efbb5.
|
1609
|
*/
|
1610
|
function date_granularity_format($granularity) {
|
1611
|
if (is_array($granularity)) {
|
1612
|
$granularity = date_granularity_precision($granularity);
|
1613
|
}
|
1614
|
$format = 'Y-m-d H:i:s';
|
1615
|
switch ($granularity) {
|
1616
|
case 'year':
|
1617
|
return substr($format, 0, 1);
|
1618
|
|
1619
|
case 'month':
|
1620
|
return substr($format, 0, 3);
|
1621
|
|
1622
|
case 'day':
|
1623
|
return substr($format, 0, 5);
|
1624
|
|
1625
|
case 'hour';
|
1626
|
return substr($format, 0, 7);
|
1627
|
|
1628
|
case 'minute':
|
1629
|
return substr($format, 0, 9);
|
1630
|
|
1631
|
default:
|
1632
|
return $format;
|
1633
|
}
|
1634
|
}
|
1635
|
|
1636
|
/**
|
1637
|
* Returns a translated array of timezone names.
|
1638
|
*
|
1639
|
* Cache the untranslated array, make the translated array a static variable.
|
1640
|
*
|
1641
|
* @param bool $required
|
1642
|
* (optional) If FALSE, the returned array will include a blank value.
|
1643
|
* Defaults to FALSE.
|
1644
|
* @param bool $refresh
|
1645
|
* (optional) Whether to refresh the list. Defaults to TRUE.
|
1646
|
*
|
1647
|
* @return array
|
1648
|
* An array of timezone names.
|
1649
|
*/
|
1650
|
function date_timezone_names($required = FALSE, $refresh = FALSE) {
|
1651
|
static $zonenames;
|
1652
|
if (empty($zonenames) || $refresh) {
|
1653
|
$cached = cache_get('date_timezone_identifiers_list');
|
1654
|
$zonenames = !empty($cached) ? $cached->data : array();
|
1655
|
if ($refresh || empty($cached) || empty($zonenames)) {
|
1656
|
$data = timezone_identifiers_list();
|
1657
|
asort($data);
|
1658
|
foreach ($data as $delta => $zone) {
|
1659
|
// Because many timezones exist in PHP only for backward compatibility
|
1660
|
// reasons and should not be used, the list is filtered by a regular
|
1661
|
// expression.
|
1662
|
if (preg_match('!^((Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/|UTC$)!', $zone)) {
|
1663
|
$zonenames[$zone] = $zone;
|
1664
|
}
|
1665
|
}
|
1666
|
|
1667
|
if (!empty($zonenames)) {
|
1668
|
cache_set('date_timezone_identifiers_list', $zonenames);
|
1669
|
}
|
1670
|
}
|
1671
|
foreach ($zonenames as $zone) {
|
1672
|
$zonenames[$zone] = t('!timezone', array('!timezone' => t($zone)));
|
1673
|
}
|
1674
|
}
|
1675
|
$none = array('' => '');
|
1676
|
return !$required ? $none + $zonenames : $zonenames;
|
1677
|
}
|
1678
|
|
1679
|
/**
|
1680
|
* Returns an array of system-allowed timezone abbreviations.
|
1681
|
*
|
1682
|
* Cache an array of just the abbreviation names because the whole
|
1683
|
* timezone_abbreviations_list() is huge, so we don't want to retrieve it more
|
1684
|
* than necessary.
|
1685
|
*
|
1686
|
* @param bool $refresh
|
1687
|
* (optional) Whether to refresh the list. Defaults to TRUE.
|
1688
|
*
|
1689
|
* @return array
|
1690
|
* An array of allowed timezone abbreviations.
|
1691
|
*/
|
1692
|
function date_timezone_abbr($refresh = FALSE) {
|
1693
|
$cached = cache_get('date_timezone_abbreviations');
|
1694
|
$data = isset($cached->data) ? $cached->data : array();
|
1695
|
if (empty($data) || $refresh) {
|
1696
|
$data = array_keys(timezone_abbreviations_list());
|
1697
|
cache_set('date_timezone_abbreviations', $data);
|
1698
|
}
|
1699
|
return $data;
|
1700
|
}
|
1701
|
|
1702
|
/**
|
1703
|
* Formats a date, using a date type or a custom date format string.
|
1704
|
*
|
1705
|
* Reworked from Drupal's format_date function to handle pre-1970 and
|
1706
|
* post-2038 dates and accept a date object instead of a timestamp as input.
|
1707
|
* Translates formatted date results, unlike PHP function date_format().
|
1708
|
* Should only be used for display, not input, because it can't be parsed.
|
1709
|
*
|
1710
|
* @param object $date
|
1711
|
* A date object.
|
1712
|
* @param string $type
|
1713
|
* (optional) The date format to use. Can be 'small', 'medium' or 'large' for
|
1714
|
* the preconfigured date formats. If 'custom' is specified, then $format is
|
1715
|
* required as well. Defaults to 'medium'.
|
1716
|
* @param string $format
|
1717
|
* (optional) A PHP date format string as required by date(). A backslash
|
1718
|
* should be used before a character to avoid interpreting the character as
|
1719
|
* part of a date format. Defaults to an empty string.
|
1720
|
* @param string $langcode
|
1721
|
* (optional) Language code to translate to. Defaults to NULL.
|
1722
|
*
|
1723
|
* @return string
|
1724
|
* A translated date string in the requested format.
|
1725
|
*
|
1726
|
* @see format_date()
|
1727
|
*/
|
1728
|
function date_format_date($date, $type = 'medium', $format = '', $langcode = NULL) {
|
1729
|
if (empty($date)) {
|
1730
|
return '';
|
1731
|
}
|
1732
|
if ($type != 'custom') {
|
1733
|
$format = variable_get('date_format_' . $type);
|
1734
|
}
|
1735
|
if ($type != 'custom' && empty($format)) {
|
1736
|
$format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
|
1737
|
}
|
1738
|
$format = date_limit_format($format, $date->granularity);
|
1739
|
$max = strlen($format);
|
1740
|
$datestring = '';
|
1741
|
for ($i = 0; $i < $max; $i++) {
|
1742
|
$c = $format[$i];
|
1743
|
switch ($c) {
|
1744
|
case 'l':
|
1745
|
$datestring .= t($date->format('l'), array(), array('context' => '', 'langcode' => $langcode));
|
1746
|
break;
|
1747
|
|
1748
|
case 'D':
|
1749
|
$datestring .= t($date->format('D'), array(), array('context' => '', 'langcode' => $langcode));
|
1750
|
break;
|
1751
|
|
1752
|
case 'F':
|
1753
|
$datestring .= t($date->format('F'), array(), array('context' => 'Long month name', 'langcode' => $langcode));
|
1754
|
break;
|
1755
|
|
1756
|
case 'M':
|
1757
|
$datestring .= t($date->format('M'), array(), array('langcode' => $langcode));
|
1758
|
break;
|
1759
|
|
1760
|
case 'A':
|
1761
|
case 'a':
|
1762
|
$datestring .= t($date->format($c), array(), array('context' => 'ampm', 'langcode' => $langcode));
|
1763
|
break;
|
1764
|
|
1765
|
// The timezone name translations can use t().
|
1766
|
case 'e':
|
1767
|
case 'T':
|
1768
|
$datestring .= t($date->format($c));
|
1769
|
break;
|
1770
|
|
1771
|
// Remaining date parts need no translation.
|
1772
|
case 'O':
|
1773
|
$datestring .= sprintf('%s%02d%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60);
|
1774
|
break;
|
1775
|
|
1776
|
case 'P':
|
1777
|
$datestring .= sprintf('%s%02d:%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60);
|
1778
|
break;
|
1779
|
|
1780
|
case 'Z':
|
1781
|
$datestring .= date_offset_get($date);
|
1782
|
break;
|
1783
|
|
1784
|
case '\\':
|
1785
|
$datestring .= $format[++$i];
|
1786
|
break;
|
1787
|
|
1788
|
case 'r':
|
1789
|
$datestring .= date_format_date($date, 'custom', 'D, d M Y H:i:s O', 'en');
|
1790
|
break;
|
1791
|
|
1792
|
default:
|
1793
|
if (strpos('BdcgGhHiIjLmnNosStTuUwWYyz', $c) !== FALSE) {
|
1794
|
$datestring .= $date->format($c);
|
1795
|
}
|
1796
|
else {
|
1797
|
$datestring .= $c;
|
1798
|
}
|
1799
|
}
|
1800
|
}
|
1801
|
return $datestring;
|
1802
|
}
|
1803
|
|
1804
|
/**
|
1805
|
* Formats a time interval with granularity, including past and future context.
|
1806
|
*
|
1807
|
* @param object $date
|
1808
|
* The current date object.
|
1809
|
* @param int $granularity
|
1810
|
* (optional) Number of units to display in the string. Defaults to 2.
|
1811
|
*
|
1812
|
* @return string
|
1813
|
* A translated string representation of the interval.
|
1814
|
*
|
1815
|
* @see format_interval()
|
1816
|
*/
|
1817
|
function date_format_interval($date, $granularity = 2, $display_ago = TRUE) {
|
1818
|
// If no date is sent, then return nothing.
|
1819
|
if (empty($date)) {
|
1820
|
return NULL;
|
1821
|
}
|
1822
|
|
1823
|
$interval = REQUEST_TIME - $date->format('U');
|
1824
|
if ($interval > 0) {
|
1825
|
return $display_ago ? t('!time ago', array('!time' => format_interval($interval, $granularity))) :
|
1826
|
t('!time', array('!time' => format_interval($interval, $granularity)));
|
1827
|
}
|
1828
|
else {
|
1829
|
return format_interval(abs($interval), $granularity);
|
1830
|
}
|
1831
|
}
|
1832
|
|
1833
|
/**
|
1834
|
* A date object for the current time.
|
1835
|
*
|
1836
|
* @param object|string|null $timezone
|
1837
|
* (optional) PHP DateTimeZone object, string or NULL allowed. Optionally
|
1838
|
* force time to a specific timezone, defaults to user timezone, if set,
|
1839
|
* otherwise site timezone. Defaults to NULL.
|
1840
|
*
|
1841
|
* @param bool $reset
|
1842
|
* (optional) Static cache reset.
|
1843
|
*
|
1844
|
* @return object
|
1845
|
* The current time as a date object.
|
1846
|
*/
|
1847
|
function date_now($timezone = NULL, $reset = FALSE) {
|
1848
|
$static_var = __FUNCTION__ . $timezone;
|
1849
|
if ($timezone instanceof DateTimeZone) {
|
1850
|
$static_var = __FUNCTION__ . $timezone->getName();
|
1851
|
}
|
1852
|
|
1853
|
if ($reset) {
|
1854
|
drupal_static_reset($static_var);
|
1855
|
}
|
1856
|
|
1857
|
$now = &drupal_static($static_var);
|
1858
|
|
1859
|
if (!isset($now)) {
|
1860
|
$now = new DateObject('now', $timezone);
|
1861
|
}
|
1862
|
|
1863
|
// Avoid unexpected manipulation of cached $now object
|
1864
|
// by subsequent code execution
|
1865
|
// @see https://drupal.org/node/2261395
|
1866
|
$clone = clone $now;
|
1867
|
return $clone;
|
1868
|
}
|
1869
|
|
1870
|
/**
|
1871
|
* Determines if a timezone string is valid.
|
1872
|
*
|
1873
|
* @param string $timezone
|
1874
|
* A potentially invalid timezone string.
|
1875
|
*
|
1876
|
* @return bool
|
1877
|
* TRUE if the timezone is valid, FALSE otherwise.
|
1878
|
*/
|
1879
|
function date_timezone_is_valid($timezone) {
|
1880
|
static $timezone_names;
|
1881
|
if (empty($timezone_names)) {
|
1882
|
$timezone_names = array_keys(date_timezone_names(TRUE));
|
1883
|
}
|
1884
|
return in_array($timezone, $timezone_names);
|
1885
|
}
|
1886
|
|
1887
|
/**
|
1888
|
* Returns a timezone name to use as a default.
|
1889
|
*
|
1890
|
* @param bool $check_user
|
1891
|
* (optional) Whether or not to check for a user-configured timezone.
|
1892
|
* Defaults to TRUE.
|
1893
|
*
|
1894
|
* @return string
|
1895
|
* The default timezone for a user, if available, otherwise the site.
|
1896
|
*/
|
1897
|
function date_default_timezone($check_user = TRUE) {
|
1898
|
global $user;
|
1899
|
if ($check_user && variable_get('configurable_timezones', 1) && !empty($user->timezone)) {
|
1900
|
return $user->timezone;
|
1901
|
}
|
1902
|
else {
|
1903
|
$default = variable_get('date_default_timezone', '');
|
1904
|
return empty($default) ? 'UTC' : $default;
|
1905
|
}
|
1906
|
}
|
1907
|
|
1908
|
/**
|
1909
|
* Returns a timezone object for the default timezone.
|
1910
|
*
|
1911
|
* @param bool $check_user
|
1912
|
* (optional) Whether or not to check for a user-configured timezone.
|
1913
|
* Defaults to TRUE.
|
1914
|
*
|
1915
|
* @return object
|
1916
|
* The default timezone for a user, if available, otherwise the site.
|
1917
|
*/
|
1918
|
function date_default_timezone_object($check_user = TRUE) {
|
1919
|
return timezone_open(date_default_timezone($check_user));
|
1920
|
}
|
1921
|
|
1922
|
/**
|
1923
|
* Identifies the number of days in a month for a date.
|
1924
|
*/
|
1925
|
function date_days_in_month($year, $month) {
|
1926
|
// Pick a day in the middle of the month to avoid timezone shifts.
|
1927
|
$datetime = date_pad($year, 4) . '-' . date_pad($month) . '-15 00:00:00';
|
1928
|
$date = new DateObject($datetime);
|
1929
|
return $date->format('t');
|
1930
|
}
|
1931
|
|
1932
|
/**
|
1933
|
* Identifies the number of days in a year for a date.
|
1934
|
*
|
1935
|
* @param mixed $date
|
1936
|
* (optional) The current date object, or a date string. Defaults to NULL.
|
1937
|
*
|
1938
|
* @return int
|
1939
|
* The number of days in the year.
|
1940
|
*/
|
1941
|
function date_days_in_year($date = NULL) {
|
1942
|
if (empty($date)) {
|
1943
|
$date = date_now();
|
1944
|
}
|
1945
|
elseif (!is_object($date)) {
|
1946
|
$date = new DateObject($date);
|
1947
|
}
|
1948
|
if (is_object($date)) {
|
1949
|
if ($date->format('L')) {
|
1950
|
return 366;
|
1951
|
}
|
1952
|
else {
|
1953
|
return 365;
|
1954
|
}
|
1955
|
}
|
1956
|
return NULL;
|
1957
|
}
|
1958
|
|
1959
|
/**
|
1960
|
* Identifies the number of ISO weeks in a year for a date.
|
1961
|
*
|
1962
|
* December 28 is always in the last ISO week of the year.
|
1963
|
*
|
1964
|
* @param mixed $date
|
1965
|
* (optional) The current date object, or a date string. Defaults to NULL.
|
1966
|
*
|
1967
|
* @return int
|
1968
|
* The number of ISO weeks in a year.
|
1969
|
*/
|
1970
|
function date_iso_weeks_in_year($date = NULL) {
|
1971
|
if (empty($date)) {
|
1972
|
$date = date_now();
|
1973
|
}
|
1974
|
elseif (!is_object($date)) {
|
1975
|
$date = new DateObject($date);
|
1976
|
}
|
1977
|
|
1978
|
if (is_object($date)) {
|
1979
|
date_date_set($date, $date->format('Y'), 12, 28);
|
1980
|
return $date->format('W');
|
1981
|
}
|
1982
|
return NULL;
|
1983
|
}
|
1984
|
|
1985
|
/**
|
1986
|
* Returns day of week for a given date (0 = Sunday).
|
1987
|
*
|
1988
|
* @param mixed $date
|
1989
|
* (optional) A date, default is current local day. Defaults to NULL.
|
1990
|
*
|
1991
|
* @return int
|
1992
|
* The number of the day in the week.
|
1993
|
*/
|
1994
|
function date_day_of_week($date = NULL) {
|
1995
|
if (empty($date)) {
|
1996
|
$date = date_now();
|
1997
|
}
|
1998
|
elseif (!is_object($date)) {
|
1999
|
$date = new DateObject($date);
|
2000
|
}
|
2001
|
|
2002
|
if (is_object($date)) {
|
2003
|
return $date->format('w');
|
2004
|
}
|
2005
|
return NULL;
|
2006
|
}
|
2007
|
|
2008
|
/**
|
2009
|
* Returns translated name of the day of week for a given date.
|
2010
|
*
|
2011
|
* @param mixed $date
|
2012
|
* (optional) A date, default is current local day. Defaults to NULL.
|
2013
|
* @param string $abbr
|
2014
|
* (optional) Whether to return the abbreviated name for that day.
|
2015
|
* Defaults to TRUE.
|
2016
|
*
|
2017
|
* @return string
|
2018
|
* The name of the day in the week for that date.
|
2019
|
*/
|
2020
|
function date_day_of_week_name($date = NULL, $abbr = TRUE) {
|
2021
|
if (!is_object($date)) {
|
2022
|
$date = new DateObject($date);
|
2023
|
}
|
2024
|
$dow = date_day_of_week($date);
|
2025
|
$days = $abbr ? date_week_days_abbr() : date_week_days();
|
2026
|
return $days[$dow];
|
2027
|
}
|
2028
|
|
2029
|
/**
|
2030
|
* Calculates the start and end dates for a calendar week.
|
2031
|
*
|
2032
|
* The dates are adjusted to use the chosen first day of week for this site.
|
2033
|
*
|
2034
|
* @param int $week
|
2035
|
* The week value.
|
2036
|
* @param int $year
|
2037
|
* The year value.
|
2038
|
*
|
2039
|
* @return array
|
2040
|
* A numeric array containing the start and end dates of a week.
|
2041
|
*/
|
2042
|
function date_week_range($week, $year) {
|
2043
|
if (variable_get('date_api_use_iso8601', FALSE)) {
|
2044
|
return date_iso_week_range($week, $year);
|
2045
|
}
|
2046
|
$min_date = new DateObject($year . '-01-01 00:00:00');
|
2047
|
$min_date->setTimezone(date_default_timezone_object());
|
2048
|
|
2049
|
// Move to the right week.
|
2050
|
date_modify($min_date, '+' . strval(7 * ($week - 1)) . ' days');
|
2051
|
|
2052
|
// Move backwards to the first day of the week.
|
2053
|
$first_day = variable_get('date_first_day', 0);
|
2054
|
$day_wday = date_format($min_date, 'w');
|
2055
|
date_modify($min_date, '-' . strval((7 + $day_wday - $first_day) % 7) . ' days');
|
2056
|
|
2057
|
// Move forwards to the last day of the week.
|
2058
|
$max_date = clone($min_date);
|
2059
|
date_modify($max_date, '+6 days');
|
2060
|
|
2061
|
if (date_format($min_date, 'Y') != $year) {
|
2062
|
$min_date = new DateObject($year . '-01-01 00:00:00');
|
2063
|
}
|
2064
|
return array($min_date, $max_date);
|
2065
|
}
|
2066
|
|
2067
|
/**
|
2068
|
* Calculates the start and end dates for an ISO week.
|
2069
|
*
|
2070
|
* @param int $week
|
2071
|
* The week value.
|
2072
|
* @param int $year
|
2073
|
* The year value.
|
2074
|
*
|
2075
|
* @return array
|
2076
|
* A numeric array containing the start and end dates of an ISO week.
|
2077
|
*/
|
2078
|
function date_iso_week_range($week, $year) {
|
2079
|
// Get to the last ISO week of the previous year.
|
2080
|
$min_date = new DateObject(($year - 1) . '-12-28 00:00:00');
|
2081
|
date_timezone_set($min_date, date_default_timezone_object());
|
2082
|
|
2083
|
// Find the first day of the first ISO week in the year.
|
2084
|
// If it's already a Monday, date_modify won't add a Monday,
|
2085
|
// it will remain the same day. So add a Sunday first, then a Monday.
|
2086
|
date_modify($min_date, '+1 Sunday');
|
2087
|
date_modify($min_date, '+1 Monday');
|
2088
|
|
2089
|
// Jump ahead to the desired week for the beginning of the week range.
|
2090
|
if ($week > 1) {
|
2091
|
date_modify($min_date, '+ ' . ($week - 1) . ' weeks');
|
2092
|
}
|
2093
|
|
2094
|
// Move forwards to the last day of the week.
|
2095
|
$max_date = clone($min_date);
|
2096
|
date_modify($max_date, '+6 days');
|
2097
|
return array($min_date, $max_date);
|
2098
|
}
|
2099
|
|
2100
|
/**
|
2101
|
* The number of calendar weeks in a year.
|
2102
|
*
|
2103
|
* PHP week functions return the ISO week, not the calendar week.
|
2104
|
*
|
2105
|
* @param int $year
|
2106
|
* A year value.
|
2107
|
*
|
2108
|
* @return int
|
2109
|
* Number of calendar weeks in selected year.
|
2110
|
*/
|
2111
|
function date_weeks_in_year($year) {
|
2112
|
$date = new DateObject(($year + 1) . '-01-01 12:00:00', 'UTC');
|
2113
|
date_modify($date, '-1 day');
|
2114
|
return date_week($date->format('Y-m-d'));
|
2115
|
}
|
2116
|
|
2117
|
/**
|
2118
|
* The calendar week number for a date.
|
2119
|
*
|
2120
|
* PHP week functions return the ISO week, not the calendar week.
|
2121
|
*
|
2122
|
* @param string $date
|
2123
|
* A date string in the format Y-m-d.
|
2124
|
*
|
2125
|
* @return int
|
2126
|
* The calendar week number.
|
2127
|
*/
|
2128
|
function date_week($date) {
|
2129
|
$date = substr($date, 0, 10);
|
2130
|
$parts = explode('-', $date);
|
2131
|
|
2132
|
$date = new DateObject($date . ' 12:00:00', 'UTC');
|
2133
|
|
2134
|
// If we are using ISO weeks, this is easy.
|
2135
|
if (variable_get('date_api_use_iso8601', FALSE)) {
|
2136
|
return intval($date->format('W'));
|
2137
|
}
|
2138
|
|
2139
|
$year_date = new DateObject($parts[0] . '-01-01 12:00:00', 'UTC');
|
2140
|
$week = intval($date->format('W'));
|
2141
|
$year_week = intval(date_format($year_date, 'W'));
|
2142
|
$date_year = intval($date->format('o'));
|
2143
|
|
2144
|
// Remove the leap week if it's present.
|
2145
|
if ($date_year > intval($parts[0])) {
|
2146
|
$last_date = clone($date);
|
2147
|
date_modify($last_date, '-7 days');
|
2148
|
$week = date_format($last_date, 'W') + 1;
|
2149
|
}
|
2150
|
elseif ($date_year < intval($parts[0])) {
|
2151
|
$week = 0;
|
2152
|
}
|
2153
|
|
2154
|
if ($year_week != 1) {
|
2155
|
$week++;
|
2156
|
}
|
2157
|
|
2158
|
// Convert to ISO-8601 day number, to match weeks calculated above.
|
2159
|
$iso_first_day = 1 + (variable_get('date_first_day', 0) + 6) % 7;
|
2160
|
|
2161
|
// If it's before the starting day, it's the previous week.
|
2162
|
if (intval($date->format('N')) < $iso_first_day) {
|
2163
|
$week--;
|
2164
|
}
|
2165
|
|
2166
|
// If the year starts before, it's an extra week at the beginning.
|
2167
|
if (intval(date_format($year_date, 'N')) < $iso_first_day) {
|
2168
|
$week++;
|
2169
|
}
|
2170
|
|
2171
|
return $week;
|
2172
|
}
|
2173
|
|
2174
|
/**
|
2175
|
* Helper function to left pad date parts with zeros.
|
2176
|
*
|
2177
|
* Provided because this is needed so often with dates.
|
2178
|
*
|
2179
|
* @param int $value
|
2180
|
* The value to pad.
|
2181
|
* @param int $size
|
2182
|
* (optional) Total size expected, usually 2 or 4. Defaults to 2.
|
2183
|
*
|
2184
|
* @return string
|
2185
|
* The padded value.
|
2186
|
*/
|
2187
|
function date_pad($value, $size = 2) {
|
2188
|
return sprintf("%0" . $size . "d", $value);
|
2189
|
}
|
2190
|
|
2191
|
/**
|
2192
|
* Determines if the granularity contains a time portion.
|
2193
|
*
|
2194
|
* @param array $granularity
|
2195
|
* An array of allowed date parts, all others will be removed.
|
2196
|
*
|
2197
|
* @return bool
|
2198
|
* TRUE if the granularity contains a time portion, FALSE otherwise.
|
2199
|
*/
|
2200
|
function date_has_time($granularity) {
|
2201
|
if (!is_array($granularity)) {
|
2202
|
$granularity = array();
|
2203
|
}
|
2204
|
$options = array('hour', 'minute', 'second');
|
2205
|
return (bool) count(array_intersect($granularity, $options));
|
2206
|
}
|
2207
|
|
2208
|
/**
|
2209
|
* Determines if the granularity contains a date portion.
|
2210
|
*
|
2211
|
* @param array $granularity
|
2212
|
* An array of allowed date parts, all others will be removed.
|
2213
|
*
|
2214
|
* @return bool
|
2215
|
* TRUE if the granularity contains a date portion, FALSE otherwise.
|
2216
|
*/
|
2217
|
function date_has_date($granularity) {
|
2218
|
if (!is_array($granularity)) {
|
2219
|
$granularity = array();
|
2220
|
}
|
2221
|
$options = array('year', 'month', 'day');
|
2222
|
return (bool) count(array_intersect($granularity, $options));
|
2223
|
}
|
2224
|
|
2225
|
/**
|
2226
|
* Helper function to get a format for a specific part of a date field.
|
2227
|
*
|
2228
|
* @param string $part
|
2229
|
* The date field part, either 'time' or 'date'.
|
2230
|
* @param string $format
|
2231
|
* A date format string.
|
2232
|
*
|
2233
|
* @return string
|
2234
|
* The date format for the given part.
|
2235
|
*/
|
2236
|
function date_part_format($part, $format) {
|
2237
|
switch ($part) {
|
2238
|
case 'date':
|
2239
|
return date_limit_format($format, array('year', 'month', 'day'));
|
2240
|
|
2241
|
case 'time':
|
2242
|
return date_limit_format($format, array('hour', 'minute', 'second'));
|
2243
|
|
2244
|
default:
|
2245
|
return date_limit_format($format, array($part));
|
2246
|
}
|
2247
|
}
|
2248
|
|
2249
|
/**
|
2250
|
* Limits a date format to include only elements from a given granularity array.
|
2251
|
*
|
2252
|
* Example:
|
2253
|
* date_limit_format('F j, Y - H:i', array('year', 'month', 'day'));
|
2254
|
* returns 'F j, Y'
|
2255
|
*
|
2256
|
* @param string $format
|
2257
|
* A date format string.
|
2258
|
* @param array $granularity
|
2259
|
* An array of allowed date parts, all others will be removed.
|
2260
|
*
|
2261
|
* @return string
|
2262
|
* The format string with all other elements removed.
|
2263
|
*/
|
2264
|
function date_limit_format($format, $granularity) {
|
2265
|
// Use the advanced drupal_static() pattern to improve performance.
|
2266
|
static $drupal_static_fast;
|
2267
|
if (!isset($drupal_static_fast)) {
|
2268
|
$drupal_static_fast['formats'] = &drupal_static(__FUNCTION__);
|
2269
|
}
|
2270
|
$formats = &$drupal_static_fast['formats'];
|
2271
|
$format_granularity_cid = $format . '|' . implode(',', $granularity);
|
2272
|
if (isset($formats[$format_granularity_cid])) {
|
2273
|
return $formats[$format_granularity_cid];
|
2274
|
}
|
2275
|
|
2276
|
// If punctuation has been escaped, remove the escaping. Done using strtr()
|
2277
|
// because it is easier than getting the escape character extracted using
|
2278
|
// preg_replace().
|
2279
|
$replace = array(
|
2280
|
'\-' => '-',
|
2281
|
'\:' => ':',
|
2282
|
"\'" => "'",
|
2283
|
'\. ' => ' . ',
|
2284
|
'\,' => ',',
|
2285
|
);
|
2286
|
$format = strtr($format, $replace);
|
2287
|
|
2288
|
// Get the 'T' out of ISO date formats that don't have both date and time.
|
2289
|
if (!date_has_time($granularity) || !date_has_date($granularity)) {
|
2290
|
$format = str_replace('\T', ' ', $format);
|
2291
|
$format = str_replace('T', ' ', $format);
|
2292
|
}
|
2293
|
|
2294
|
$regex = array();
|
2295
|
if (!date_has_time($granularity)) {
|
2296
|
$regex[] = '((?<!\\\\)[a|A])';
|
2297
|
}
|
2298
|
// Create regular expressions to remove selected values from string.
|
2299
|
// Use (?<!\\\\) to keep escaped letters from being removed.
|
2300
|
foreach (date_nongranularity($granularity) as $element) {
|
2301
|
switch ($element) {
|
2302
|
case 'year':
|
2303
|
$regex[] = '([\-/\.,:]?\s?(?<!\\\\)[Yy])';
|
2304
|
break;
|
2305
|
|
2306
|
case 'day':
|
2307
|
$regex[] = '([\-/\.,:]?\s?(?<!\\\\)[l|D|d|dS|j|jS|N|w|W|z]{1,2})';
|
2308
|
break;
|
2309
|
|
2310
|
case 'month':
|
2311
|
$regex[] = '([\-/\.,:]?\s?(?<!\\\\)[FMmn])';
|
2312
|
break;
|
2313
|
|
2314
|
case 'hour':
|
2315
|
$regex[] = '([\-/\.,:]?\s?(?<!\\\\)[HhGg])';
|
2316
|
break;
|
2317
|
|
2318
|
case 'minute':
|
2319
|
$regex[] = '([\-/\.,:]?\s?(?<!\\\\)[i])';
|
2320
|
break;
|
2321
|
|
2322
|
case 'second':
|
2323
|
$regex[] = '([\-/\.,:]?\s?(?<!\\\\)[s])';
|
2324
|
break;
|
2325
|
|
2326
|
case 'timezone':
|
2327
|
$regex[] = '([\-/\.,:]?\s?(?<!\\\\)[TOZPe])';
|
2328
|
break;
|
2329
|
|
2330
|
}
|
2331
|
}
|
2332
|
// Remove empty parentheses, brackets, pipes.
|
2333
|
$regex[] = '(\(\))';
|
2334
|
$regex[] = '(\[\])';
|
2335
|
$regex[] = '(\|\|)';
|
2336
|
|
2337
|
// Remove selected values from string.
|
2338
|
$format = trim(preg_replace($regex, array(), $format));
|
2339
|
// Remove orphaned punctuation at the beginning of the string.
|
2340
|
$format = preg_replace('`^([\-/\.,:\'])`', '', $format);
|
2341
|
// Remove orphaned punctuation at the end of the string.
|
2342
|
$format = preg_replace('([\-/,:\']$)', '', $format);
|
2343
|
$format = preg_replace('(\\$)', '', $format);
|
2344
|
|
2345
|
// Trim any whitespace from the result.
|
2346
|
$format = trim($format);
|
2347
|
|
2348
|
// After removing the non-desired parts of the format, test if the only things
|
2349
|
// left are escaped, non-date, characters. If so, return nothing.
|
2350
|
// Using S instead of w to pick up non-ASCII characters.
|
2351
|
$test = trim(preg_replace('(\\\\\S{1,3})u', '', $format));
|
2352
|
if (empty($test)) {
|
2353
|
$format = '';
|
2354
|
}
|
2355
|
|
2356
|
// Store the return value in the static array for performance.
|
2357
|
$formats[$format_granularity_cid] = $format;
|
2358
|
|
2359
|
return $format;
|
2360
|
}
|
2361
|
|
2362
|
/**
|
2363
|
* Converts a format to an ordered array of granularity parts.
|
2364
|
*
|
2365
|
* Example:
|
2366
|
* date_format_order('m/d/Y H:i')
|
2367
|
* returns
|
2368
|
* array(
|
2369
|
* 0 => 'month',
|
2370
|
* 1 => 'day',
|
2371
|
* 2 => 'year',
|
2372
|
* 3 => 'hour',
|
2373
|
* 4 => 'minute',
|
2374
|
* );
|
2375
|
*
|
2376
|
* @param string $format
|
2377
|
* A date format string.
|
2378
|
*
|
2379
|
* @return array
|
2380
|
* An array of ordered granularity elements from the given format string.
|
2381
|
*/
|
2382
|
function date_format_order($format) {
|
2383
|
$order = array();
|
2384
|
if (empty($format)) {
|
2385
|
return $order;
|
2386
|
}
|
2387
|
|
2388
|
$max = strlen($format);
|
2389
|
for ($i = 0; $i <= $max; $i++) {
|
2390
|
if (!isset($format[$i])) {
|
2391
|
break;
|
2392
|
}
|
2393
|
switch ($format[$i]) {
|
2394
|
case 'd':
|
2395
|
case 'j':
|
2396
|
$order[] = 'day';
|
2397
|
break;
|
2398
|
|
2399
|
case 'F':
|
2400
|
case 'M':
|
2401
|
case 'm':
|
2402
|
case 'n':
|
2403
|
$order[] = 'month';
|
2404
|
break;
|
2405
|
|
2406
|
case 'Y':
|
2407
|
case 'y':
|
2408
|
$order[] = 'year';
|
2409
|
break;
|
2410
|
|
2411
|
case 'g':
|
2412
|
case 'G':
|
2413
|
case 'h':
|
2414
|
case 'H':
|
2415
|
$order[] = 'hour';
|
2416
|
break;
|
2417
|
|
2418
|
case 'i':
|
2419
|
$order[] = 'minute';
|
2420
|
break;
|
2421
|
|
2422
|
case 's':
|
2423
|
$order[] = 'second';
|
2424
|
break;
|
2425
|
}
|
2426
|
}
|
2427
|
return $order;
|
2428
|
}
|
2429
|
|
2430
|
/**
|
2431
|
* Strips out unwanted granularity elements.
|
2432
|
*
|
2433
|
* @param array $granularity
|
2434
|
* An array like ('year', 'month', 'day', 'hour', 'minute', 'second');
|
2435
|
*
|
2436
|
* @return array
|
2437
|
* A reduced set of granularitiy elements.
|
2438
|
*/
|
2439
|
function date_nongranularity($granularity) {
|
2440
|
$options = array(
|
2441
|
'year',
|
2442
|
'month',
|
2443
|
'day',
|
2444
|
'hour',
|
2445
|
'minute',
|
2446
|
'second',
|
2447
|
'timezone',
|
2448
|
);
|
2449
|
return array_diff($options, (array) $granularity);
|
2450
|
}
|
2451
|
|
2452
|
/**
|
2453
|
* Implements hook_element_info().
|
2454
|
*/
|
2455
|
function date_api_element_info() {
|
2456
|
module_load_include('inc', 'date_api', 'date_api_elements');
|
2457
|
return _date_api_element_info();
|
2458
|
}
|
2459
|
|
2460
|
/**
|
2461
|
* Implements hook_theme().
|
2462
|
*/
|
2463
|
function date_api_theme($existing, $type, $theme, $path) {
|
2464
|
$base = array(
|
2465
|
'file' => 'theme.inc',
|
2466
|
'path' => "$path/theme",
|
2467
|
);
|
2468
|
return array(
|
2469
|
'date_nav_title' => $base + array(
|
2470
|
'variables' => array(
|
2471
|
'granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL
|
2472
|
),
|
2473
|
),
|
2474
|
'date_timezone' => $base + array('render element' => 'element'),
|
2475
|
'date_select' => $base + array('render element' => 'element'),
|
2476
|
'date_text' => $base + array('render element' => 'element'),
|
2477
|
'date_select_element' => $base + array('render element' => 'element'),
|
2478
|
'date_textfield_element' => $base + array('render element' => 'element'),
|
2479
|
'date_part_hour_prefix' => $base + array('render element' => 'element'),
|
2480
|
'date_part_minsec_prefix' => $base + array('render element' => 'element'),
|
2481
|
'date_part_label_year' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
|
2482
|
'date_part_label_month' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
|
2483
|
'date_part_label_day' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
|
2484
|
'date_part_label_hour' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
|
2485
|
'date_part_label_minute' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
|
2486
|
'date_part_label_second' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
|
2487
|
'date_part_label_ampm' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
|
2488
|
'date_part_label_timezone' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
|
2489
|
'date_part_label_date' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
|
2490
|
'date_part_label_time' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
|
2491
|
'date_views_filter_form' => $base + array('template' => 'date-views-filter-form', 'render element' => 'form'),
|
2492
|
'date_calendar_day' => $base + array('variables' => array('date' => NULL)),
|
2493
|
'date_time_ago' => $base + array(
|
2494
|
'variables' => array(
|
2495
|
'start_date' => NULL, 'end_date' => NULL, 'interval' => NULL
|
2496
|
),
|
2497
|
),
|
2498
|
);
|
2499
|
}
|
2500
|
|
2501
|
/**
|
2502
|
* Function to figure out which local timezone applies to a date and select it.
|
2503
|
*
|
2504
|
* @param string $handling
|
2505
|
* The timezone handling.
|
2506
|
* @param string $timezone
|
2507
|
* (optional) A timezone string. Defaults to an empty string.
|
2508
|
*
|
2509
|
* @return string
|
2510
|
* The timezone string.
|
2511
|
*/
|
2512
|
function date_get_timezone($handling, $timezone = '') {
|
2513
|
switch ($handling) {
|
2514
|
case 'date':
|
2515
|
$timezone = !empty($timezone) ? $timezone : date_default_timezone();
|
2516
|
break;
|
2517
|
|
2518
|
case 'utc':
|
2519
|
$timezone = 'UTC';
|
2520
|
break;
|
2521
|
|
2522
|
default:
|
2523
|
$timezone = date_default_timezone();
|
2524
|
}
|
2525
|
return $timezone > '' ? $timezone : date_default_timezone();
|
2526
|
}
|
2527
|
|
2528
|
/**
|
2529
|
* Function to figure out which db timezone applies to a date.
|
2530
|
*
|
2531
|
* @param string $handling
|
2532
|
* The timezone handling.
|
2533
|
* @param string $timezone
|
2534
|
* (optional) When $handling is 'date', date_get_timezone_db() returns this
|
2535
|
* value.
|
2536
|
*
|
2537
|
* @return string
|
2538
|
* The timezone string.
|
2539
|
*/
|
2540
|
function date_get_timezone_db($handling, $timezone = NULL) {
|
2541
|
switch ($handling) {
|
2542
|
case ('utc'):
|
2543
|
case ('site'):
|
2544
|
case ('user'):
|
2545
|
// These handling modes all convert to UTC before storing in the DB.
|
2546
|
$timezone = 'UTC';
|
2547
|
break;
|
2548
|
|
2549
|
case ('date'):
|
2550
|
if ($timezone == NULL) {
|
2551
|
// This shouldn't happen, since it's meaning is undefined. But we need
|
2552
|
// to fall back to *something* that's a legal timezone.
|
2553
|
$timezone = date_default_timezone();
|
2554
|
}
|
2555
|
break;
|
2556
|
|
2557
|
case ('none'):
|
2558
|
default:
|
2559
|
$timezone = date_default_timezone();
|
2560
|
break;
|
2561
|
}
|
2562
|
return $timezone;
|
2563
|
}
|
2564
|
|
2565
|
/**
|
2566
|
* Helper function for converting back and forth from '+1' to 'First'.
|
2567
|
*/
|
2568
|
function date_order_translated() {
|
2569
|
return array(
|
2570
|
'+1' => t('First', array(), array('context' => 'date_order')),
|
2571
|
'+2' => t('Second', array(), array('context' => 'date_order')),
|
2572
|
'+3' => t('Third', array(), array('context' => 'date_order')),
|
2573
|
'+4' => t('Fourth', array(), array('context' => 'date_order')),
|
2574
|
'+5' => t('Fifth', array(), array('context' => 'date_order')),
|
2575
|
'-1' => t('Last', array(), array('context' => 'date_order_reverse')),
|
2576
|
'-2' => t('Next to last', array(), array('context' => 'date_order_reverse')),
|
2577
|
'-3' => t('Third from last', array(), array('context' => 'date_order_reverse')),
|
2578
|
'-4' => t('Fourth from last', array(), array('context' => 'date_order_reverse')),
|
2579
|
'-5' => t('Fifth from last', array(), array('context' => 'date_order_reverse')),
|
2580
|
);
|
2581
|
}
|
2582
|
|
2583
|
/**
|
2584
|
* Creates an array of ordered strings, using English text when possible.
|
2585
|
*/
|
2586
|
function date_order() {
|
2587
|
return array(
|
2588
|
'+1' => 'First',
|
2589
|
'+2' => 'Second',
|
2590
|
'+3' => 'Third',
|
2591
|
'+4' => 'Fourth',
|
2592
|
'+5' => 'Fifth',
|
2593
|
'-1' => 'Last',
|
2594
|
'-2' => '-2',
|
2595
|
'-3' => '-3',
|
2596
|
'-4' => '-4',
|
2597
|
'-5' => '-5',
|
2598
|
);
|
2599
|
}
|
2600
|
|
2601
|
/**
|
2602
|
* Tests validity of a date range string.
|
2603
|
*
|
2604
|
* @param string $string
|
2605
|
* A min and max year string like '-3:+1'a.
|
2606
|
*
|
2607
|
* @return bool
|
2608
|
* TRUE if the date range is valid, FALSE otherwise.
|
2609
|
*/
|
2610
|
function date_range_valid($string) {
|
2611
|
$matches = preg_match('@^([\+\-][0-9]+|[0-9]{4}):([\+\-][0-9]+|[0-9]{4})$@', $string);
|
2612
|
return $matches < 1 ? FALSE : TRUE;
|
2613
|
}
|
2614
|
|
2615
|
/**
|
2616
|
* Splits a string like -3:+3 or 2001:2010 into an array of start and end years.
|
2617
|
*
|
2618
|
* Center the range around the current year, if any, but expand it far
|
2619
|
* enough so it will pick up the year value in the field in case
|
2620
|
* the value in the field is outside the initial range.
|
2621
|
*
|
2622
|
* @param string $string
|
2623
|
* A min and max year string like '-3:+1'.
|
2624
|
* @param object $date
|
2625
|
* (optional) A date object. Defaults to NULL.
|
2626
|
*
|
2627
|
* @return array
|
2628
|
* A numerically indexed array, containing a start and end year.
|
2629
|
*/
|
2630
|
function date_range_years($string, $date = NULL) {
|
2631
|
$this_year = date_format(date_now(), 'Y');
|
2632
|
list($start_year, $end_year) = explode(':', $string);
|
2633
|
|
2634
|
// Valid patterns would be -5:+5, 0:+1, 2008:2010.
|
2635
|
$plus_pattern = '@[\+\-][0-9]{1,4}@';
|
2636
|
$year_pattern = '@^[0-9]{4}@';
|
2637
|
if (!preg_match($year_pattern, $start_year, $matches)) {
|
2638
|
if (preg_match($plus_pattern, $start_year, $matches)) {
|
2639
|
$start_year = $this_year + $matches[0];
|
2640
|
}
|
2641
|
else {
|
2642
|
$start_year = $this_year;
|
2643
|
}
|
2644
|
}
|
2645
|
if (!preg_match($year_pattern, $end_year, $matches)) {
|
2646
|
if (preg_match($plus_pattern, $end_year, $matches)) {
|
2647
|
$end_year = $this_year + $matches[0];
|
2648
|
}
|
2649
|
else {
|
2650
|
$end_year = $this_year;
|
2651
|
}
|
2652
|
}
|
2653
|
// If there is a current value, stretch the range to include it.
|
2654
|
$value_year = is_object($date) ? $date->format('Y') : '';
|
2655
|
if (!empty($value_year)) {
|
2656
|
if ($start_year <= $end_year) {
|
2657
|
$start_year = min($value_year, $start_year);
|
2658
|
$end_year = max($value_year, $end_year);
|
2659
|
}
|
2660
|
else {
|
2661
|
$start_year = max($value_year, $start_year);
|
2662
|
$end_year = min($value_year, $end_year);
|
2663
|
}
|
2664
|
}
|
2665
|
return array($start_year, $end_year);
|
2666
|
}
|
2667
|
|
2668
|
/**
|
2669
|
* Converts a min and max year into a string like '-3:+1'.
|
2670
|
*
|
2671
|
* @param array $years
|
2672
|
* A numerically indexed array, containing a minimum and maximum year.
|
2673
|
*
|
2674
|
* @return string
|
2675
|
* A min and max year string like '-3:+1'.
|
2676
|
*/
|
2677
|
function date_range_string($years) {
|
2678
|
$this_year = date_format(date_now(), 'Y');
|
2679
|
|
2680
|
if ($years[0] < $this_year) {
|
2681
|
$min = '-' . ($this_year - $years[0]);
|
2682
|
}
|
2683
|
else {
|
2684
|
$min = '+' . ($years[0] - $this_year);
|
2685
|
}
|
2686
|
|
2687
|
if ($years[1] < $this_year) {
|
2688
|
$max = '-' . ($this_year - $years[1]);
|
2689
|
}
|
2690
|
else {
|
2691
|
$max = '+' . ($years[1] - $this_year);
|
2692
|
}
|
2693
|
|
2694
|
return $min . ':' . $max;
|
2695
|
}
|
2696
|
|
2697
|
/**
|
2698
|
* Temporary helper to re-create equivalent of content_database_info().
|
2699
|
*/
|
2700
|
function date_api_database_info($field, $revision = FIELD_LOAD_CURRENT) {
|
2701
|
return array(
|
2702
|
'columns' => $field['storage']['details']['sql'][$revision],
|
2703
|
'table' => _field_sql_storage_tablename($field),
|
2704
|
);
|
2705
|
}
|
2706
|
|
2707
|
/**
|
2708
|
* Implements hook_form_FORM_ID_alter() for system_regional_settings().
|
2709
|
*
|
2710
|
* Add a form element to configure whether or not week numbers are ISO-8601, the
|
2711
|
* default is FALSE (US/UK/AUS norm).
|
2712
|
*/
|
2713
|
function date_api_form_system_regional_settings_alter(&$form, &$form_state, $form_id) {
|
2714
|
$form['locale']['date_api_use_iso8601'] = array(
|
2715
|
'#type' => 'checkbox',
|
2716
|
'#title' => t('Use ISO-8601 week numbers'),
|
2717
|
'#default_value' => variable_get('date_api_use_iso8601', FALSE),
|
2718
|
'#description' => t('IMPORTANT! If checked, First day of week MUST be set to Monday'),
|
2719
|
);
|
2720
|
$form['#validate'][] = 'date_api_form_system_settings_validate';
|
2721
|
}
|
2722
|
|
2723
|
/**
|
2724
|
* Validate that the option to use ISO weeks matches first day of week choice.
|
2725
|
*/
|
2726
|
function date_api_form_system_settings_validate(&$form, &$form_state) {
|
2727
|
$form_values = $form_state['values'];
|
2728
|
if ($form_values['date_api_use_iso8601'] && $form_values['date_first_day'] != 1) {
|
2729
|
form_set_error('date_first_day', t('When using ISO-8601 week numbers, the first day of the week must be set to Monday.'));
|
2730
|
}
|
2731
|
}
|
2732
|
|
2733
|
/**
|
2734
|
* Creates an array of date format types for use as an options list.
|
2735
|
*/
|
2736
|
function date_format_type_options() {
|
2737
|
$options = array();
|
2738
|
$format_types = system_get_date_types();
|
2739
|
if (!empty($format_types)) {
|
2740
|
foreach ($format_types as $type => $type_info) {
|
2741
|
$options[$type] = $type_info['title'] . ' (' . date_format_date(date_example_date(), $type) . ')';
|
2742
|
}
|
2743
|
}
|
2744
|
return $options;
|
2745
|
}
|
2746
|
|
2747
|
/**
|
2748
|
* Creates an example date.
|
2749
|
*
|
2750
|
* This ensures a clear difference between month and day, and 12 and 24 hours.
|
2751
|
*/
|
2752
|
function date_example_date() {
|
2753
|
$now = date_now();
|
2754
|
if (date_format($now, 'M') == date_format($now, 'F')) {
|
2755
|
date_modify($now, '+1 month');
|
2756
|
}
|
2757
|
if (date_format($now, 'm') == date_format($now, 'd')) {
|
2758
|
date_modify($now, '+1 day');
|
2759
|
}
|
2760
|
if (date_format($now, 'H') == date_format($now, 'h')) {
|
2761
|
date_modify($now, '+12 hours');
|
2762
|
}
|
2763
|
return $now;
|
2764
|
}
|
2765
|
|
2766
|
/**
|
2767
|
* Determine if a start/end date combination qualify as 'All day'.
|
2768
|
*
|
2769
|
* @param string $string1
|
2770
|
* A string date in datetime format for the 'start' date.
|
2771
|
* @param string $string2
|
2772
|
* A string date in datetime format for the 'end' date.
|
2773
|
* @param string $granularity
|
2774
|
* (optional) The granularity of the date. Defaults to 'second'.
|
2775
|
* @param int $increment
|
2776
|
* (optional) The increment of the date. Defaults to 1.
|
2777
|
*
|
2778
|
* @return bool
|
2779
|
* TRUE if the date is all day, FALSE otherwise.
|
2780
|
*/
|
2781
|
function date_is_all_day($string1, $string2, $granularity = 'second', $increment = 1) {
|
2782
|
if (empty($string1) || empty($string2)) {
|
2783
|
return FALSE;
|
2784
|
}
|
2785
|
elseif (!in_array($granularity, array('hour', 'minute', 'second'))) {
|
2786
|
return FALSE;
|
2787
|
}
|
2788
|
|
2789
|
preg_match('/([0-9]{4}-[0-9]{2}-[0-9]{2}) (([0-9]{2}):([0-9]{2}):([0-9]{2}))/', $string1, $matches);
|
2790
|
$count = count($matches);
|
2791
|
$date1 = $count > 1 ? $matches[1] : '';
|
2792
|
$time1 = $count > 2 ? $matches[2] : '';
|
2793
|
$hour1 = $count > 3 ? intval($matches[3]) : 0;
|
2794
|
$min1 = $count > 4 ? intval($matches[4]) : 0;
|
2795
|
$sec1 = $count > 5 ? intval($matches[5]) : 0;
|
2796
|
preg_match('/([0-9]{4}-[0-9]{2}-[0-9]{2}) (([0-9]{2}):([0-9]{2}):([0-9]{2}))/', $string2, $matches);
|
2797
|
$count = count($matches);
|
2798
|
$date2 = $count > 1 ? $matches[1] : '';
|
2799
|
$time2 = $count > 2 ? $matches[2] : '';
|
2800
|
$hour2 = $count > 3 ? intval($matches[3]) : 0;
|
2801
|
$min2 = $count > 4 ? intval($matches[4]) : 0;
|
2802
|
$sec2 = $count > 5 ? intval($matches[5]) : 0;
|
2803
|
if (empty($date1) || empty($date2)) {
|
2804
|
return FALSE;
|
2805
|
}
|
2806
|
if (empty($time1) || empty($time2)) {
|
2807
|
return FALSE;
|
2808
|
}
|
2809
|
|
2810
|
$tmp = date_seconds('s', TRUE, $increment);
|
2811
|
$max_seconds = intval(array_pop($tmp));
|
2812
|
$tmp = date_minutes('i', TRUE, $increment);
|
2813
|
$max_minutes = intval(array_pop($tmp));
|
2814
|
|
2815
|
// See if minutes and seconds are the maximum allowed for an increment or the
|
2816
|
// maximum possible (59), or 0.
|
2817
|
switch ($granularity) {
|
2818
|
case 'second':
|
2819
|
$min_match = $time1 == '00:00:00'
|
2820
|
|| ($hour1 == 0 && $min1 == 0 && $sec1 == 0);
|
2821
|
$max_match = $time2 == '00:00:00'
|
2822
|
|| ($hour2 == 23 && in_array($min2, array($max_minutes, 59)) && in_array($sec2, array($max_seconds, 59)))
|
2823
|
|| ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0 && $sec1 == 0 && $sec2 == 0);
|
2824
|
break;
|
2825
|
|
2826
|
case 'minute':
|
2827
|
$min_match = $time1 == '00:00:00'
|
2828
|
|| ($hour1 == 0 && $min1 == 0);
|
2829
|
$max_match = $time2 == '00:00:00'
|
2830
|
|| ($hour2 == 23 && in_array($min2, array($max_minutes, 59)))
|
2831
|
|| ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0);
|
2832
|
break;
|
2833
|
|
2834
|
case 'hour':
|
2835
|
$min_match = $time1 == '00:00:00'
|
2836
|
|| ($hour1 == 0);
|
2837
|
$max_match = $time2 == '00:00:00'
|
2838
|
|| ($hour2 == 23)
|
2839
|
|| ($hour1 == 0 && $hour2 == 0);
|
2840
|
break;
|
2841
|
|
2842
|
default:
|
2843
|
$min_match = TRUE;
|
2844
|
$max_match = FALSE;
|
2845
|
}
|
2846
|
|
2847
|
if ($min_match && $max_match) {
|
2848
|
return TRUE;
|
2849
|
}
|
2850
|
|
2851
|
return FALSE;
|
2852
|
}
|
2853
|
|
2854
|
/**
|
2855
|
* Helper function to round minutes and seconds to requested value.
|
2856
|
*/
|
2857
|
function date_increment_round(&$date, $increment) {
|
2858
|
// Round minutes and seconds, if necessary.
|
2859
|
if (is_object($date) && $increment > 1) {
|
2860
|
$day = intval(date_format($date, 'j'));
|
2861
|
$hour = intval(date_format($date, 'H'));
|
2862
|
$second = intval(round(intval(date_format($date, 's')) / $increment) * $increment);
|
2863
|
$minute = intval(date_format($date, 'i'));
|
2864
|
if ($second == 60) {
|
2865
|
$minute += 1;
|
2866
|
$second = 0;
|
2867
|
}
|
2868
|
$minute = intval(round($minute / $increment) * $increment);
|
2869
|
if ($minute == 60) {
|
2870
|
$hour += 1;
|
2871
|
$minute = 0;
|
2872
|
}
|
2873
|
date_time_set($date, $hour, $minute, $second);
|
2874
|
if ($hour == 24) {
|
2875
|
$day += 1;
|
2876
|
$hour = 0;
|
2877
|
$year = date_format($date, 'Y');
|
2878
|
$month = date_format($date, 'n');
|
2879
|
date_date_set($date, $year, $month, $day);
|
2880
|
}
|
2881
|
}
|
2882
|
return $date;
|
2883
|
}
|
2884
|
|
2885
|
/**
|
2886
|
* Determines if a date object is valid.
|
2887
|
*
|
2888
|
* @param object $date
|
2889
|
* The date object to check.
|
2890
|
*
|
2891
|
* @return bool
|
2892
|
* TRUE if the date is a valid date object, FALSE otherwise.
|
2893
|
*/
|
2894
|
function date_is_date($date) {
|
2895
|
if (empty($date) || !is_object($date) || !empty($date->errors)) {
|
2896
|
return FALSE;
|
2897
|
}
|
2898
|
return TRUE;
|
2899
|
}
|
2900
|
|
2901
|
/**
|
2902
|
* Replace specific ISO values using patterns.
|
2903
|
*
|
2904
|
* Function will replace ISO values that have the pattern 9999-00-00T00:00:00
|
2905
|
* with a pattern like 9999-01-01T00:00:00, to match the behavior of non-ISO dates
|
2906
|
* and ensure that date objects created from this value contain a valid month
|
2907
|
* and day.
|
2908
|
* Without this fix, the ISO date '2020-00-00T00:00:00' would be created as
|
2909
|
* November 30, 2019 (the previous day in the previous month).
|
2910
|
*
|
2911
|
* @param string $iso_string
|
2912
|
* An ISO string that needs to be made into a complete, valid date.
|
2913
|
*
|
2914
|
* @return mixed|string
|
2915
|
* replaced value, or incoming value.
|
2916
|
*
|
2917
|
* @TODO Expand on this to work with all sorts of partial ISO dates.
|
2918
|
*/
|
2919
|
function date_make_iso_valid($iso_string) {
|
2920
|
// If this isn't a value that uses an ISO pattern, there is nothing to do.
|
2921
|
if (is_numeric($iso_string) || !preg_match(DATE_REGEX_ISO, $iso_string)) {
|
2922
|
return $iso_string;
|
2923
|
}
|
2924
|
// First see if month and day parts are '-00-00'.
|
2925
|
if (substr($iso_string, 4, 6) == '-00-00') {
|
2926
|
return preg_replace('/([\d]{4}-)(00-00)(T[\d]{2}:[\d]{2}:[\d]{2})/', '${1}01-01${3}', $iso_string);
|
2927
|
}
|
2928
|
// Then see if the day part is '-00'.
|
2929
|
elseif (substr($iso_string, 7, 3) == '-00') {
|
2930
|
return preg_replace('/([\d]{4}-[\d]{2}-)(00)(T[\d]{2}:[\d]{2}:[\d]{2})/', '${1}01${3}', $iso_string);
|
2931
|
}
|
2932
|
|
2933
|
// Fall through, no changes required.
|
2934
|
return $iso_string;
|
2935
|
}
|