1: <?php
2: /**
3: * CakeTime utility class file.
4: *
5: * PHP 5
6: *
7: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
8: * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
9: *
10: * Licensed under The MIT License
11: * Redistributions of files must retain the above copyright notice.
12: *
13: * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org)
14: * @link http://cakephp.org CakePHP(tm) Project
15: * @package Cake.Utility
16: * @since CakePHP(tm) v 0.10.0.1076
17: * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
18: */
19:
20: App::uses('Multibyte', 'I18n');
21:
22: /**
23: * Time Helper class for easy use of time data.
24: *
25: * Manipulation of time data.
26: *
27: * @package Cake.Utility
28: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html
29: */
30: class CakeTime {
31:
32: /**
33: * The format to use when formatting a time using `CakeTime::nice()`
34: *
35: * The format should use the locale strings as defined in the PHP docs under
36: * `strftime` (http://php.net/manual/en/function.strftime.php)
37: *
38: * @var string
39: * @see CakeTime::format()
40: */
41: public static $niceFormat = '%a, %b %eS %Y, %H:%M';
42:
43: /**
44: * The format to use when formatting a time using `CakeTime::timeAgoInWords()`
45: * and the difference is more than `CakeTime::$wordEnd`
46: *
47: * @var string
48: * @see CakeTime::timeAgoInWords()
49: */
50: public static $wordFormat = 'j/n/y';
51:
52: /**
53: * The format to use when formatting a time using `CakeTime::niceShort()`
54: * and the difference is between 3 and 7 days
55: *
56: * @var string
57: * @see CakeTime::niceShort()
58: */
59: public static $niceShortFormat = '%B %d, %H:%M';
60:
61: /**
62: * The format to use when formatting a time using `CakeTime::timeAgoInWords()`
63: * and the difference is less than `CakeTime::$wordEnd`
64: *
65: * @var array
66: * @see CakeTime::timeAgoInWords()
67: */
68: public static $wordAccuracy = array(
69: 'year' => "day",
70: 'month' => "day",
71: 'week' => "day",
72: 'day' => "hour",
73: 'hour' => "minute",
74: 'minute' => "minute",
75: 'second' => "second",
76: );
77:
78: /**
79: * The end of relative time telling
80: *
81: * @var string
82: * @see CakeTime::timeAgoInWords()
83: */
84: public static $wordEnd = '+1 month';
85:
86: /**
87: * Temporary variable containing timestamp value, used internally convertSpecifiers()
88: */
89: protected static $_time = null;
90:
91: /**
92: * Magic set method for backward compatibility.
93: *
94: * Used by TimeHelper to modify static variables in CakeTime
95: */
96: public function __set($name, $value) {
97: switch ($name) {
98: case 'niceFormat':
99: self::${$name} = $value;
100: break;
101: default:
102: break;
103: }
104: }
105:
106: /**
107: * Magic set method for backward compatibility.
108: *
109: * Used by TimeHelper to get static variables in CakeTime
110: */
111: public function __get($name) {
112: switch ($name) {
113: case 'niceFormat':
114: return self::${$name};
115: default:
116: return null;
117: }
118: }
119:
120: /**
121: * Converts a string representing the format for the function strftime and returns a
122: * windows safe and i18n aware format.
123: *
124: * @param string $format Format with specifiers for strftime function.
125: * Accepts the special specifier %S which mimics the modifier S for date()
126: * @param string $time UNIX timestamp
127: * @return string windows safe and date() function compatible format for strftime
128: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
129: */
130: public static function convertSpecifiers($format, $time = null) {
131: if (!$time) {
132: $time = time();
133: }
134: self::$_time = $time;
135: return preg_replace_callback('/\%(\w+)/', array('CakeTime', '_translateSpecifier'), $format);
136: }
137:
138: /**
139: * Auxiliary function to translate a matched specifier element from a regular expression into
140: * a windows safe and i18n aware specifier
141: *
142: * @param array $specifier match from regular expression
143: * @return string converted element
144: */
145: protected static function _translateSpecifier($specifier) {
146: switch ($specifier[1]) {
147: case 'a':
148: $abday = __dc('cake', 'abday', 5);
149: if (is_array($abday)) {
150: return $abday[date('w', self::$_time)];
151: }
152: break;
153: case 'A':
154: $day = __dc('cake', 'day', 5);
155: if (is_array($day)) {
156: return $day[date('w', self::$_time)];
157: }
158: break;
159: case 'c':
160: $format = __dc('cake', 'd_t_fmt', 5);
161: if ($format != 'd_t_fmt') {
162: return self::convertSpecifiers($format, self::$_time);
163: }
164: break;
165: case 'C':
166: return sprintf("%02d", date('Y', self::$_time) / 100);
167: case 'D':
168: return '%m/%d/%y';
169: case 'e':
170: if (DS === '/') {
171: return '%e';
172: }
173: $day = date('j', self::$_time);
174: if ($day < 10) {
175: $day = ' ' . $day;
176: }
177: return $day;
178: case 'eS' :
179: return date('jS', self::$_time);
180: case 'b':
181: case 'h':
182: $months = __dc('cake', 'abmon', 5);
183: if (is_array($months)) {
184: return $months[date('n', self::$_time) - 1];
185: }
186: return '%b';
187: case 'B':
188: $months = __dc('cake', 'mon', 5);
189: if (is_array($months)) {
190: return $months[date('n', self::$_time) - 1];
191: }
192: break;
193: case 'n':
194: return "\n";
195: case 'p':
196: case 'P':
197: $default = array('am' => 0, 'pm' => 1);
198: $meridiem = $default[date('a', self::$_time)];
199: $format = __dc('cake', 'am_pm', 5);
200: if (is_array($format)) {
201: $meridiem = $format[$meridiem];
202: return ($specifier[1] == 'P') ? strtolower($meridiem) : strtoupper($meridiem);
203: }
204: break;
205: case 'r':
206: $complete = __dc('cake', 't_fmt_ampm', 5);
207: if ($complete != 't_fmt_ampm') {
208: return str_replace('%p', self::_translateSpecifier(array('%p', 'p')), $complete);
209: }
210: break;
211: case 'R':
212: return date('H:i', self::$_time);
213: case 't':
214: return "\t";
215: case 'T':
216: return '%H:%M:%S';
217: case 'u':
218: return ($weekDay = date('w', self::$_time)) ? $weekDay : 7;
219: case 'x':
220: $format = __dc('cake', 'd_fmt', 5);
221: if ($format != 'd_fmt') {
222: return self::convertSpecifiers($format, self::$_time);
223: }
224: break;
225: case 'X':
226: $format = __dc('cake', 't_fmt', 5);
227: if ($format != 't_fmt') {
228: return self::convertSpecifiers($format, self::$_time);
229: }
230: break;
231: }
232: return $specifier[0];
233: }
234:
235: /**
236: * Converts given time (in server's time zone) to user's local time, given his/her timezone.
237: *
238: * @param string $serverTime UNIX timestamp
239: * @param string|DateTimeZone $timezone User's timezone string or DateTimeZone object
240: * @return integer UNIX timestamp
241: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
242: */
243: public static function convert($serverTime, $timezone) {
244: static $serverTimezone = null;
245: if (is_null($serverTimezone) || (date_default_timezone_get() !== $serverTimezone->getName())) {
246: $serverTimezone = new DateTimeZone(date_default_timezone_get());
247: }
248: $serverOffset = $serverTimezone->getOffset(new DateTime('@' . $serverTime));
249: $gmtTime = $serverTime - $serverOffset;
250: if (is_numeric($timezone)) {
251: $userOffset = $timezone * (60 * 60);
252: } else {
253: $timezone = self::timezone($timezone);
254: $userOffset = $timezone->getOffset(new DateTime('@' . $gmtTime));
255: }
256: $userTime = $gmtTime + $userOffset;
257: return (int)$userTime;
258: }
259:
260: /**
261: * Returns a timezone object from a string or the user's timezone object
262: *
263: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
264: * If null it tries to get timezone from 'Config.timezone' config var
265: * @return DateTimeZone Timezone object
266: */
267: public static function timezone($timezone = null) {
268: static $tz = null;
269:
270: if (is_object($timezone)) {
271: if ($tz === null || $tz->getName() !== $timezone->getName()) {
272: $tz = $timezone;
273: }
274: } else {
275: if ($timezone === null) {
276: $timezone = Configure::read('Config.timezone');
277: if ($timezone === null) {
278: $timezone = date_default_timezone_get();
279: }
280: }
281:
282: if ($tz === null || $tz->getName() !== $timezone) {
283: $tz = new DateTimeZone($timezone);
284: }
285: }
286:
287: return $tz;
288: }
289:
290: /**
291: * Returns server's offset from GMT in seconds.
292: *
293: * @return integer Offset
294: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
295: */
296: public static function serverOffset() {
297: return date('Z', time());
298: }
299:
300: /**
301: * Returns a UNIX timestamp, given either a UNIX timestamp or a valid strtotime() date string.
302: *
303: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
304: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
305: * @return string Parsed timestamp
306: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
307: */
308: public static function fromString($dateString, $timezone = null) {
309: if (empty($dateString)) {
310: return false;
311: }
312:
313: if (is_integer($dateString) || is_numeric($dateString)) {
314: $date = intval($dateString);
315: } elseif (is_object($dateString) && $dateString instanceof DateTime) {
316: $clone = clone $dateString;
317: $clone->setTimezone(new DateTimeZone(date_default_timezone_get()));
318: $date = (int)$clone->format('U') + $clone->getOffset();
319: } else {
320: $date = strtotime($dateString);
321: }
322:
323: if ($date === -1 || empty($date)) {
324: return false;
325: }
326:
327: if ($timezone === null) {
328: $timezone = Configure::read('Config.timezone');
329: }
330:
331: if ($timezone !== null) {
332: return self::convert($date, $timezone);
333: }
334: return $date;
335: }
336:
337: /**
338: * Returns a nicely formatted date string for given Datetime string.
339: *
340: * See http://php.net/manual/en/function.strftime.php for information on formatting
341: * using locale strings.
342: *
343: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
344: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
345: * @param string $format The format to use. If null, `TimeHelper::$niceFormat` is used
346: * @return string Formatted date string
347: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
348: */
349: public static function nice($dateString = null, $timezone = null, $format = null) {
350: if (!$dateString) {
351: $dateString = time();
352: }
353: $date = self::fromString($dateString, $timezone);
354:
355: if (!$format) {
356: $format = self::$niceFormat;
357: }
358: $format = self::convertSpecifiers($format, $date);
359: return self::_strftime($format, $date);
360: }
361:
362: /**
363: * Returns a formatted descriptive date string for given datetime string.
364: *
365: * If the given date is today, the returned string could be "Today, 16:54".
366: * If the given date is tomorrow, the returned string could be "Tomorrow, 16:54".
367: * If the given date was yesterday, the returned string could be "Yesterday, 16:54".
368: * If the given date is within next or last week, the returned string could be "On Thursday, 16:54".
369: * If $dateString's year is the current year, the returned string does not
370: * include mention of the year.
371: *
372: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
373: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
374: * @return string Described, relative date string
375: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
376: */
377: public static function niceShort($dateString = null, $timezone = null) {
378: if (!$dateString) {
379: $dateString = time();
380: }
381: $date = self::fromString($dateString, $timezone);
382:
383: $y = self::isThisYear($date) ? '' : ' %Y';
384:
385: $d = self::_strftime("%w", $date);
386: $day = array(
387: __d('cake', 'Sunday'),
388: __d('cake', 'Monday'),
389: __d('cake', 'Tuesday'),
390: __d('cake', 'Wednesday'),
391: __d('cake', 'Thursday'),
392: __d('cake', 'Friday'),
393: __d('cake', 'Saturday')
394: );
395:
396: if (self::isToday($dateString, $timezone)) {
397: $ret = __d('cake', 'Today, %s', self::_strftime("%H:%M", $date));
398: } elseif (self::wasYesterday($dateString, $timezone)) {
399: $ret = __d('cake', 'Yesterday, %s', self::_strftime("%H:%M", $date));
400: } elseif (self::isTomorrow($dateString, $timezone)) {
401: $ret = __d('cake', 'Tomorrow, %s', self::_strftime("%H:%M", $date));
402: } elseif (self::wasWithinLast('7 days', $dateString, $timezone)) {
403: $ret = sprintf('%s %s', $day[$d], self::_strftime(self::$niceShortFormat, $date));
404: } elseif (self::isWithinNext('7 days', $dateString, $timezone)) {
405: $ret = __d('cake', 'On %s %s', $day[$d], self::_strftime(self::$niceShortFormat, $date));
406: } else {
407: $format = self::convertSpecifiers("%b %eS{$y}, %H:%M", $date);
408: $ret = self::_strftime($format, $date);
409: }
410: return $ret;
411: }
412:
413: /**
414: * Returns a partial SQL string to search for all records between two dates.
415: *
416: * @param integer|string|DateTime $begin UNIX timestamp, strtotime() valid string or DateTime object
417: * @param integer|string|DateTime $end UNIX timestamp, strtotime() valid string or DateTime object
418: * @param string $fieldName Name of database field to compare with
419: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
420: * @return string Partial SQL string.
421: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
422: */
423: public static function daysAsSql($begin, $end, $fieldName, $timezone = null) {
424: $begin = self::fromString($begin, $timezone);
425: $end = self::fromString($end, $timezone);
426: $begin = date('Y-m-d', $begin) . ' 00:00:00';
427: $end = date('Y-m-d', $end) . ' 23:59:59';
428:
429: return "($fieldName >= '$begin') AND ($fieldName <= '$end')";
430: }
431:
432: /**
433: * Returns a partial SQL string to search for all records between two times
434: * occurring on the same day.
435: *
436: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
437: * @param string $fieldName Name of database field to compare with
438: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
439: * @return string Partial SQL string.
440: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
441: */
442: public static function dayAsSql($dateString, $fieldName, $timezone = null) {
443: return self::daysAsSql($dateString, $dateString, $fieldName);
444: }
445:
446: /**
447: * Returns true if given datetime string is today.
448: *
449: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
450: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
451: * @return boolean True if datetime string is today
452: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#testing-time
453: */
454: public static function isToday($dateString, $timezone = null) {
455: $date = self::fromString($dateString, $timezone);
456: return date('Y-m-d', $date) == date('Y-m-d', time());
457: }
458:
459: /**
460: * Returns true if given datetime string is within this week.
461: *
462: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
463: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
464: * @return boolean True if datetime string is within current week
465: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#testing-time
466: */
467: public static function isThisWeek($dateString, $timezone = null) {
468: $date = self::fromString($dateString, $timezone);
469: return date('W o', $date) == date('W o', time());
470: }
471:
472: /**
473: * Returns true if given datetime string is within this month
474: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
475: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
476: * @return boolean True if datetime string is within current month
477: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#testing-time
478: */
479: public static function isThisMonth($dateString, $timezone = null) {
480: $date = self::fromString($dateString);
481: return date('m Y', $date) == date('m Y', time());
482: }
483:
484: /**
485: * Returns true if given datetime string is within current year.
486: *
487: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
488: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
489: * @return boolean True if datetime string is within current year
490: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#testing-time
491: */
492: public static function isThisYear($dateString, $timezone = null) {
493: $date = self::fromString($dateString, $timezone);
494: return date('Y', $date) == date('Y', time());
495: }
496:
497: /**
498: * Returns true if given datetime string was yesterday.
499: *
500: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
501: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
502: * @return boolean True if datetime string was yesterday
503: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#testing-time
504: *
505: */
506: public static function wasYesterday($dateString, $timezone = null) {
507: $date = self::fromString($dateString, $timezone);
508: return date('Y-m-d', $date) == date('Y-m-d', strtotime('yesterday'));
509: }
510:
511: /**
512: * Returns true if given datetime string is tomorrow.
513: *
514: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
515: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
516: * @return boolean True if datetime string was yesterday
517: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#testing-time
518: */
519: public static function isTomorrow($dateString, $timezone = null) {
520: $date = self::fromString($dateString, $timezone);
521: return date('Y-m-d', $date) == date('Y-m-d', strtotime('tomorrow'));
522: }
523:
524: /**
525: * Returns the quarter
526: *
527: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
528: * @param boolean $range if true returns a range in Y-m-d format
529: * @return mixed 1, 2, 3, or 4 quarter of year or array if $range true
530: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
531: */
532: public static function toQuarter($dateString, $range = false) {
533: $time = self::fromString($dateString);
534: $date = ceil(date('m', $time) / 3);
535:
536: if ($range === true) {
537: $range = 'Y-m-d';
538: }
539:
540: if ($range !== false) {
541: $year = date('Y', $time);
542:
543: switch ($date) {
544: case 1:
545: $date = array($year . '-01-01', $year . '-03-31');
546: break;
547: case 2:
548: $date = array($year . '-04-01', $year . '-06-30');
549: break;
550: case 3:
551: $date = array($year . '-07-01', $year . '-09-30');
552: break;
553: case 4:
554: $date = array($year . '-10-01', $year . '-12-31');
555: break;
556: }
557: }
558: return $date;
559: }
560:
561: /**
562: * Returns a UNIX timestamp from a textual datetime description. Wrapper for PHP function strtotime().
563: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
564: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
565: * @return integer Unix timestamp
566: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
567: */
568: public static function toUnix($dateString, $timezone = null) {
569: return self::fromString($dateString, $timezone);
570: }
571:
572: /**
573: * Returns a formatted date in server's timezone.
574: *
575: * If a DateTime object is given or the dateString has a timezone
576: * segment, the timezone parameter will be ignored.
577: *
578: * If no timezone parameter is given and no DateTime object, the passed $dateString will be
579: * considered to be in the UTC timezone.
580: *
581: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
582: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
583: * @param string $format date format string
584: * @return mixed Formatted date
585: */
586: public static function toServer($dateString, $timezone = null, $format = 'Y-m-d H:i:s') {
587: if ($timezone === null) {
588: $timezone = new DateTimeZone('UTC');
589: } elseif (is_string($timezone)) {
590: $timezone = new DateTimeZone($timezone);
591: } elseif (!($timezone instanceof DateTimeZone)) {
592: return false;
593: }
594:
595: if ($dateString instanceof DateTime) {
596: $date = $dateString;
597: } elseif (is_integer($dateString) || is_numeric($dateString)) {
598: $dateString = (int)$dateString;
599:
600: $date = new DateTime('@' . $dateString);
601: $date->setTimezone($timezone);
602: } else {
603: $date = new DateTime($dateString, $timezone);
604: }
605:
606: $date->setTimezone(new DateTimeZone(date_default_timezone_get()));
607: return $date->format($format);
608: }
609:
610: /**
611: * Returns a date formatted for Atom RSS feeds.
612: *
613: * @param string $dateString Datetime string or Unix timestamp
614: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
615: * @return string Formatted date string
616: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
617: */
618: public static function toAtom($dateString, $timezone = null) {
619: $date = self::fromString($dateString, $timezone);
620: return date('Y-m-d\TH:i:s\Z', $date);
621: }
622:
623: /**
624: * Formats date for RSS feeds
625: *
626: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
627: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
628: * @return string Formatted date string
629: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
630: */
631: public static function toRSS($dateString, $timezone = null) {
632: $date = self::fromString($dateString, $timezone);
633:
634: if (!is_null($timezone)) {
635: if (is_numeric($timezone)) {
636: $userOffset = $timezone;
637: } else {
638: if (!is_object($timezone)) {
639: $timezone = new DateTimeZone($timezone);
640: }
641: $currentDate = new DateTime('@' . $date);
642: $currentDate->setTimezone($timezone);
643: $userOffset = $timezone->getOffset($currentDate) / 60 / 60;
644: }
645: if ($userOffset == 0) {
646: $timezone = '+0000';
647: } else {
648: $hours = (int)floor(abs($userOffset));
649: $minutes = (int)(fmod(abs($userOffset), $hours) * 60);
650: $timezone = ($userOffset < 0 ? '-' : '+') . str_pad($hours, 2, '0', STR_PAD_LEFT) . str_pad($minutes, 2, '0', STR_PAD_LEFT);
651: }
652: return date('D, d M Y H:i:s', $date) . ' ' . $timezone;
653: }
654: return date("r", $date);
655: }
656:
657: /**
658: * Returns either a relative date or a formatted date depending
659: * on the difference between the current time and given datetime.
660: * $datetime should be in a *strtotime* - parsable format, like MySQL's datetime datatype.
661: *
662: * ### Options:
663: *
664: * - `format` => a fall back format if the relative time is longer than the duration specified by end
665: * - `accuracy` => Specifies how accurate the date should be described (array)
666: * - year => The format if years > 0 (default "day")
667: * - month => The format if months > 0 (default "day")
668: * - week => The format if weeks > 0 (default "day")
669: * - day => The format if weeks > 0 (default "hour")
670: * - hour => The format if hours > 0 (default "minute")
671: * - minute => The format if minutes > 0 (default "minute")
672: * - second => The format if seconds > 0 (default "second")
673: * - `end` => The end of relative time telling
674: * - `userOffset` => Users offset from GMT (in hours) *Deprecated* use timezone intead.
675: * - `timezone` => The user timezone the timestamp should be formatted in.
676: *
677: * Relative dates look something like this:
678: *
679: * - 3 weeks, 4 days ago
680: * - 15 seconds ago
681: *
682: * Default date formatting is d/m/yy e.g: on 18/2/09
683: *
684: * The returned string includes 'ago' or 'on' and assumes you'll properly add a word
685: * like 'Posted ' before the function output.
686: *
687: * NOTE: If the difference is one week or more, the lowest level of accuracy is day
688: *
689: * @param integer|string|DateTime $dateTime Datetime UNIX timestamp, strtotime() valid string or DateTime object
690: * @param array $options Default format if timestamp is used in $dateString
691: * @return string Relative time string.
692: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
693: */
694: public static function timeAgoInWords($dateTime, $options = array()) {
695: $timezone = null;
696: $format = self::$wordFormat;
697: $end = self::$wordEnd;
698: $accuracy = self::$wordAccuracy;
699:
700: if (is_array($options)) {
701: if (isset($options['timezone'])) {
702: $timezone = $options['timezone'];
703: } elseif (isset($options['userOffset'])) {
704: $timezone = $options['userOffset'];
705: }
706:
707: if (isset($options['accuracy'])) {
708: if (is_array($options['accuracy'])) {
709: $accuracy = array_merge($accuracy, $options['accuracy']);
710: } else {
711: foreach ($accuracy as $key => $level) {
712: $accuracy[$key] = $options['accuracy'];
713: }
714: }
715: }
716:
717: if (isset($options['format'])) {
718: $format = $options['format'];
719: }
720: if (isset($options['end'])) {
721: $end = $options['end'];
722: }
723: unset($options['end'], $options['format']);
724: } else {
725: $format = $options;
726: }
727:
728: $now = self::fromString(time(), $timezone);
729: $inSeconds = self::fromString($dateTime, $timezone);
730: $backwards = ($inSeconds > $now);
731:
732: if ($backwards) {
733: $futureTime = $inSeconds;
734: $pastTime = $now;
735: } else {
736: $futureTime = $now;
737: $pastTime = $inSeconds;
738: }
739: $diff = $futureTime - $pastTime;
740:
741: // If more than a week, then take into account the length of months
742: if ($diff >= 604800) {
743: list($future['H'], $future['i'], $future['s'], $future['d'], $future['m'], $future['Y']) = explode('/', date('H/i/s/d/m/Y', $futureTime));
744:
745: list($past['H'], $past['i'], $past['s'], $past['d'], $past['m'], $past['Y']) = explode('/', date('H/i/s/d/m/Y', $pastTime));
746: $years = $months = $weeks = $days = $hours = $minutes = $seconds = 0;
747:
748: $years = $future['Y'] - $past['Y'];
749: $months = $future['m'] + ((12 * $years) - $past['m']);
750:
751: if ($months >= 12) {
752: $years = floor($months / 12);
753: $months = $months - ($years * 12);
754: }
755: if ($future['m'] < $past['m'] && $future['Y'] - $past['Y'] == 1) {
756: $years--;
757: }
758:
759: if ($future['d'] >= $past['d']) {
760: $days = $future['d'] - $past['d'];
761: } else {
762: $daysInPastMonth = date('t', $pastTime);
763: $daysInFutureMonth = date('t', mktime(0, 0, 0, $future['m'] - 1, 1, $future['Y']));
764:
765: if (!$backwards) {
766: $days = ($daysInPastMonth - $past['d']) + $future['d'];
767: } else {
768: $days = ($daysInFutureMonth - $past['d']) + $future['d'];
769: }
770:
771: if ($future['m'] != $past['m']) {
772: $months--;
773: }
774: }
775:
776: if ($months == 0 && $years >= 1 && $diff < ($years * 31536000)) {
777: $months = 11;
778: $years--;
779: }
780:
781: if ($months >= 12) {
782: $years = $years + 1;
783: $months = $months - 12;
784: }
785:
786: if ($days >= 7) {
787: $weeks = floor($days / 7);
788: $days = $days - ($weeks * 7);
789: }
790: } else {
791: $years = $months = $weeks = 0;
792: $days = floor($diff / 86400);
793:
794: $diff = $diff - ($days * 86400);
795:
796: $hours = floor($diff / 3600);
797: $diff = $diff - ($hours * 3600);
798:
799: $minutes = floor($diff / 60);
800: $diff = $diff - ($minutes * 60);
801: $seconds = $diff;
802: }
803: $relativeDate = '';
804: $diff = $futureTime - $pastTime;
805:
806: if ($diff > abs($now - self::fromString($end))) {
807: $relativeDate = __d('cake', 'on %s', date($format, $inSeconds));
808: } else {
809: if ($years > 0) {
810: $f = $accuracy['year'];
811: } elseif (abs($months) > 0) {
812: $f = $accuracy['month'];
813: } elseif (abs($weeks) > 0) {
814: $f = $accuracy['week'];
815: } elseif (abs($days) > 0) {
816: $f = $accuracy['day'];
817: } elseif (abs($hours) > 0) {
818: $f = $accuracy['hour'];
819: } elseif (abs($minutes) > 0) {
820: $f = $accuracy['minute'];
821: } else {
822: $f = $accuracy['second'];
823: }
824:
825: $f = str_replace(array('year', 'month', 'week', 'day', 'hour', 'minute', 'second'), array(1, 2, 3, 4, 5, 6, 7), $f);
826:
827: $relativeDate .= $f >= 1 && $years > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d year', '%d years', $years, $years) : '';
828: $relativeDate .= $f >= 2 && $months > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d month', '%d months', $months, $months) : '';
829: $relativeDate .= $f >= 3 && $weeks > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d week', '%d weeks', $weeks, $weeks) : '';
830: $relativeDate .= $f >= 4 && $days > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d day', '%d days', $days, $days) : '';
831: $relativeDate .= $f >= 5 && $hours > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d hour', '%d hours', $hours, $hours) : '';
832: $relativeDate .= $f >= 6 && $minutes > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d minute', '%d minutes', $minutes, $minutes) : '';
833: $relativeDate .= $f >= 7 && $seconds > 0 ? ($relativeDate ? ', ' : '') . __dn('cake', '%d second', '%d seconds', $seconds, $seconds) : '';
834:
835: if (!$backwards) {
836: $relativeDate = __d('cake', '%s ago', $relativeDate);
837: }
838: }
839:
840: // If now
841: if ($diff == 0) {
842: $relativeDate = __d('cake', 'just now', 'just now');
843: }
844: return $relativeDate;
845: }
846:
847: /**
848: * Returns true if specified datetime was within the interval specified, else false.
849: *
850: * @param string|integer $timeInterval the numeric value with space then time type.
851: * Example of valid types: 6 hours, 2 days, 1 minute.
852: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
853: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
854: * @return boolean
855: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#testing-time
856: */
857: public static function wasWithinLast($timeInterval, $dateString, $timezone = null) {
858: $tmp = str_replace(' ', '', $timeInterval);
859: if (is_numeric($tmp)) {
860: $timeInterval = $tmp . ' ' . __d('cake', 'days');
861: }
862:
863: $date = self::fromString($dateString, $timezone);
864: $interval = self::fromString('-' . $timeInterval);
865:
866: if ($date >= $interval && $date <= time()) {
867: return true;
868: }
869: return false;
870: }
871:
872: /**
873: * Returns true if specified datetime is within the interval specified, else false.
874: *
875: * @param string|integer $timeInterval the numeric value with space then time type.
876: * Example of valid types: 6 hours, 2 days, 1 minute.
877: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
878: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
879: * @return boolean
880: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#testing-time
881: */
882: public static function isWithinNext($timeInterval, $dateString, $timezone = null) {
883: $tmp = str_replace(' ', '', $timeInterval);
884: if (is_numeric($tmp)) {
885: $timeInterval = $tmp . ' ' . __d('cake', 'days');
886: }
887:
888: $date = self::fromString($dateString, $timezone);
889: $interval = self::fromString('+' . $timeInterval);
890:
891: if ($date <= $interval && $date >= time()) {
892: return true;
893: }
894: return false;
895: }
896:
897: /**
898: * Returns gmt as a UNIX timestamp.
899: *
900: * @param integer|string|DateTime $dateString UNIX timestamp, strtotime() valid string or DateTime object
901: * @return integer UNIX timestamp
902: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
903: */
904: public static function gmt($dateString = null) {
905: if ($dateString != null) {
906: $time = self::fromString($dateString);
907: } else {
908: $time = time();
909: }
910: $hour = intval(date("G", $time));
911: $minute = intval(date("i", $time));
912: $second = intval(date("s", $time));
913: $month = intval(date("n", $time));
914: $day = intval(date("j", $time));
915: $year = intval(date("Y", $time));
916: return gmmktime($hour, $minute, $second, $month, $day, $year);
917: }
918:
919: /**
920: * Returns a formatted date string, given either a UNIX timestamp or a valid strtotime() date string.
921: * This function also accepts a time string and a format string as first and second parameters.
922: * In that case this function behaves as a wrapper for TimeHelper::i18nFormat()
923: *
924: * ## Examples
925: *
926: * Create localized & formatted time:
927: *
928: * {{{
929: * CakeTime::format('2012-02-15', '%m-%d-%Y'); // returns 02-15-2012
930: * CakeTime::format('2012-02-15 23:01:01', '%c'); // returns preferred date and time based on configured locale
931: * CakeTime::format('0000-00-00', '%d-%m-%Y', 'N/A'); // return N/A becuase an invalid date was passed
932: * CakeTime::format('2012-02-15 23:01:01', '%c', 'N/A', 'America/New_York'); // converts passed date to timezone
933: * }}}
934: *
935: * @param integer|string|DateTime $date UNIX timestamp, strtotime() valid string or DateTime object (or a date format string)
936: * @param integer|string|DateTime $format date format string (or UNIX timestamp, strtotime() valid string or DateTime object)
937: * @param boolean|string $default if an invalid date is passed it will output supplied default value. Pass false if you want raw conversion value
938: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
939: * @return string Formatted date string
940: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
941: * @see CakeTime::i18nFormat()
942: */
943: public static function format($date, $format = null, $default = false, $timezone = null) {
944: //Backwards compatible params re-order test
945: $time = self::fromString($format, $timezone);
946:
947: if ($time === false) {
948: return self::i18nFormat($date, $format, $default, $timezone);
949: }
950: return date($date, $time);
951: }
952:
953: /**
954: * Returns a formatted date string, given either a UNIX timestamp or a valid strtotime() date string.
955: * It take in account the default date format for the current language if a LC_TIME file is used.
956: *
957: * @param integer|string|DateTime $date UNIX timestamp, strtotime() valid string or DateTime object
958: * @param string $format strftime format string.
959: * @param boolean|string $default if an invalid date is passed it will output supplied default value. Pass false if you want raw conversion value
960: * @param string|DateTimeZone $timezone Timezone string or DateTimeZone object
961: * @return string Formatted and translated date string
962: * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/time.html#formatting
963: */
964: public static function i18nFormat($date, $format = null, $default = false, $timezone = null) {
965: $date = self::fromString($date, $timezone);
966: if ($date === false && $default !== false) {
967: return $default;
968: }
969: if (empty($format)) {
970: $format = '%x';
971: }
972: $format = self::convertSpecifiers($format, $date);
973: return self::_strftime($format, $date);
974: }
975:
976: /**
977: * Get list of timezone identifiers
978: *
979: * @param integer|string $filter A regex to filter identifer
980: * Or one of DateTimeZone class constants (PHP 5.3 and above)
981: * @param string $country A two-letter ISO 3166-1 compatible country code.
982: * This option is only used when $filter is set to DateTimeZone::PER_COUNTRY (available only in PHP 5.3 and above)
983: * @param boolean $group If true (default value) groups the identifiers list by primary region
984: * @return array List of timezone identifiers
985: * @since 2.2
986: */
987: public static function listTimezones($filter = null, $country = null, $group = true) {
988: $regex = null;
989: if (is_string($filter)) {
990: $regex = $filter;
991: $filter = null;
992: }
993: if (version_compare(PHP_VERSION, '5.3.0', '<')) {
994: if ($regex === null) {
995: $regex = '#^((Africa|America|Antartica|Arctic|Asia|Atlantic|Australia|Europe|Indian|Pacific)/|UTC)#';
996: }
997: $identifiers = DateTimeZone::listIdentifiers();
998: } else {
999: if ($filter === null) {
1000: $filter = DateTimeZone::ALL;
1001: }
1002: $identifiers = DateTimeZone::listIdentifiers($filter, $country);
1003: }
1004:
1005: if ($regex) {
1006: foreach ($identifiers as $key => $tz) {
1007: if (!preg_match($regex, $tz)) {
1008: unset($identifiers[$key]);
1009: }
1010: }
1011: }
1012:
1013: if ($group) {
1014: $return = array();
1015: foreach ($identifiers as $key => $tz) {
1016: $item = explode('/', $tz, 2);
1017: if (isset($item[1])) {
1018: $return[$item[0]][$tz] = $item[1];
1019: } else {
1020: $return[$item[0]] = array($tz => $item[0]);
1021: }
1022: }
1023: return $return;
1024: } else {
1025: return array_combine($identifiers, $identifiers);
1026: }
1027: }
1028:
1029: /**
1030: * Multibyte wrapper for strftime.
1031: *
1032: * Handles utf8_encoding the result of strftime when necessary.
1033: *
1034: * @param string $format Format string.
1035: * @param integer $date Timestamp to format.
1036: * @return string formatted string with correct encoding.
1037: */
1038: protected static function _strftime($format, $date) {
1039: $format = strftime($format, $date);
1040: $encoding = Configure::read('App.encoding');
1041:
1042: if (!empty($encoding) && $encoding === 'UTF-8') {
1043: if (function_exists('mb_check_encoding')) {
1044: $valid = mb_check_encoding($format, $encoding);
1045: } else {
1046: $valid = !Multibyte::checkMultibyte($format);
1047: }
1048: if (!$valid) {
1049: $format = utf8_encode($format);
1050: }
1051: }
1052: return $format;
1053: }
1054:
1055: }
1056: