Projet

Général

Profil

Paste
Télécharger (26,5 ko) Statistiques
| Branche: | Révision:

root / date / date_api / date_api.module @ 77885877

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
use Drupal\Core\Datetime\DrupalDateTime;
12

    
13
/**
14
 * Format options array.
15
 *
16
 * TODO Remove any formats not supported by the widget, if any.
17
 */
18
function date_datepicker_formats() {
19
  $formats = str_replace('i', 'i:s', array_keys(system_get_date_formats('short')));
20
  $formats = drupal_map_assoc($formats);
21
  return $formats;
22
}
23

    
24
function date_get_format($instance, $part = 'all') {
25
  switch ($instance['widget']['type']) {
26
    case 'date_select':
27
      $date_format = $instance['widget']['date_date_format'];
28
      $time_format = '';
29
      break;
30
    case 'date_popup':
31
      $date_format = datetime_get_format('date', $instance['widget']['date_date_format']);
32
      $time_format = datetime_get_format('date', $instance['widget']['date_time_format']);
33
      break;
34
  }
35
  switch ($part) {
36
    case 'date':
37
      return $date_format;
38
    case 'time':
39
      return $time_format;
40
    default:
41
      return trim($date_format . ' ' . $time_format);
42
  }
43
  return $format;
44
}
45

    
46
/**
47
 * Converts a date format to an ordered array of parts.
48
 *
49
 * Example:
50
 *   date_format_order('m/d/Y H:i')
51
 *   returns
52
 *     array(
53
 *       0 => 'month',
54
 *       1 => 'day',
55
 *       2 => 'year',
56
 *       3 => 'hour',
57
 *       4 => 'minute',
58
 *     );
59
 *
60
 * @param string $format
61
 *   A date format string.
62
 *
63
 * @return array
64
 *   An array of ordered elements from the given format string that
65
 *   includes only the date parts that exist in that string.
66
 */
67
function date_format_order($format) {
68
  $order = array();
69
  $max = strlen($format);
70
  for ($i = 0; $i < $max; $i++) {
71
    switch ($format[$i]) {
72
      case 'd':
73
      case 'j':
74
        $order[] = 'day';
75
        break;
76
      case 'F':
77
      case 'M':
78
      case 'm':
79
      case 'n':
80
        $order[] = 'month';
81
        break;
82
      case 'Y':
83
      case 'y':
84
        $order[] = 'year';
85
        break;
86
      case 'g':
87
      case 'G':
88
      case 'h':
89
      case 'H':
90
        $order[] = 'hour';
91
        break;
92
      case 'i':
93
        $order[] = 'minute';
94
        break;
95
      case 's':
96
        $order[] = 'second';
97
        break;
98
    }
99
  }
100
  return $order;
101
}
102

    
103
/**
104
 * Set up some constants.
105
 *
106
 * Includes standard date types, format strings, strict regex strings for ISO
107
 * and DATETIME formats (seconds are optional).
108
 *
109
 * The loose regex will find any variety of ISO date and time, with or
110
 * without time, with or without dashes and colons separating the elements,
111
 * and with either a 'T' or a space separating date and time.
112
 */
113
const DATE_ISO = 'date';
114
const DATE_UNIX = 'timestamp';
115

    
116
const DATE_FORMAT_ISO = "Y-m-d\TH:i:s";
117
const DATE_FORMAT_UNIX = "U";
118
const DATE_FORMAT_DATETIME = "Y-m-d H:i:s";
119
const DATE_FORMAT_ICAL = "Ymd\THis";
120
const DATE_FORMAT_ICAL_DATE = "Ymd";
121
const DATE_FORMAT_DATE = 'Y-m-d';
122

    
123
/**
124
 * Implements hook_help().
125
 */
126
function date_help($path, $arg) {
127
  switch ($path) {
128
    case 'admin/help#date':
129
      $output = '';
130
      if (module_exists('date_tools')) {
131
        $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')));
132
      }
133
      else {
134
        $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. ');
135
      }
136

    
137
      $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>';
138

    
139
      return $output;
140
      break;
141
  }
142
}
143

    
144
/**
145
 * Implements hook_menu().
146
 *
147
 * Creates a 'Date API' section on the administration page for Date
148
 * modules to use for their configuration and settings.
149
 */
150
function date_api_menu() {
151
  $items['admin/config/date'] = array(
152
    'title' => 'Date API',
153
    'description' => 'Settings for modules the use the Date API.',
154
    'position' => 'left',
155
    'weight' => -10,
156
    'page callback' => 'system_admin_menu_block_page',
157
    'access arguments' => array('administer site configuration'),
158
    'file' => 'system.admin.inc',
159
    'file path' => drupal_get_path('module', 'system'),
160
  );
161
  return $items;
162
}
163

    
164
/**
165
 * Determines if the date element needs to be processed.
166
 *
167
 * Helper function to see if date element has been hidden by FAPI to see if it
168
 * needs to be processed or just pass the value through. This is needed since
169
 * normal date processing explands the date element into parts and then
170
 * reconstructs it, which is not needed or desirable if the field is hidden.
171
 *
172
 * @param array $element
173
 *   The date element to check.
174
 *
175
 * @return bool
176
 *   TRUE if the element is effectively hidden, FALSE otherwise.
177
 */
178
function date_hidden_element($element) {
179
  // @TODO What else needs to be tested to see if dates are hidden or disabled?
180
  if ((isset($element['#access']) && empty($element['#access']))
181
    || !empty($element['#programmed'])
182
    || in_array($element['#type'], array('hidden', 'value'))) {
183
    return TRUE;
184
  }
185
  return FALSE;
186
}
187

    
188
/**
189
 * Helper function for getting the format string for a date type.
190
 *
191
 * @param string $type
192
 *   A date type format name.
193
 *
194
 * @return string
195
 *   A date type format, like 'Y-m-d H:i:s'.
196
 */
197
function date_type_format($type) {
198
  switch ($type) {
199
    case DATE_ISO:
200
      return DATE_FORMAT_ISO;
201
    case DATE_UNIX:
202
      return DATE_FORMAT_UNIX;
203
    case DATE_DATETIME:
204
      return DATE_FORMAT_DATETIME;
205
    case DATE_ICAL:
206
      return DATE_FORMAT_ICAL;
207
  }
208
}
209

    
210
/**
211
 * Formats a time interval with granularity, including past and future context.
212
 *
213
 * @param object $date
214
 *   The current date object.
215
 * @param int $granularity
216
 *   (optional) Number of units to display in the string. Defaults to 2.
217
 *
218
 * @return string
219
 *   A translated string representation of the interval.
220
 *
221
 * @see format_interval()
222
 */
223
function date_format_interval($date, $granularity = 2, $display_ago = TRUE) {
224
  // If no date is sent, then return nothing.
225
  if (empty($date)) {
226
    return NULL;
227
  }
228

    
229
  $interval = REQUEST_TIME - $date->format('U');
230
  if ($interval > 0) {
231
    return $display_ago ? t('!time ago', array('!time' => format_interval($interval, $granularity))) :
232
      t('!time', array('!time' => format_interval($interval, $granularity)));
233
  }
234
  else {
235
    return format_interval(abs($interval), $granularity);
236
  }
237
}
238

    
239
/**
240
 * Implements hook_element_info().
241
 */
242
function date_api_element_info() {
243
  module_load_include('inc', 'date_api', 'date_api_elements');
244
  return _date_api_element_info();
245
}
246

    
247
/**
248
 * Implements hook_theme().
249
 */
250
function date_api_theme($existing, $type, $theme, $path) {
251

    
252
  $base = array(
253
    'file' => 'theme.inc',
254
    'path' => "$path/theme",
255
  );
256
  return array(
257
    'date_nav_title' => $base + array('variables' => array('granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL)),
258
    'date_timezone' => $base + array('render element' => 'element'),
259
    'date_select' => $base + array('render element' => 'element'),
260
    'date_select_element' => $base + array('render element' => 'element'),
261
    'date_textfield_element' => $base + array('render element' => 'element'),
262
    'date_part_hour_prefix' => $base + array('render element' => 'element'),
263
    'date_part_minsec_prefix' => $base + array('render element' => 'element'),
264
    'date_part_label_year' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
265
    'date_part_label_month' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
266
    'date_part_label_day' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
267
    'date_part_label_hour' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
268
    'date_part_label_minute' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
269
    'date_part_label_second' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
270
    'date_part_label_ampm' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
271
    'date_part_label_timezone' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
272
    'date_part_label_date' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
273
    'date_part_label_time' => $base + array('variables' => array('date_part' => NULL, 'element' => NULL)),
274
    'date_views_filter_form' => $base + array('template' => 'date-views-filter-form', 'render element' => 'form'),
275
    'date_calendar_day' => $base + array('variables' => array('date' => NULL)),
276
    'date_time_ago' => $base + array('variables' => array('start_date' => NULL, 'end_date' => NULL, 'interval' => NULL)),
277
    'datelist' => array(
278
      'render element' => 'element',
279
    ),
280
  );
281
}
282

    
283
/**
284
 * Returns HTML for a date selection form element.
285
 *
286
 * @param $variables
287
 *   An associative array containing:
288
 *   - element: An associative array containing the properties of the element.
289
 *     Properties used: #title, #value, #options, #description, #required,
290
 *     #attributes.
291
 *
292
 * @ingroup themeable
293
 */
294
function theme_datelist($variables) {
295

    
296
  $element = $variables['element'];
297

    
298
  $attributes = array();
299
  if (isset($element['#id'])) {
300
    $attributes['id'] = $element['#id'];
301
  }
302
  if (!empty($element['#attributes']['class'])) {
303
    $attributes['class'] = (array) $element['#attributes']['class'];
304
  }
305
  $attributes['class'][] = 'container-inline';
306

    
307
  return '<div' . new Attribute($attributes) . '>' . drupal_render_children($element) . '</div>';
308
}
309

    
310
/**
311
 * Function to figure out which local timezone applies to a date and select it.
312
 *
313
 * @param string $handling
314
 *   The timezone handling.
315
 * @param string $timezone
316
 *   (optional) A timezone string. Defaults to an empty string.
317
 *
318
 * @return string
319
 *   The timezone string.
320
 */
321
function date_get_timezone($handling, $timezone = '') {
322
  switch ($handling) {
323
    case 'date':
324
      $timezone = !empty($timezone) ? $timezone : drupal_get_user_timezone();
325
      break;
326
    case 'utc':
327
      $timezone = 'UTC';
328
      break;
329
    default:
330
      $timezone = drupal_get_user_timezone();
331
  }
332
  return $timezone > '' ? $timezone : drupal_get_user_timezone();
333
}
334

    
335
/**
336
 * Function to figure out which db timezone applies to a date and select it.
337
 *
338
 * @param string $handling
339
 *   The timezone handling.
340
 * @param string $timezone
341
 *   (optional) A timezone string. Defaults to an empty string.
342
 *
343
 * @return string
344
 *   The timezone string.
345
 */
346
function date_get_timezone_db($handling, $timezone = '') {
347
  switch ($handling) {
348
    case 'none':
349
      $timezone = drupal_get_user_timezone();
350
      break;
351
    default:
352
      $timezone = 'UTC';
353
      break;
354
  }
355
  return $timezone > '' ? $timezone : 'UTC';
356
}
357

    
358
/**
359
 * Helper function for converting back and forth from '+1' to 'First'.
360
 */
361
function date_order_translated() {
362
  return array(
363
    '+1' => t('First', array(), array('context' => 'date_order')),
364
    '+2' => t('Second', array(), array('context' => 'date_order')),
365
    '+3' => t('Third', array(), array('context' => 'date_order')),
366
    '+4' => t('Fourth', array(), array('context' => 'date_order')),
367
    '+5' => t('Fifth', array(), array('context' => 'date_order')),
368
    '-1' => t('Last', array(), array('context' => 'date_order_reverse')),
369
    '-2' => t('Next to last', array(), array('context' => 'date_order_reverse')),
370
    '-3' => t('Third from last', array(), array('context' => 'date_order_reverse')),
371
    '-4' => t('Fourth from last', array(), array('context' => 'date_order_reverse')),
372
    '-5' => t('Fifth from last', array(), array('context' => 'date_order_reverse')),
373
  );
374
}
375

    
376
/**
377
 * Creates an array of ordered strings, using English text when possible.
378
 */
379
function date_order() {
380
  return array(
381
    '+1' => 'First',
382
    '+2' => 'Second',
383
    '+3' => 'Third',
384
    '+4' => 'Fourth',
385
    '+5' => 'Fifth',
386
    '-1' => 'Last',
387
    '-2' => '-2',
388
    '-3' => '-3',
389
    '-4' => '-4',
390
    '-5' => '-5',
391
  );
392
}
393

    
394
/**
395
 * Tests validity of a date range string.
396
 *
397
 * @param string $string
398
 *   A min and max year string like '-3:+1'a.
399
 *
400
 * @return bool
401
 *   TRUE if the date range is valid, FALSE otherwise.
402
 */
403
function date_range_valid($string) {
404
  $matches = preg_match('@^(\-[0-9]+|[0-9]{4}):([\+|\-][0-9]+|[0-9]{4})$@', $string);
405
  return $matches < 1 ? FALSE : TRUE;
406
}
407

    
408
/**
409
 * Converts a min and max year into a string like '-3:+1'.
410
 *
411
 * @param array $years
412
 *   A numerically indexed array, containing a minimum and maximum year.
413
 *
414
 * @return string
415
 *   A min and max year string like '-3:+1'.
416
 */
417
function date_range_string($years) {
418
  $this_year = date_format(new DrupalDateTime(), 'Y');
419

    
420
  if ($years[0] < $this_year) {
421
    $min = '-' . ($this_year - $years[0]);
422
  }
423
  else {
424
    $min = '+' . ($years[0] - $this_year);
425
  }
426

    
427
  if ($years[1] < $this_year) {
428
    $max = '-' . ($this_year - $years[1]);
429
  }
430
  else {
431
    $max = '+' . ($years[1] - $this_year);
432
  }
433

    
434
  return $min . ':' . $max;
435
}
436

    
437
/**
438
 * Temporary helper to re-create equivalent of content_database_info().
439
 */
440
function date_api_database_info($field, $revision = FIELD_LOAD_CURRENT) {
441
  return array(
442
    'columns' => $field['storage']['details']['sql'][$revision],
443
    'table' => _field_sql_storage_tablename($field),
444
  );
445
}
446

    
447
/**
448
 * Implements hook_form_FORM_ID_alter() for system_regional_settings().
449
 *
450
 * Add a form element to configure whether or not week numbers are ISO-8601, the
451
 * default is FALSE (US/UK/AUS norm).
452
 */
453
function date_api_form_system_regional_settings_alter(&$form, &$form_state, $form_id) {
454
  $form['locale']['date_api_iso8601'] = array(
455
    '#type' => 'checkbox',
456
    '#title' => t('Use ISO-8601 week numbers'),
457
    '#default_value' => config('date_api.settings')->get('iso8601'),
458
    '#description' => t('IMPORTANT! If checked, First day of week MUST be set to Monday'),
459
  );
460
  $form['#validate'][] = 'date_api_form_system_settings_validate';
461
  $form['#submit'][] = 'date_api_form_system_settings_submit';
462
}
463

    
464
/**
465
 * Validate that the option to use ISO weeks matches first day of week choice.
466
 */
467
function date_api_form_system_settings_validate(&$form, &$form_state) {
468
  $form_values = $form_state['values'];
469
  if ($form_values['date_api_iso8601'] && $form_values['date_first_day'] != 1) {
470
    form_set_error('date_first_day', t('When using ISO-8601 week numbers, the first day of the week must be set to Monday.'));
471
  }
472
}
473

    
474
/**
475
 * Store the Date API 8601 week numbers setting.
476
 */
477
function date_api_form_system_settings_submit($form, &$form_state) {
478
  $form_values = $form_state['values'];
479
  config('date_api.settings')->set('iso8601', $form_values['date_api_iso8601'])->save();
480
}
481

    
482
/**
483
 * Creates an array of date format types for use as an options list.
484
 */
485
function date_format_type_options() {
486
  $options = array();
487
  $time = date_example_date();
488
  $format_types = system_get_date_types();
489
  if (!empty($format_types)) {
490
    foreach ($format_types as $type => $type_info) {
491
      $options[$type] = $type_info['title'] . ' (' . format_date($time->format('U'), $type) . ')';
492
    }
493
  }
494
  return $options;
495
}
496

    
497
/**
498
 * Creates an example date.
499
 *
500
 * This ensures a clear difference between month and day, and 12 and 24 hours.
501
 */
502
function date_example_date() {
503
  $now = new DrupalDateTime();
504
  if (date_format($now, 'M') == date_format($now, 'F')) {
505
    date_modify($now, '+1 month');
506
  }
507
  if (date_format($now, 'm') == date_format($now, 'd')) {
508
    date_modify($now, '+1 day');
509
  }
510
  if (date_format($now, 'H') == date_format($now, 'h')) {
511
    date_modify($now, '+12 hours');
512
  }
513
  return $now;
514
}
515

    
516
/**
517
 * Determine if a start/end date combination qualify as 'All day'.
518
 *
519
 * @param string $string1
520
 *   A string date in datetime format for the 'start' date.
521
 * @param string $string2
522
 *   A string date in datetime format for the 'end' date.
523
 * @param string $granularity
524
 *   (optional) The granularity of the date. Defaults to 'second'.
525
 * @param int $increment
526
 *   (optional) The increment of the date. Defaults to 1.
527
 *
528
 * @return bool
529
 *   TRUE if the date is all day, FALSE otherwise.
530
 */
531
function date_is_all_day($string1, $string2, $granularity = 'second', $increment = 1) {
532
  if (empty($string1) || empty($string2)) {
533
    return FALSE;
534
  }
535
  elseif (!in_array($granularity, array('hour', 'minute', 'second'))) {
536
    return FALSE;
537
  }
538

    
539
  preg_match('/([0-9]{4}-[0-9]{2}-[0-9]{2}) (([0-9]{2}):([0-9]{2}):([0-9]{2}))/', $string1, $matches);
540
  $count = count($matches);
541
  $date1 = $count > 1 ? $matches[1] : '';
542
  $time1 = $count > 2 ? $matches[2] : '';
543
  $hour1 = $count > 3 ? intval($matches[3]) : 0;
544
  $min1 = $count > 4 ? intval($matches[4]) : 0;
545
  $sec1 = $count > 5 ? intval($matches[5]) : 0;
546
  preg_match('/([0-9]{4}-[0-9]{2}-[0-9]{2}) (([0-9]{2}):([0-9]{2}):([0-9]{2}))/', $string2, $matches);
547
  $count = count($matches);
548
  $date2 = $count > 1 ? $matches[1] : '';
549
  $time2 = $count > 2 ? $matches[2] : '';
550
  $hour2 = $count > 3 ? intval($matches[3]) : 0;
551
  $min2 = $count > 4 ? intval($matches[4]) : 0;
552
  $sec2 = $count > 5 ? intval($matches[5]) : 0;
553
  if (empty($date1) || empty($date2)) {
554
    return FALSE;
555
  }
556
  if (empty($time1) || empty($time2)) {
557
    return FALSE;
558
  }
559

    
560
  $calendar = system_calendar();
561
  $tmp = $calendar->seconds('s', TRUE, $increment);
562
  $max_seconds = intval(array_pop($tmp));
563
  $tmp = $calendar->minutes('i', TRUE, $increment);
564
  $max_minutes = intval(array_pop($tmp));
565

    
566
  // See if minutes and seconds are the maximum allowed for an increment or the
567
  // maximum possible (59), or 0.
568
  switch ($granularity) {
569
    case 'second':
570
      $min_match = $time1 == '00:00:00'
571
        || ($hour1 == 0 && $min1 == 0 && $sec1 == 0);
572
      $max_match = $time2 == '00:00:00'
573
        || ($hour2 == 23 && in_array($min2, array($max_minutes, 59)) && in_array($sec2, array($max_seconds, 59)))
574
        || ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0 && $sec1 == 0 && $sec2 == 0);
575
      break;
576
    case 'minute':
577
      $min_match = $time1 == '00:00:00'
578
        || ($hour1 == 0 && $min1 == 0);
579
      $max_match = $time2 == '00:00:00'
580
        || ($hour2 == 23 && in_array($min2, array($max_minutes, 59)))
581
        || ($hour1 == 0 && $hour2 == 0 && $min1 == 0 && $min2 == 0);
582
      break;
583
    case 'hour':
584
      $min_match = $time1 == '00:00:00'
585
        || ($hour1 == 0);
586
      $max_match = $time2 == '00:00:00'
587
        || ($hour2 == 23)
588
        || ($hour1 == 0 && $hour2 == 0);
589
      break;
590
    default:
591
      $min_match = TRUE;
592
      $max_match = FALSE;
593
  }
594

    
595
  if ($min_match && $max_match) {
596
    return TRUE;
597
  }
598

    
599
  return FALSE;
600
}
601

    
602
/**
603
 * Helper function to round minutes and seconds to requested value.
604
 */
605
function date_increment_round(&$date, $increment) {
606
  // Round minutes and seconds, if necessary.
607
  if ($date instanceOf DrupalDateTime && $increment > 1) {
608
    $day = intval(date_format($date, 'j'));
609
    $hour = intval(date_format($date, 'H'));
610
    $second = intval(round(intval(date_format($date, 's')) / $increment) * $increment);
611
    $minute = intval(date_format($date, 'i'));
612
    if ($second == 60) {
613
      $minute += 1;
614
      $second = 0;
615
    }
616
    $minute = intval(round($minute / $increment) * $increment);
617
    if ($minute == 60) {
618
      $hour += 1;
619
      $minute = 0;
620
    }
621
    date_time_set($date, $hour, $minute, $second);
622
    if ($hour == 24) {
623
      $day += 1;
624
      $hour = 0;
625
      $year = date_format($date, 'Y');
626
      $month = date_format($date, 'n');
627
      date_date_set($date, $year, $month, $day);
628
    }
629
  }
630
  return $date;
631
}
632

    
633
/**
634
 * This function will replace ISO values that have the pattern 9999-00-00T00:00:00
635
 * with a pattern like 9999-01-01T00:00:00, to match the behavior of non-ISO
636
 * dates and ensure that date objects created from this value contain a valid month
637
 * and day. Without this fix, the ISO date '2020-00-00T00:00:00' would be created as
638
 * November 30, 2019 (the previous day in the previous month).
639
 *
640
 * @param string $iso_string
641
 *   An ISO string that needs to be made into a complete, valid date.
642
 *
643
 * @TODO Expand on this to work with all sorts of partial ISO dates.
644
 */
645
function date_make_iso_valid($iso_string) {
646
  // If this isn't a value that uses an ISO pattern, there is nothing to do.
647
  if (is_numeric($iso_string) || !preg_match(DATE_REGEX_ISO, $iso_string)) {
648
    return $iso_string;
649
  }
650
  // First see if month and day parts are '-00-00'.
651
  if (substr($iso_string, 4, 6) == '-00-00') {
652
    return preg_replace('/([\d]{4}-)(00-00)(T[\d]{2}:[\d]{2}:[\d]{2})/', '${1}01-01${3}', $iso_string);
653
  }
654
  // Then see if the day part is '-00'.
655
  elseif (substr($iso_string, 7, 3) == '-00') {
656
    return preg_replace('/([\d]{4}-[\d]{2}-)(00)(T[\d]{2}:[\d]{2}:[\d]{2})/', '${1}01${3}', $iso_string);
657
  }
658

    
659
  // Fall through, no changes required.
660
  return $iso_string;
661
}
662

    
663

    
664
/**
665
 * @file
666
 * SQL helper for Date API.
667
 *
668
 * @TODO
669
 * Add experimental support for sqlite: http://www.sqlite.org/lang_datefunc.html
670
 * and Oracle (http://psoug.org/reference/date_func.html and
671
 * http://psoug.org/reference/datatypes.html) date/time functions.
672
 */
673

    
674
/**
675
 * A helper function to do cross-database concatation of date parts.
676
 *
677
 * @param array $array
678
 *   An array of values to be concatonated in sql.
679
 *
680
 * @return string
681
 *   Correct sql string for database type.
682
 */
683
function date_sql_concat($array) {
684
  switch (db_driver()) {
685
    case 'mysql':
686
    case 'mysqli':
687
      return "CONCAT(" . implode(",", $array) . ")";
688
    case 'pgsql':
689
      return implode(" || ", $array);
690
  }
691
}
692

    
693
/**
694
 * Helper function to do cross-database NULL replacements
695
 *
696
 * @param array $array
697
 *   An array of values to test for NULL values.
698
 *
699
 * @return string
700
 *   SQL statement to return the first non-NULL value in the list.
701
 */
702
function date_sql_coalesce($array) {
703
  switch (db_driver()) {
704
    case 'mysql':
705
    case 'mysqli':
706
    case 'pgsql':
707
      return "COALESCE(" . implode(',', $array) . ")";
708
  }
709
}
710

    
711
/**
712
 * A helper function to do cross-database padding of date parts.
713
 *
714
 * @param string $str
715
 *   A string to apply padding to
716
 * @param int $size
717
 *   The size the final string should be
718
 * @param string $pad
719
 *   The value to pad the string with
720
 * @param string $side
721
 *   The side of the string to pad
722
 */
723
function date_sql_pad($str, $size = 2, $pad = '0', $side = 'l') {
724
  switch ($side) {
725
    case 'r':
726
      return "RPAD($str, $size, '$pad')";
727
    default:
728
      return "LPAD($str, $size, '$pad')";
729
  }
730
}
731

    
732
/**
733
   * Calculates the start and end dates for a calendar week.
734
   *
735
   * The dates are adjusted to use the chosen first day of week
736
   * for this site.
737
   *
738
   * @param int $week
739
   *   The week value.
740
   * @param int $year
741
   *   The year value.
742
   *
743
   * @return array
744
   *   An array containing the start and end dates of a week.
745
   */
746
function date_calendar_week_range($week, $year) {
747
  $min_date = new DrupalDateTime($year . '-01-01 00:00:00');
748

    
749
  // Move to the right week.
750
  date_modify($min_date, '+' . strval(7 * ($week - 1)) . ' days');
751

    
752
  // Move backwards to the first day of the week.
753
  $first_day = variable_get('date_first_day', 0);
754
  $day_wday = date_format($min_date, 'w');
755
  date_modify($min_date, '-' . strval((7 + $day_wday - $first_day) % 7) . ' days');
756

    
757
  // Move forwards to the last day of the week.
758
  $max_date = clone($min_date);
759
  date_modify($max_date, '+7 days');
760

    
761
  if (date_format($min_date, 'Y') != $year) {
762
    $min_date = new DrupalDateTime($year . '-01-01 00:00:00');
763
  }
764
  return array($min_date, $max_date);
765
}
766

    
767
/**
768
 * Calculates the start and end dates for an ISO week.
769
 *
770
 * @param int $week
771
 *   The week value.
772
 * @param int $year
773
 *   The year value.
774
 *
775
 * @return array
776
 *   An array containing the start and end dates of an
777
 *   ISO week.
778
 */
779
function date_iso_week_range($week, $year) {
780
  // Get to the last ISO week of the previous year.
781
  $min_date = new DrupalDateTime(($year - 1) . '-12-28 00:00:00');
782

    
783
  // Find the first day of the first ISO week in the year.
784
  date_modify($min_date, '+1 Monday');
785

    
786
  // Jump ahead to the desired week for the beginning of the week range.
787
  if ($week > 1) {
788
    date_modify($min_date, '+ ' . ($week - 1) . ' weeks');
789
  }
790

    
791
  // Move forwards to the last day of the week.
792
  $max_date = clone($min_date);
793
  date_modify($max_date, '+7 days');
794
  return array($min_date, $max_date);
795
}
796

    
797
/**
798
 * The number of calendar weeks in a year.
799
 *
800
 * PHP week functions return the ISO week, not the calendar week.
801
 *
802
 * @param int $year
803
 *   A year value.
804
 *
805
 * @return int
806
 *   Number of calendar weeks in selected year.
807
 */
808
function date_weeks_in_year($year) {
809
  $date = new DrupalDateTime(($year + 1) . '-01-01 12:00:00', 'UTC');
810
  date_modify($date, '-1 day');
811
  return self::calendar_week($date->format('Y-m-d'));
812
}
813

    
814
 /**
815
 * Identifies the number of ISO weeks in a year for a date.
816
 *
817
 * December 28 is always in the last ISO week of the year.
818
 *
819
 * @param mixed $date
820
 *   (optional) A date object, timestamp, or a date string.
821
 *   Defaults to current date.
822
 *
823
 * @return integer
824
 *   The number of ISO weeks in a year.
825
 */
826
function date_iso_weeks_in_year($date = NULL) {
827
  if (!$date instanceOf DrupalDateTime) {
828
    $date = new DrupalDateTime($date);
829
  }
830
  if ($date instanceOf DrupalDateTime && !$date->hasErrors()) {
831
    date_date_set($date, $date->format('Y'), 12, 28);
832
    return $date->format('W');
833
  }
834
  return NULL;
835
}
836

    
837
/**
838
 * The calendar week number for a date.
839
 *
840
 * PHP week functions return the ISO week, not the calendar week.
841
 *
842
 * @param string $date
843
 *   A date string in the format Y-m-d.
844
 *
845
 * @return int
846
 *   The calendar week number.
847
 */
848
function date_calendar_week($date) {
849
  $date = substr($date, 0, 10);
850
  $parts = explode('-', $date);
851

    
852
  $date = new DrupalDateTime($date . ' 12:00:00', 'UTC');
853

    
854
  // If we are using ISO weeks, this is easy.
855
  if (config('date_api.settings')->get('iso8601')) {
856
    return intval($date->format('W'));
857
  }
858

    
859
  $year_date = new DrupalDateTime($parts[0] . '-01-01 12:00:00', 'UTC');
860
  $week = intval($date->format('W'));
861
  $year_week = intval(date_format($year_date, 'W'));
862
  $date_year = intval($date->format('o'));
863

    
864
  // Remove the leap week if it's present.
865
  if ($date_year > intval($parts[0])) {
866
    $last_date = clone($date);
867
    date_modify($last_date, '-7 days');
868
    $week = date_format($last_date, 'W') + 1;
869
  }
870
  elseif ($date_year < intval($parts[0])) {
871
    $week = 0;
872
  }
873

    
874
  if ($year_week != 1) {
875
    $week++;
876
  }
877

    
878
  // Convert to ISO-8601 day number, to match weeks calculated above.
879
  $iso_first_day = 1 + (variable_get('date_first_day', 0) + 6) % 7;
880

    
881
  // If it's before the starting day, it's the previous week.
882
  if (intval($date->format('N')) < $iso_first_day) {
883
    $week--;
884
  }
885

    
886
  // If the year starts before, it's an extra week at the beginning.
887
  if (intval(date_format($year_date, 'N')) < $iso_first_day) {
888
    $week++;
889
  }
890

    
891
  return $week;
892
}