/[drupal]/contributions/modules/date/date_api.module
ViewVC logotype

Contents of /contributions/modules/date/date_api.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.72 - (show annotations) (download) (as text)
Tue Sep 8 14:19:24 2009 UTC (2 months, 2 weeks ago) by karens
Branch: MAIN
CVS Tags: HEAD
Changes since 1.71: +28 -24 lines
File MIME type: text/x-php
Lots of renaming of field and instance values to match the new array structures.
1 <?php
2 // $Id: date_api.module,v 1.71 2009/09/08 08:52:18 karens Exp $
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(\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 * Implementation of hook_init().
43 */
44 function date_api_init() {
45 drupal_add_css(drupal_get_path('module', 'date_api') . '/date.css', array('weight' => CSS_THEME));
46 }
47
48 /**
49 * Implementation of hook_menu().
50 */
51 function date_api_menu() {
52 $items = array();
53 $items['admin/config/regional/settings/formats'] = array(
54 'title' => 'Formats',
55 'description' => 'Allow users to configure date formats',
56 'type' => MENU_LOCAL_TASK,
57 'file' => 'date_api.admin.inc',
58 'page callback' => 'drupal_get_form',
59 'page arguments' => array('date_api_date_formats_form'),
60 'access arguments' => array('administer site configuration'),
61 'weight' => 1,
62 );
63 $items['admin/config/regional/settings/formats/configure'] = array(
64 'title' => 'Configure',
65 'description' => 'Allow users to configure date formats',
66 'type' => MENU_DEFAULT_LOCAL_TASK,
67 'file' => 'date_api.admin.inc',
68 'page callback' => 'drupal_get_form',
69 'page arguments' => array('date_api_date_formats_form'),
70 'access arguments' => array('administer site configuration'),
71 'weight' => 1,
72 );
73 $items['admin/config/regional/settings/formats/lookup'] = array(
74 'title' => 'Date and time lookup',
75 'type' => MENU_CALLBACK,
76 'file' => 'date_api.admin.inc',
77 'page callback' => 'date_api_date_time_lookup',
78 'access arguments' => array('administer site configuration'),
79 );
80 $items['admin/config/regional/settings/formats/custom'] = array(
81 'title' => 'Custom formats',
82 'description' => 'Allow users to configure custom date formats. ',
83 'type' => MENU_LOCAL_TASK,
84 'file' => 'date_api.admin.inc',
85 'page callback' => 'date_api_configure_custom_date_formats',
86 'access arguments' => array('administer site configuration'),
87 'weight' => 2,
88 );
89 $items['admin/config/regional/settings/formats/add'] = array(
90 'title' => 'Add format',
91 'description' => 'Allow users to add additional date formats. ',
92 'type' => MENU_LOCAL_TASK,
93 'file' => 'date_api.admin.inc',
94 'page callback' => 'drupal_get_form',
95 'page arguments' => array('date_api_add_date_formats_form'),
96 'access arguments' => array('administer site configuration'),
97 'weight' => 3,
98 );
99 $items['admin/config/regional/settings/formats/delete/%'] = array(
100 'title' => 'Delete date format',
101 'description' => 'Allow users to delete a configured date format. ',
102 'type' => MENU_CALLBACK,
103 'file' => 'date_api.admin.inc',
104 'page callback' => 'drupal_get_form',
105 'page arguments' => array('date_api_delete_format_form', 5),
106 'access arguments' => array('administer site configuration'),
107 );
108 $items['admin/config/regional/settings/delete/%'] = array(
109 'title' => 'Delete date format type',
110 'description' => 'Allow users to delete a configured date format type. ',
111 'type' => MENU_CALLBACK,
112 'file' => 'date_api.admin.inc',
113 'page callback' => 'drupal_get_form',
114 'page arguments' => array('date_api_delete_format_type_form', 4),
115 'access arguments' => array('administer site configuration'),
116 );
117 return $items;
118 }
119
120 /**
121 * Implementation of hook_menu_alter().
122 */
123 function date_api_menu_alter(&$callbacks) {
124 // Add a new 'admin/config/regional/settings/configure' path and make it the same as
125 // the 'admin/config/regional/settings' . Also set it to be the default local task -
126 // needed to make tabs work as expected.
127 $callbacks['admin/config/regional/settings/configure'] = $callbacks['admin/config/regional/settings'];
128 $callbacks['admin/config/regional/settings/configure']['type'] = MENU_DEFAULT_LOCAL_TASK;
129 }
130
131 /**
132 * Helper function for getting the format string for a date type.
133 */
134 function date_type_format($type) {
135 switch ($type) {
136 case DATE_ISO:
137 return DATE_FORMAT_ISO;
138 case DATE_UNIX:
139 return DATE_FORMAT_UNIX;
140 case DATE_DATETIME:
141 return DATE_FORMAT_DATETIME;
142 case DATE_ICAL:
143 return DATE_FORMAT_ICAL;
144 }
145 }
146
147 /**
148 * An untranslated array of month names
149 *
150 * Needed for css, translation functions, strtotime(), and other places
151 * that use the English versions of these words.
152 *
153 * @return
154 * an array of month names
155 */
156 function date_month_names_untranslated() {
157 static $month_names;
158 if (empty($month_names)) {
159 $month_names = array(1 => 'January', 2 => 'February', 3 => 'March',
160 4 => 'April', 5 => 'May', 6 => 'June', 7 => 'July',
161 8 => 'August', 9 => 'September', 10 => 'October',
162 11 => 'November', 12 => 'December');
163 }
164 return $month_names;
165 }
166
167 /**
168 * A translated array of month names
169 *
170 * @param $required
171 * If not required, will include a blank value at the beginning of the list.
172 * @return
173 * an array of month names
174 */
175 function date_month_names($required = FALSE) {
176 $month_names = array();
177 foreach (date_month_names_untranslated() as $key => $day) {
178 $month_names[$key] = date_t($day, 'month_name');
179 }
180 $none = array('' => '');
181 return !$required ? $none + $month_names : $month_names;
182 }
183
184 /**
185 * A translated array of month name abbreviations
186 *
187 * @param $required
188 * If not required, will include a blank value at the beginning of the list.
189 * @return
190 * an array of month abbreviations
191 */
192 function date_month_names_abbr($required = FALSE) {
193 $month_names = array();
194 foreach (date_month_names_untranslated() as $key => $day) {
195 $month_names[$key] = date_t($day, 'month_abbr');
196 }
197 $none = array('' => '');
198 return !$required ? $none + $month_names : $month_names;
199 }
200
201 /**
202 * An untranslated array of week days
203 *
204 * Needed for css, translation functions, strtotime(), and other places
205 * that use the English versions of these words.
206 *
207 * @return
208 * an array of week day names
209 */
210 function date_week_days_untranslated($refresh = TRUE) {
211 static $weekdays;
212 if ($refresh || empty($weekdays)) {
213 $weekdays = array(0 => 'Sunday', 1 => 'Monday', 2 => 'Tuesday',
214 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday',
215 6 => 'Saturday');
216 }
217 return $weekdays;
218 }
219
220 /**
221 * A translated array of week days
222 *
223 * @param $required
224 * If not required, will include a blank value at the beginning of the array.
225 * @return
226 * an array of week day names
227 */
228 function date_week_days($required = FALSE, $refresh = TRUE) {
229 $weekdays = array();
230 foreach (date_week_days_untranslated() as $key => $day) {
231 $weekdays[$key] = date_t($day, 'day_name');
232 }
233 $none = array('' => '');
234 return !$required ? $none + $weekdays : $weekdays;
235 }
236
237 /**
238 * An translated array of week day abbreviations.
239 *
240 * @param $required
241 * If not required, will include a blank value at the beginning of the array.
242 * @return
243 * an array of week day abbreviations
244 */
245 function date_week_days_abbr($required = FALSE, $refresh = TRUE, $length = 3) {
246 $weekdays = array();
247 switch ($length) {
248 case 1:
249 $context = 'day_abbr1';
250 break;
251 case 2:
252 $context = 'day_abbr2';
253 break;
254 default:
255 $context = 'day_abbr';
256 break;
257 }
258 foreach (date_week_days_untranslated() as $key => $day) {
259 $weekdays[$key] = date_t($day, $context);
260 }
261 $none = array('' => '');
262 return !$required ? $none + $weekdays : $weekdays;
263 }
264
265 /**
266 * Order weekdays
267 * Correct weekdays array so first day in array matches the first day of
268 * the week. Use to create things like calendar headers.
269 *
270 * @param array $weekdays
271 * @return array
272 */
273 function date_week_days_ordered($weekdays) {
274 if (variable_get('date_first_day', 1) > 0) {
275 for ($i = 1; $i <= variable_get('date_first_day', 1); $i++) {
276 $last = array_shift($weekdays);
277 array_push($weekdays, $last);
278 }
279 }
280 return $weekdays;
281 }
282
283 /**
284 * An array of years.
285 *
286 * @param int $min
287 * the minimum year in the array
288 * @param int $max
289 * the maximum year in the array
290 * @param $required
291 * If not required, will include a blank value at the beginning of the array.
292 * @return
293 * an array of years in the selected range
294 */
295 function date_years($min = 0, $max = 0, $required = FALSE) {
296 // Have to be sure $min and $max are valid values;
297 if (empty($min)) $min = intval(date('Y', REQUEST_TIME) - 3);
298 if (empty($max)) $max = intval(date('Y', REQUEST_TIME) + 3);
299 $none = array(0 => '');
300 return !$required ? $none + drupal_map_assoc(range($min, $max)) : drupal_map_assoc(range($min, $max));
301 }
302
303 /**
304 * An array of days.
305 *
306 * @param $required
307 * If not required, returned array will include a blank value.
308 * @param integer $month (optional)
309 * @param integer $year (optional)
310 * @return
311 * an array of days for the selected month.
312 */
313 function date_days($required = FALSE, $month = NULL, $year = NULL) {
314 // If we have a month and year, find the right last day of the month.
315 if (!empty($month) && !empty($year)) {
316 $date = date_make_date($year . '-' . $month . '-01 00:00:00', 'UTC');
317 $max = date_format('t', $date);
318 }
319 // If there is no month and year given, default to 31.
320 if (empty($max)) $max = 31;
321 $none = array(0 => '');
322 return !$required ? $none + drupal_map_assoc(range(1, $max)) : drupal_map_assoc(range(1, $max));
323 }
324
325 /**
326 * An array of hours.
327 *
328 * @param string $format
329 * @param $required
330 * If not required, returned array will include a blank value.
331 * @return
332 * an array of hours in the selected format.
333 */
334 function date_hours($format = 'H', $required = FALSE) {
335 $hours = array();
336 if ($format == 'h' || $format == 'g') {
337 $min = 1;
338 $max = 12;
339 }
340 else {
341 $min = 0;
342 $max = 23;
343 }
344 for ($i = $min; $i <= $max; $i++) {
345 $hours[$i] = $i < 10 && ($format == 'H' || $format == 'h') ? "0$i" : $i;
346 }
347 $none = array('' => '');
348 return !$required ? $none + $hours : $hours;
349 }
350
351 /**
352 * An array of minutes.
353 *
354 * @param string $format
355 * @param $required
356 * If not required, returned array will include a blank value.
357 * @return
358 * an array of minutes in the selected format.
359 */
360 function date_minutes($format = 'i', $required = FALSE, $increment = 1) {
361 $minutes = array();
362 // Have to be sure $increment has a value so we don't loop endlessly;
363 if (empty($increment)) $increment = 1;
364 for ($i = 0; $i < 60; $i += $increment) {
365 $minutes[$i] = $i < 10 && $format == 'i' ? "0$i" : $i;
366 }
367 $none = array('' => '');
368 return !$required ? $none + $minutes : $minutes;
369 }
370
371 /**
372 * An array of seconds.
373 *
374 * @param string $format
375 * @param $required
376 * If not required, returned array will include a blank value.
377 * @return array an array of seconds in the selected format.
378 */
379 function date_seconds($format = 's', $required = FALSE, $increment = 1) {
380 $seconds = array();
381 // Have to be sure $increment has a value so we don't loop endlessly;
382 if (empty($increment)) $increment = 1;
383 for ($i = 0; $i < 60; $i += $increment) {
384 $seconds[$i] = $i < 10 && $format == 's' ? "0$i" : $i;
385 }
386 $none = array('' => '');
387 return !$required ? $none + $seconds : $seconds;
388 }
389
390 /**
391 * An array of am and pm options.
392 * @param $required
393 * If not required, returned array will include a blank value.
394 * @return array an array of am pm options.
395 */
396 function date_ampm($required = FALSE) {
397 $none = array('' => '');
398 $ampm = array('am' => date_t('am', 'ampm'), 'pm' => date_t('pm', 'ampm'));
399 return !$required ? $none + $ampm : $ampm;
400 }
401
402 /**
403 * Implementation of hook_date_formats().
404 *
405 * @return
406 * An array of date formats with attributes 'type' (short, medium or long),
407 * 'format' (the format string) and 'locales' . The 'locales' attribute is an
408 * array of locales, which can include both 2 character language codes like
409 * 'en', 'fr', but also 5 character language codes like 'en-gb' and 'en-us' .
410 */
411 function date_api_date_formats() {
412 include_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'date_api') . '/date_api_formats_list.inc');
413 return _date_api_date_formats_list();
414 }
415
416 /**
417 * Implementation of hook_date_format_types().
418 */
419 function date_api_date_format_types() {
420 return array(
421 'long' => t('Long'),
422 'medium' => t('Medium'),
423 'short' => t('Short'),
424 );
425 }
426
427 /**
428 * Array of regex replacement strings for date format elements.
429 * Used to allow input in custom formats. Based on work done for
430 * the Date module by Yves Chedemois (yched).
431 *
432 * @return array of date() format letters and their regex equivalents.
433 */
434 function date_format_patterns($strict = FALSE) {
435 return array(
436 'd' => '\d{' . ($strict ? '2' : '1,2') . '}',
437 'm' => '\d{' . ($strict ? '2' : '1,2') . '}',
438 'h' => '\d{' . ($strict ? '2' : '1,2') . '}',
439 'H' => '\d{' . ($strict ? '2' : '1,2') . '}',
440 'i' => '\d{' . ($strict ? '2' : '1,2') . '}',
441 's' => '\d{' . ($strict ? '2' : '1,2') . '}',
442 'j' => '\d{1,2}', 'N' => '\d', 'S' => '\w{2}',
443 'w' => '\d', 'z' => '\d{1,3}', 'W' => '\d{1,2}',
444 'n' => '\d{1,2}', 't' => '\d{2}', 'L' => '\d', 'o' => '\d{4}',
445 'Y' => '\d{4}', 'y' => '\d{2}', 'B' => '\d{3}', 'g' => '\d{1,2}',
446 'G' => '\d{1,2}', 'e' => '\w*', 'I' => '\d', 'T' => '\w*',
447 'U' => '\d*', 'z' => '[+-]?\d*', 'O' => '[+-]?\d{4}',
448 //Using S instead of w and 3 as well as 4 to pick up non-ASCII chars like German umlaute
449 'D' => '\S{3,4}', 'l' => '\S*', 'M' => '\S{3,4}', 'F' => '\S*',
450 'P' => '[+-]?\d{2}\:\d{2}',
451 'c' => '(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})([+-]?\d{2}\:\d{2})',
452 'r' => '(\w{3}), (\d{2})\s(\w{3})\s(\d{2,4})\s(\d{2}):(\d{2}):(\d{2})([+-]?\d{4})?',
453 );
454 }
455
456 /**
457 * Array of granularity options and their labels
458 *
459 * @return array
460 */
461 function date_granularity_names() {
462 return array(
463 'year' => date_t('Year', 'datetime'),
464 'month' => date_t('Month', 'datetime'),
465 'day' => date_t('Day', 'datetime'),
466 'hour' => date_t('Hour', 'datetime'),
467 'minute' => date_t('Minute', 'datetime'),
468 'second' => date_t('Second', 'datetime'),
469 );
470 }
471
472 /**
473 * A translated array of timezone names.
474 * Cache the untranslated array, make the translated array a static variable.
475 *
476 * @param $required
477 * If not required, returned array will include a blank value.
478 * @return
479 * an array of timezone names
480 */
481 function date_timezone_names($required = FALSE, $refresh = FALSE) {
482 static $zonenames;
483 if (empty($zonenames) || $refresh) {
484 $cached = cache_get('date_timezone_identifiers_list');
485 $zonenames = !empty($cached) ? $cached->data : array();
486 if ($refresh || empty($cached) || empty($zonenames)) {
487 $data = timezone_identifiers_list();
488 // Use include instead of include once in case the function gets
489 // refreshed via devel or other API and is called more than once.
490 if (module_exists('date_php4')) {
491 include(' ./' . drupal_get_path('module', 'date_php4') . '/date_php4_missing_data.inc');
492 }
493 foreach ($data as $delta => $zone) {
494 // Because many time zones exist in PHP only for backward
495 // compatibility reasons and should not be used, the list is
496 // filtered by a regular expression.
497 if (preg_match('!^((Africa|America|Antarctica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/|UTC$)!', $zone)) {
498 $zonenames[$zone] = t($zone);
499 }
500 }
501 // If using PHP4, filter the list down to only the timezones
502 // the PHP4 wrapper has data for.
503 if (module_exists('date_php4')) {
504 foreach ($missing_timezone_data as $zone) {
505 unset($zonenames[$zone]);
506 }
507 }
508
509 // If using Event, further filter the list down to only
510 // zones that exist in the event module.
511 if (module_exists('event') && db_table_exists('event_timezones')) {
512 $result = db_query("SELECT name FROM {event_timezones} ORDER BY name");
513 $names = array();
514 while ($row = db_fetch_array($result)) {
515 $names[] = str_replace(' ', '_', $row['name']);
516 }
517 foreach ($zonenames as $name => $zone) {
518 if (!in_array($name, $names)) {
519 unset($zonenames[$name]);
520 }
521 }
522 }
523 if (!empty($zonenames)) {
524 cache_set('date_timezone_identifiers_list', $zonenames);
525 }
526 }
527 else {
528 foreach ($zonenames as $zone) {
529 $zonenames[$zone] = t('!timezone', array('!timezone' => $zone));
530 }
531 }
532 }
533 $none = array('' => '');
534 return !$required ? $none + $zonenames : $zonenames;
535 }
536
537 /**
538 * An array of timezone abbreviations that the system allows.
539 * Cache an array of just the abbreviation names because the
540 * whole timezone_abbreviations_list is huge so we don't want
541 * to get it more than necessary.
542 *
543 * @return array
544 */
545 function date_timezone_abbr($refresh = FALSE) {
546 $cached = cache_get('date_timezone_abbreviations');
547 $data = isset($cached->data) ? $cached->data : array();
548 if (empty($data) || $refresh) {
549 $data = array_keys(timezone_abbreviations_list());
550 cache_set('date_timezone_abbreviations', $data);
551 }
552 return $data;
553 }
554
555 /**
556 * A function to translate ambiguous short date strings.
557 *
558 * Example: Pass in 'Monday', 'day_abbr' and get the translated
559 * abbreviation for Monday.
560 *
561 * @param string $string
562 * @param string $context
563 * @param int array('langcode' => $langcode)
564 * @return translated value of the string
565 */
566 function date_t($string, $context, $langcode = NULL) {
567 //static $replace = array();
568
569 if (empty($replace[$langcode])) {
570 // The function to create the date string arrays is kept separate
571 // so those arrays can be directly accessed by other functions.
572 date_t_strings($replace, $langcode);
573 }
574 switch ($context) {
575 case 'day_name':
576 case 'day_abbr':
577 case 'day_abbr1':
578 case 'day_abbr2':
579 $untranslated = array_flip(date_week_days_untranslated());
580 break;
581 case 'month_name':
582 case 'month_abbr':
583 $untranslated = array_flip(date_month_names_untranslated());
584 break;
585 case 'ampm':
586 $untranslated = array_flip(array('am', 'pm', 'AM', 'PM'));
587 break;
588 case 'datetime':
589 $untranslated = array_flip(array('Year', 'Month', 'Day', 'Week', 'Hour', 'Minute', 'Second', 'All Day', 'All day'));
590 break;
591 case 'datetime_plural':
592 $untranslated = array_flip(array('Years', 'Months', 'Days', 'Weeks', 'Hours', 'Minutes', 'Seconds'));
593 break;
594 case 'date_order':
595 $untranslated = array_flip(array('Every', 'First', 'Second', 'Third', 'Fourth', 'Fifth'));
596 break;
597 case 'date_order_reverse':
598 $untranslated = array_flip(array('', 'Last', 'Next to last', 'Third from last', 'Fourth from last', 'Fifth from last'));
599 break;
600 case 'date_nav':
601 $untranslated = array_flip(array('Prev', 'Next', 'Today'));
602 break;
603 }
604 $pos = $untranslated[$string];
605 return $replace[$langcode][$context][$pos];
606 }
607
608 /**
609 * Construct translation arrays from pipe-delimited strings.
610 *
611 * Combining these strings into a single t() gives them the context needed
612 * for better translation.
613 */
614 function date_t_strings(&$replace, $langcode) {
615 $replace[$langcode]['day_name'] = explode('|', trim(t('!day-name Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday', array('!day-name' => ''), array('langcode' => $langcode))));
616 $replace[$langcode]['day_abbr'] = explode('|', trim(t('!day-abbreviation Sun|Mon|Tue|Wed|Thu|Fri|Sat', array('!day-abbreviation' => ''), array('langcode' => $langcode))));
617 $replace[$langcode]['day_abbr1'] = explode('|', trim(t('!day-abbreviation S|M|T|W|T|F|S', array('!day-abbreviation' => ''), array('langcode' => $langcode))));
618 $replace[$langcode]['day_abbr2'] = explode('|', trim(t('!day-abbreviation SU|MO|TU|WE|TH|FR|SA', array('!day-abbreviation' => ''), array('langcode' => $langcode))));
619 $replace[$langcode]['ampm'] = explode('|', trim(t('!ampm-abbreviation am|pm|AM|PM', array('!ampm-abbreviation' => ''), array('langcode' => $langcode))));
620 $replace[$langcode]['datetime'] = explode('|', trim(t('!datetime Year|Month|Day|Week|Hour|Minute|Second|All Day|All day', array('!datetime' => ''), array('langcode' => $langcode))));
621 $replace[$langcode]['datetime_plural'] = explode('|', trim(t('!datetime_plural Years|Months|Days|Weeks|Hours|Minutes|Seconds', array('!datetime_plural' => ''), array('langcode' => $langcode))));
622 $replace[$langcode]['date_order'] = explode('|', trim(t('!date_order Every|First|Second|Third|Fourth|Fifth', array('!date_order' => ''), array('langcode' => $langcode))));
623 $replace[$langcode]['date_order_reverse'] = explode('|', trim(t('!date_order |Last|Next to last|Third from last|Fourth from last|Fifth from last', array('!date_order' => ''), array('langcode' => $langcode))));
624 $replace[$langcode]['date_nav'] = explode('|', trim(t('!date_nav Prev|Next|Today', array('!date_nav' => ''), array('langcode' => $langcode))));
625
626 // These start with a pipe so the January value will be in position 1 instead of position 0.
627 $replace[$langcode]['month_name'] = explode('|', trim(t('!month-name |January|February|March|April|May|June|July|August|September|October|November|December', array('!month-name' => ''), array('langcode' => $langcode))));
628 $replace[$langcode]['month_abbr'] = explode('|', trim(t('!month-abbreviation |Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec', array('!month-abbreviation' => ''), array('langcode' => $langcode))));
629 }
630
631 /**
632 * Reworked from Drupal's format_date function to handle pre-1970 and
633 * post-2038 dates and accept a date object instead of a timestamp as input.
634 *
635 * Translates formatted date results, unlike PHP function date_format().
636 *
637 * @param $oject
638 * A date object, could be created by date_make_date().
639 * @param $type
640 * The format to use. Can be "small", "medium" or "large" for the preconfigured
641 * date formats. If "custom" is specified, then $format is required as well.
642 * @param $format
643 * A PHP date format string as required by date(). A backslash should be used
644 * before a character to avoid interpreting the character as part of a date
645 * format.
646 * @return
647 * A translated date string in the requested format.
648 */
649 function date_format_date($date, $type = 'medium', $format = '', $langcode = NULL) {
650 if (empty($date)) {
651 return '';
652 }
653 switch ($type) {
654 case 'small':
655 case 'short':
656 $format = variable_get('date_format_short', 'm/d/Y - H:i');
657 break;
658 case 'large':
659 case 'long':
660 $format = variable_get('date_format_long', 'l, F j, Y - H:i');
661 break;
662 case 'custom':
663 $format = $format;
664 break;
665 case 'medium':
666 default:
667 $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
668 }
669 $max = strlen($format);
670 $datestring = '';
671 for ($i = 0; $i < $max; $i++) {
672 $c = $format[$i];
673 switch ($c) {
674 // Use date_t() for ambiguous short strings that need translation.
675 // We send long day and month names to date_t(), along with context.
676 case 'l':
677 $datestring .= date_t(date_format($date, 'l'), 'day_name', $langcode);
678 break;
679 case 'D':
680 $datestring .= date_t(date_format($date, 'l'), 'day_abbr', $langcode);
681 break;
682 case 'F':
683 $datestring .= date_t(date_format($date, 'F'), 'month_name', $langcode);
684 break;
685 case 'M':
686 $datestring .= date_t(date_format($date, 'F'), 'month_abbr', $langcode);
687 break;
688 case 'A':
689 case 'a':
690 $datestring .= date_t(date_format($date, $c), 'ampm', $langcode);
691 break;
692 // The timezone name translations can use t().
693 case 'e':
694 case 'T':
695 $datestring .= t(date_format($date, $c));
696 break;
697 // Remaining date parts need no translation.
698 case 'O':
699 $datestring .= sprintf('%s%02d%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60);
700 break;
701 case 'P':
702 $datestring .= sprintf('%s%02d:%02d', (date_offset_get($date) < 0 ? '-' : '+'), abs(date_offset_get($date) / 3600), abs(date_offset_get($date) % 3600) / 60);
703 break;
704 case 'Z':
705 $datestring .= date_offset_get($date);
706 break;
707 case '\\':
708 $datestring .= $format[++$i];
709 break;
710 default:
711 if (strpos('BdcgGhHiIjLmnsStTUwWYyz', $c) !== FALSE) {
712 $datestring .= date_format($date, $c);
713 }
714 elseif ($c == 'r') {
715 $datestring .= format_date($date, 'custom', 'D, d M Y H:i:s O', $langcode);
716 }
717 else {
718 $datestring .= $c;
719 }
720 }
721 }
722 return $datestring;
723 }
724
725 /**
726 * An override for interval formatting that adds past and future context
727 *
728 * @param DateTime $date
729 * @param integer $granularity
730 * @return formatted string
731 */
732 function date_format_interval($date, $granularity = 2) {
733 // If no date is sent, then return nothing
734 if (empty($date)) {
735 return NULL;
736 }
737
738 $interval = REQUEST_TIME - date_format($date, 'U');
739 if($interval > 0 ) {
740 return t('!time ago', array('!time' => format_interval($interval, $granularity)));
741 }
742 else {
743 return format_interval(abs($interval), $granularity);
744 }
745 }
746
747 /**
748 * A date object for the current time.
749 *
750 * @param $timezone
751 * Optional method to force time to a specific timezone,
752 * defaults to user timezone, if set, otherwise site timezone.
753 * @return object date
754 */
755 function date_now($timezone = NULL) {
756 return date_make_date('now', $timezone);
757 }
758
759 /**
760 * Convert a date of any type or an array of date parts into a valid date
761 * object.
762
763 * @param $date
764 * A date in any format or the string 'now' .
765 * @param $timezone
766 * Optional, the name of the timezone this date is in, defaults
767 * to the user timezone, if set, otherwise the site timezone.
768 * Accepts either a timezone name or a timezone object as input.
769 * @param $type
770 * The type of date provided, could be
771 * DATE_ARRAY, DATE_UNIX, DATE_DATETIME, DATE_ISO, or DATE_OBJECT.
772 * @param $granularity
773 * The granularity of the date value provided. Set this for partial
774 * dates so they pass validation and don't get reset to 'now' .
775 */
776 function date_make_date($date, $timezone = NULL, $type = DATE_DATETIME, $granularity = array('year', 'month', 'day', 'hour', 'minute')) {
777 // No value or one that can't be used.
778 if (empty($date) || is_array($date)) {
779 return NULL;
780 }
781
782 // Make sure some value is set for the date and timezone even if the
783 // site timezone is not yet set up to avoid fatal installation
784 // errors.
785 if (empty($timezone) || !date_timezone_is_valid($timezone)) {
786 $timezone = date_default_timezone();
787 }
788
789 // Special handling for partial dates that don't need precision.
790 $max_granularity = array_pop($granularity);
791 if (in_array($max_granularity, array('year', 'month')) || $type == DATE_ISO || $type == DATE_ARRAY) {
792 if ($type == DATE_UNIX) {
793 $date = date_convert($date, $type, DATE_DATETIME);
794 }
795 $date = date_fuzzy_datetime($date);
796 $type = DATE_DATETIME;
797 }
798
799
800 if (!date_is_valid($date, $type, $granularity)) {
801 $date = 'now';
802 }
803 if (!empty($timezone) && !empty($date)) {
804 if ($date == 'now') {
805 return date_create('now', timezone_open($timezone));
806 }
807 elseif ($datetime = date_convert($date, $type, DATE_DATETIME, $timezone)) {
808 return date_create($datetime, timezone_open($timezone));
809 }
810 }
811 return NULL;
812 }
813
814 function date_timezone_is_valid($timezone) {
815 static $timezone_names;
816 if (empty($timezone_names)) {
817 $timezone_names = array_keys(date_timezone_names(TRUE));
818 }
819 if (!in_array($timezone, $timezone_names)) {
820 return FALSE;
821 }
822 return TRUE;
823 }
824
825 /**
826 * Return a timezone name to use as a default.
827 *
828 * @return a timezone name
829 * Identify the default timezone for a user, if available, otherwise the site.
830 * Must return a value even if no timezone info has been set up.
831 */
832 function date_default_timezone($check_user = TRUE) {
833 global $user;
834 if ($check_user && variable_get('configurable_timezones', 1) && !empty($user->timezone)) {
835 return $user->timezone;
836 }
837 else {
838 $default = variable_get('date_default_timezone', '');
839 return empty($default) ? 'UTC' : $default;
840 }
841 }
842
843 /**
844 * A timezone object for the default timezone.
845 *
846 * @return a timezone name
847 * Identify the default timezone for a user, if available, otherwise the site.
848 */
849 function date_default_timezone_object($check_user = TRUE) {
850 $timezone = date_default_timezone($check_user);
851 return timezone_open(date_default_timezone($check_user));
852 }
853
854 /**
855 * Identify the number of days in a month for a date.
856 */
857 function date_days_in_month($year, $month) {
858 // Pick a day in the middle of the month to avoid timezone shifts.
859 $datetime = date_pad($year, 4) . '-' . date_pad($month) . '-15 00:00:00';
860 $date = date_make_date($datetime);
861 return date_format($date, 't');
862 }
863
864 /**
865 * Identify the number of days in a year for a date.
866 *
867 * @param mixed $date
868 * @param string $type
869 * @return integer
870 */
871 function date_days_in_year($date = NULL, $type = DATE_OBJECT) {
872 if (empty($date)) {
873 $date = date_now();
874 $type = DATE_OBJECT;
875 }
876 $date = date_convert($date, $type, DATE_OBJECT);
877 if (is_object($date)) {
878 if (date_format($date, 'L')) {
879 return 366;
880 }
881 else {
882 return 365;
883 }
884 }
885 return NULL;
886 }
887
888 /**
889 * Identify the number of ISO weeks in a year for a date.
890 *
891 * December 28 is always in the last ISO week of the year.
892 *
893 * @param mixed $date
894 * @param string $type
895 * @return integer
896 */
897 function date_iso_weeks_in_year($date = NULL, $type = DATE_OBJECT) {
898 if (empty($date)) {
899 $date = date_now();
900 $type = DATE_OBJECT;
901 }
902 $date = date_convert($date, $type, DATE_OBJECT);
903 if (is_object($date)) {
904 date_date_set($date, date_format($date, 'Y'), 12, 28);
905 return date_format($date, 'W');
906 }
907 return NULL;
908 }
909
910 /**
911 * Returns day of week for a given date (0 = Sunday).
912 *
913 * @param mixed $date
914 * a date, default is current local day
915 * @param string $type
916 * The type of date, DATE_ISO, DATE_DATETIME, or DATE_UNIX
917 * @return
918 * the number of the day in the week
919 */
920 function date_day_of_week($date = NULL, $type = DATE_OBJECT) {
921 if (empty($date)) {
922 $date = date_now();
923 $type = DATE_OBJECT;
924 }
925 $date = date_convert($date, $type, DATE_OBJECT);
926 if (is_object($date)) {
927 return date_format($date, 'w');
928 }
929 return NULL;
930 }
931
932 /**
933 * Returns translated name of the day of week for a given date.
934 *
935 * @param mixed $date
936 * a date, default is current local day
937 * @param string $type
938 * The type of date, DATE_ISO, DATE_DATETIME, or DATE_UNIX
939 * @param string $abbr
940 * Whether to return the abbreviated name for that day
941 * @return
942 * the name of the day in the week for that date
943 */
944 function date_day_of_week_name($date = NULL, $abbr = TRUE, $type = DATE_DATETIME) {
945 $dow = date_day_of_week($date, $type);
946 $days = $abbr ? date_week_days_abbr() : date_week_days();
947 return $days[$dow];
948 }
949
950 /**
951 * Compute difference between two days using a given measure.
952 *
953 * @param mixed $date1
954 * the starting date
955 * @param mixed $date2
956 * the ending date
957 * @param string $measure
958 * 'years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds'
959 * @param string $type
960 * the type of dates provided:
961 * DATE_OBJECT, DATE_DATETIME, DATE_ISO, DATE_UNIX, DATE_ARRAY
962 */
963 function date_difference($date1_in, $date2_in, $measure = 'seconds', $type = DATE_OBJECT) {
964 // Create cloned objects or original dates will be impacted by
965 // the date_modify() operations done in this code.
966 $date1 = clone(date_convert($date1_in, $type, DATE_OBJECT));
967 $date2 = clone(date_convert($date2_in, $type, DATE_OBJECT));
968 if (is_object($date1) && is_object($date2)) {
969 $diff = date_format($date2, 'U') - date_format($date1, 'U');
970 if ($diff == 0 ) {
971 return 0;
972 }
973 elseif ($diff < 0) {
974 // Make sure $date1 is the smaller date.
975 $temp = $date2;
976 $date2 = $date1;
977 $date1 = $temp;
978 $diff = date_format($date2, 'U') - date_format($date1, 'U');
979 }
980 $year_diff = intval(date_format($date2, 'Y') - date_format($date1, 'Y'));
981 switch ($measure) {
982
983 // The easy cases first.
984 case 'seconds':
985 return $diff;
986 case 'minutes':
987 return $diff / 60;
988 case 'hours':
989 return $diff / 3600;
990 case 'years':
991 return $year_diff;
992
993 case 'months':
994 $format = 'n';
995 $item1 = date_format($date1, $format);
996 $item2 = date_format($date2, $format);
997 if ($year_diff == 0) {
998 return intval($item2 - $item1);
999 }
1000 else {
1001 $item_diff = 12 - $item1;
1002 $item_diff += intval(($year_diff - 1) * 12);
1003 return $item_diff + $item2;
1004 }
1005 break;
1006
1007 case 'days':
1008 $format = 'z';
1009 $item1 = date_format($date1, $format);
1010 $item2 = date_format($date2, $format);
1011 if ($year_diff == 0) {
1012 return intval($item2 - $item1);
1013 }
1014 else {
1015 $item_diff = date_days_in_year($date1) - $item1;
1016 for ($i = 1; $i < $year_diff; $i++) {
1017 date_modify($date1, '+1 year');
1018 $item_diff += date_days_in_year($date1);
1019 }
1020 return $item_diff + $item2;
1021 }
1022 break;
1023
1024 case 'weeks':
1025 $week_diff = date_format($date2, 'W') - date_format($date1, 'W');
1026 $year_diff = date_format($date2, 'o') - date_format($date1, 'o');
1027 for ($i = 1; $i <= $year_diff; $i++) {
1028 date_modify($date1, '+1 year');
1029 $week_diff += date_iso_weeks_in_year($date1);
1030 }
1031 return $week_diff;
1032 }
1033 }
1034 return NULL;
1035 }
1036
1037 /**
1038 * Start and end dates for a calendar week, adjusted to use the
1039 * chosen first day of week for this site.
1040 */
1041 function date_week_range($week, $year) {
1042 $min_date = date_make_date($year . '-01-01 00:00:00', date_default_timezone());
1043 date_timezone_set($min_date, date_default_timezone());
1044
1045 // move to the right week
1046 date_modify($min_date, '+' . strval(7 * ($week - 1)) . ' days');
1047
1048 // move backwards to the first day of the week
1049 $first_day = variable_get('date_first_day', 0);
1050 $day_wday = date_format($min_date, 'w');
1051 date_modify($min_date, '-' . strval((7 + $day_wday - $first_day) % 7) . ' days');
1052
1053 // move forwards to the last day of the week
1054 $max_date = clone($min_date);
1055 date_modify($max_date, '+7 days');
1056
1057 if (date_format($min_date, 'Y') != $year) {
1058 $min_date = date_make_date($year . '-01-01 00:00:00', date_default_timezone());
1059 }
1060 return array($min_date, $max_date);
1061 }
1062
1063 /**
1064 * The number of calendar weeks in a year.
1065 *
1066 * PHP week functions return the ISO week, not the calendar week.
1067 *
1068 * @param int $year
1069 * @return int number of calendar weeks in selected year.
1070 */
1071 function date_weeks_in_year($year) {
1072 $date = date_make_date(($year + 1) . '-01-01 12:00:00', 'UTC');
1073 date_modify($date, '-1 day');
1074 return date_week(date_format($date, 'Y-m-d'));
1075 }
1076
1077 /**
1078 * The calendar week number for a date.
1079 *
1080 * PHP week functions return the ISO week, not the calendar week.
1081 *
1082 * @param string $date, in the format Y-m-d
1083 * @return int calendar week number.
1084 */
1085 function date_week($date) {
1086 $date = substr($date, 0, 10);
1087 $parts = explode('-', $date);
1088
1089 $date = date_make_date($date . ' 12:00:00', 'UTC');
1090 $year_date = date_make_date($parts[0] . '-01-01 12:00:00', 'UTC');
1091
1092 $week = intval(date_format($date, 'W'));
1093 $year_week = intval(date_format($year_date, 'W'));
1094 $date_year = intval(date_format($date, 'o'));
1095
1096 // remove the leap week if it's present
1097 if ($date_year > intval($parts[0])) {
1098 $last_date = clone($date);
1099 date_modify($last_date, '-7 days');
1100 $week = date_format($last_date, 'W') + 1;
1101 }
1102 elseif ($date_year < intval($parts[0])) {
1103 $week = 0;
1104 }
1105 if ($year_week != 1) $week++;
1106
1107 // convert to ISO-8601 day number, to match weeks calculated above
1108 $iso_first_day = 1 + (variable_get('date_first_day', 0) + 6) % 7;
1109
1110 // if it's before the starting day, it's the previous week
1111 if (intval(date_format($date, 'N')) < $iso_first_day) $week--;
1112
1113 // if the year starts before, it's an extra week at the beginning
1114 if (intval(date_format($year_date, 'N')) < $iso_first_day) $week++;
1115
1116 return $week;
1117 }
1118
1119 /**
1120 * Date conversion helper function.
1121 *
1122 * A variety of ways to convert dates from one type to another.
1123 * No timezone conversion is done in this operation, except
1124 * when handling timestamps because create_date() assumes
1125 * timestamps hold the UTC value for the time.
1126 *
1127 * @param mixed $date
1128 * the date to convert
1129 * @param string $from_type
1130 * the type of date to convert from
1131 * @param string $to_type
1132 * the type of date to convert to
1133 * @param string $tz
1134 * the timezone of the supplied value, only needed when using timestamps
1135 * for dates not set to UTC.
1136 */
1137 function date_convert($date, $from_type, $to_type, $tz = 'UTC') {
1138 if (empty($date) && !$date === 0) return NULL;
1139 if (empty($from_type) || empty($to_type) || $from_type == $to_type) return $date;
1140 switch ($from_type) {
1141 case DATE_ARRAY:
1142 if (!is_array($date)) return NULL;
1143