2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
7 * Generic time span handling class for PEAR
13 * Copyright (c) 1997-2005 Leandro Lucarella, Pierre-Alain Joye
14 * All rights reserved.
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted under the terms of the BSD License.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
32 * @category Date and Time
34 * @author Leandro Lucarella <llucax@php.net>
35 * @author Pierre-Alain Joye <pajoye@php.net>
36 * @copyright 1997-2006 Leandro Lucarella, Pierre-Alain Joye
37 * @license http://www.opensource.org/licenses/bsd-license.php
40 * @link http://pear.php.net/package/Date
41 * @since File available since Release 1.4
50 require_once 'Date.php';
53 * Get the Date_Calc class
55 require_once 'Date/Calc.php';
61 * Non Numeric Separated Values (NNSV) Input Format
63 * Input format guessed from something like this:
65 * <b>days</b><sep><b>hours</b><sep><b>minutes</b><sep><b>seconds</b>
67 * Where '<sep>' is any quantity of non numeric chars. If no values are
68 * given, time span is set to zero, if one value is given, it is used for
69 * hours, if two values are given it's used for hours and minutes and if
70 * three values are given, it is used for hours, minutes and seconds.
74 * - <b>""</b> -> 0, 0, 0, 0 (days, hours, minutes, seconds)
75 * - <b>"12"</b> -> 0, 12, 0, 0
76 * - <b>"12.30"</b> -> 0, 12, 30, 0
77 * - <b>"12:30:18"</b> -> 0, 12, 30, 18
78 * - <b>"3-12-30-18"</b> -> 3, 12, 30, 18
79 * - <b>"3 days, 12-30-18"</b> -> 3, 12, 30, 18
80 * - <b>"12:30 with 18 secs"</b> -> 0, 12, 30, 18
82 * @see Date_Span::setFromString()
84 define('DATE_SPAN_INPUT_FORMAT_NNSV', 1);
87 // {{{ Global Variables
90 * Default time format when converting to a string
94 $GLOBALS['_DATE_SPAN_FORMAT'] = '%C';
97 * Default time format when converting from a string
101 $GLOBALS['_DATE_SPAN_INPUT_FORMAT'] = DATE_SPAN_INPUT_FORMAT_NNSV;
104 // {{{ Class: Date_Span
107 * Generic time span handling class for PEAR
109 * @category Date and Time
111 * @author Leandro Lucarella <llucax@php.net>
112 * @author Pierre-Alain Joye <pajoye@php.net>
113 * @copyright 1997-2006 Leandro Lucarella, Pierre-Alain Joye
114 * @license http://www.opensource.org/licenses/bsd-license.php
116 * @version Release: 1.5.0a1
117 * @link http://pear.php.net/package/Date
118 * @since Class available since Release 1.4
130 * @since Property available since Release 1.0
135 * The no of hours (0 to 23)
139 * @since Property available since Release 1.0
144 * The no of minutes (0 to 59)
148 * @since Property available since Release 1.0
153 * The no of seconds (0 to 59)
157 * @since Property available since Release 1.0
168 * Creates the time span object calling {@link Date_Span::set()}
170 * @param mixed $time time span expression
171 * @param mixed $format format string to set it from a string or the
172 * second date set it from a date diff
177 public function Date_Span($time = 0, $format = null)
179 $this->set($time, $format);
187 * Set the time span to a new value in a 'smart' way
189 * Sets the time span depending on the argument types, calling
190 * to the appropriate setFromXxx() method.
192 * @param mixed $time time span expression
193 * @param mixed $format format string to set it from a string or the
194 * second date set it from a date diff
196 * @return bool true on success
198 * @see Date_Span::copy(), Date_Span::setFromArray(),
199 * Date_Span::setFromString(), Date_Span::setFromSeconds(),
200 * Date_Span::setFromDateDiff()
202 public function set($time = 0, $format = null)
204 if (is_a($time, 'Date_Span')) {
205 return $this->copy($time);
206 } elseif (is_a($time, 'Date') and is_a($format, 'Date')) {
207 return $this->setFromDateDiff($time, $format);
208 } elseif (is_array($time)) {
209 return $this->setFromArray($time);
210 } elseif (is_string($time) || is_string($format)) {
211 return $this->setFromString((string)$time, $format);
212 } elseif (is_int($time)) {
213 return $this->setFromSeconds($time);
215 return $this->setFromSeconds(0);
221 // {{{ setFromArray()
224 * Set the time span from an array
226 * Any value can be a float (but it has no sense in seconds), for example:
228 * <code>$object->setFromArray(array(23.5, 20, 0));</code>
230 * is interpreted as 23 hours, 0.5 * 60 + 20 = 50 minutes and 0 seconds.
232 * @param array $time items are counted from right to left. First
233 * item is for seconds, second for minutes, third
234 * for hours and fourth for days. If there are
235 * less items than 4, zero (0) is assumed for the
238 * @return bool true on success
240 * @see Date_Span::set()
242 public function setFromArray($time)
244 if (!is_array($time)) {
247 $tmp1 = new Date_Span;
248 if (!$tmp1->setFromSeconds(@array_pop($time))) {
251 $tmp2 = new Date_Span;
252 if (!$tmp2->setFromMinutes(@array_pop($time))) {
256 if (!$tmp2->setFromHours(@array_pop($time))) {
260 if (!$tmp2->setFromDays(@array_pop($time))) {
264 return $this->copy($tmp1);
269 // {{{ setFromString()
272 * Sets the time span from a string, based on an input format
274 * This is some like a mix of the PHP functions
275 * {@link http://www.php.net/strftime strftime()} and
276 * {@link http://www.php.net/sscanf sscanf()}.
277 * The error checking and validation of this function is very primitive,
278 * so you should be careful when using it with unknown strings.
279 * With this method you are assigning day, hour, minute and second
280 * values, and the last values are used. This means that if you use
283 * <code>$object->setFromString('10, 20', '%H, %h');</code>
285 * your time span would be 20 hours long. Always remember that this
286 * method sets all the values, so if you had a span object 30
287 * minutes long and you call:
289 * <code>$object->setFromString('20 hours', '%H hours');</code>
291 * the span object would be 20 hours long (and not 20 hours and 30
294 * Input format options:
296 * - <b>%C</b> - Days with time, equivalent to '<b>D, %H:%M:%S</b>'
297 * - <b>%d</b> - Total days as a float number
298 * (2 days, 12 hours = 2.5 days)
299 * - <b>%D</b> - Days as a decimal number
300 * - <b>%e</b> - Total hours as a float number
301 * (1 day, 2 hours, 30 minutes = 26.5 hours)
302 * - <b>%f</b> - Total minutes as a float number
303 * (2 minutes, 30 seconds = 2.5 minutes)
304 * - <b>%g</b> - Total seconds as a decimal number
305 * (2 minutes, 30 seconds = 90 seconds)
306 * - <b>%h</b> - Hours as decimal number
307 * - <b>%H</b> - Hours as decimal number limited to 2 digits
308 * - <b>%m</b> - Minutes as a decimal number
309 * - <b>%M</b> - Minutes as a decimal number limited to 2 digits
310 * - <b>%n</b> - Newline character (\n)
311 * - <b>%p</b> - Either 'am' or 'pm' depending on the time. If 'pm'
312 * is detected it adds 12 hours to the resulting time
313 * span (without any checks). This is case
315 * - <b>%r</b> - Time in am/pm notation, equivalent to '<b>H:%M:%S %p</b>'
316 * - <b>%R</b> - Time in 24-hour notation, equivalent to '<b>H:%M</b>'
317 * - <b>%s</b> - Seconds as a decimal number
318 * - <b>%S</b> - Seconds as a decimal number limited to 2 digits
319 * - <b>%t</b> - Tab character (\t)
320 * - <b>%T</b> - Current time equivalent, equivalent to '<b>H:%M:%S</b>'
321 * - <b>%%</b> - Literal '%'
323 * @param string $time string from where to get the time span
325 * @param string $format format string
327 * @return bool true on success
329 * @see Date_Span::set(), DATE_SPAN_INPUT_FORMAT_NNSV
331 public function setFromString($time, $format = null)
333 if (is_null($format)) {
334 $format = $GLOBALS['_DATE_SPAN_INPUT_FORMAT'];
336 // If format is a string, it parses the string format.
337 if (is_string($format)) {
341 $day = $hour = $minute = $second = 0;
342 for ($i = 0; $i < strlen($format); $i++) {
345 $nextchar = $format{++$i};
348 $str .= '%d, %d:%d:%d';
358 $str .= '%d, %2d:%2d:%2d';
369 array_push($vars, 'day');
373 array_push($vars, 'day');
377 array_push($vars, 'hour');
381 array_push($vars, 'minute');
385 array_push($vars, 'second');
389 array_push($vars, 'hour');
393 array_push($vars, 'hour');
397 array_push($vars, 'minute');
401 array_push($vars, 'minute');
408 array_push($vars, 'pm');
411 $str .= '%2d:%2d:%2d %2s';
422 array_push($vars, 'hour', 'minute');
426 array_push($vars, 'second');
430 array_push($vars, 'second');
436 $str .= '%2d:%2d:%2d';
437 array_push($vars, 'hour', 'minute', 'second');
443 $str .= $char . $nextchar;
449 $vals = sscanf($time, $str);
450 foreach ($vals as $i => $val) {
456 if (strcasecmp($pm, 'pm') == 0) {
458 } elseif (strcasecmp($pm, 'am') != 0) {
461 $this->setFromArray(array($day, $hour, $minute, $second));
462 } elseif (is_integer($format)) {
463 // If format is a integer, it uses a predefined format
466 case DATE_SPAN_INPUT_FORMAT_NNSV:
467 $time = preg_split('/\D+/', $time);
468 switch (count($time)) {
470 return $this->setFromArray(array(0,
475 return $this->setFromArray(array(0,
480 return $this->setFromArray(array(0,
485 return $this->setFromArray(array(0,
490 return $this->setFromArray($time);
500 // {{{ setFromSeconds()
503 * Set the time span from a total number of seconds
505 * @param int $seconds total number of seconds
507 * @return bool true on success
509 * @see Date_Span::set(), Date_Span::setFromDays(),
510 * Date_Span::setFromHours(), Date_Span::setFromMinutes()
512 public function setFromSeconds($seconds)
517 $sec = intval($seconds);
518 $min = floor($sec / 60);
519 $hour = floor($min / 60);
520 $day = intval(floor($hour / 24));
522 $this->second = $sec % 60;
523 $this->minute = $min % 60;
524 $this->hour = $hour % 24;
531 // {{{ setFromMinutes()
534 * Sets the time span from a total number of minutes
536 * @param float $minutes total number of minutes
538 * @return bool true on success
540 * @see Date_Span::set(), Date_Span::setFromDays(),
541 * Date_Span::setFromHours(), Date_Span::setFromSeconds()
543 public function setFromMinutes($minutes)
545 return $this->setFromSeconds(round($minutes * 60));
550 // {{{ setFromHours()
553 * Sets the time span from a total number of hours
555 * @param float $hours total number of hours
557 * @return bool true on success
559 * @see Date_Span::set(), Date_Span::setFromDays(),
560 * Date_Span::setFromHours(), Date_Span::setFromMinutes()
562 public function setFromHours($hours)
564 return $this->setFromSeconds(round($hours * 3600));
572 * Sets the time span from a total number of days
574 * @param float $days total number of days
576 * @return bool true on success
578 * @see Date_Span::set(), Date_Span::setFromHours(),
579 * Date_Span::setFromMinutes(), Date_Span::setFromSeconds()
581 public function setFromDays($days)
583 return $this->setFromSeconds(round($days * 86400));
588 // {{{ setFromDateDiff()
591 * Sets the span from the elapsed time between two dates
593 * The time span is unsigned, so the date's order is not important.
595 * @param object $date1 first Date
596 * @param object $date2 second Date
598 * @return bool true on success
600 * @see Date_Span::set()
602 public function setFromDateDiff($date1, $date2)
604 if (!is_a($date1, 'date') or !is_a($date2, 'date')) {
608 // create a local copy of instance, in order avoid changes the object
609 // reference when its object has converted to UTC due PHP5 is always
610 // passed the object by reference.
611 $tdate1 = new Date($date1);
612 $tdate2 = new Date($date2);
618 if ($tdate1->after($tdate2)) {
619 list($tdate1, $tdate2) = array($tdate2, $tdate1);
622 $days = Date_Calc::dateDiff(
631 $hours = $tdate2->getHour() - $tdate1->getHour();
632 $mins = $tdate2->getMinute() - $tdate1->getMinute();
633 $secs = $tdate2->getSecond() - $tdate1->getSecond();
635 $this->setFromSeconds($days * 86400 +
645 * Sets the time span from another time object
647 * @param object $time source time span object
649 * @return bool true on success
652 public function copy($time)
654 if (is_a($time, 'date_span')) {
655 $this->second = $time->second;
656 $this->minute = $time->minute;
657 $this->hour = $time->hour;
658 $this->day = $time->day;
670 * Formats time span according to specified code (similar to
671 * {@link Date::formatLikeStrftime()})
673 * Uses a code based on {@link http://www.php.net/strftime strftime()}.
675 * Formatting options:
677 * - <b>%C</b> - Days with time, equivalent to '<b>%D, %H:%M:%S</b>'
678 * - <b>%d</b> - Total days as a float number
679 * (2 days, 12 hours = 2.5 days)
680 * - <b>%D</b> - Days as a decimal number
681 * - <b>%e</b> - Total hours as a float number
682 * (1 day, 2 hours, 30 minutes = 26.5 hours)
683 * - <b>%E</b> - Total hours as a decimal number
684 * (1 day, 2 hours, 40 minutes = 26 hours)
685 * - <b>%f</b> - Total minutes as a float number
686 * (2 minutes, 30 seconds = 2.5 minutes)
687 * - <b>%F</b> - Total minutes as a decimal number
688 * (1 hour, 2 minutes, 40 seconds = 62 minutes)
689 * - <b>%g</b> - Total seconds as a decimal number
690 * (2 minutes, 30 seconds = 90 seconds)
691 * - <b>%h</b> - Hours as decimal number (0 to 23)
692 * - <b>%H</b> - Hours as decimal number (00 to 23)
693 * - <b>%i</b> - Hours as decimal number on 12-hour clock
695 * - <b>%I</b> - Hours as decimal number on 12-hour clock
697 * - <b>%m</b> - Minutes as a decimal number (0 to 59)
698 * - <b>%M</b> - Minutes as a decimal number (00 to 59)
699 * - <b>%n</b> - Newline character (\n)
700 * - <b>%p</b> - Either 'am' or 'pm' depending on the time
701 * - <b>%P</b> - Either 'AM' or 'PM' depending on the time
702 * - <b>%r</b> - Time in am/pm notation, equivalent to '<b>%I:%M:%S %p</b>'
703 * - <b>%R</b> - Time in 24-hour notation, equivalent to '<b>%H:%M</b>'
704 * - <b>%s</b> - Seconds as a decimal number (0 to 59)
705 * - <b>%S</b> - Seconds as a decimal number (00 to 59)
706 * - <b>%t</b> - Tab character (\t)
707 * - <b>%T</b> - Current time equivalent, equivalent to '<b>%H:%M:%S</b>'
708 * - <b>%%</b> - Literal '%'
710 * @param string $format the format string for returned time span
712 * @return string the time span in specified format
715 public function format($format = null)
717 if (is_null($format)) {
718 $format = $GLOBALS['_DATE_SPAN_FORMAT'];
721 for ($i = 0; $i < strlen($format); $i++) {
724 $nextchar = $format{++$i};
728 '%d, %02d:%02d:%02d',
736 $output .= $this->toDays();
739 $output .= $this->day;
742 $output .= $this->toHours();
745 $output .= floor($this->toHours());
748 $output .= $this->toMinutes();
751 $output .= floor($this->toMinutes());
754 $output .= $this->toSeconds();
757 $output .= $this->hour;
760 $output .= sprintf('%02d', $this->hour);
764 $hour = $this->hour + 1 > 12 ?
767 $output .= $hour == 0 ?
771 sprintf('%02d', $hour));
774 $output .= $this->minute;
777 $output .= sprintf('%02d', $this->minute);
783 $output .= $this->hour >= 12 ? 'pm' : 'am';
786 $output .= $this->hour >= 12 ? 'PM' : 'AM';
789 $hour = $this->hour + 1 > 12 ?
794 $hour == 0 ? 12 : $hour,
797 $this->hour >= 12 ? 'pm' : 'am'
808 $output .= $this->second;
811 $output .= sprintf('%02d', $this->second);
828 $output .= $char . $nextchar;
842 * Converts time span to seconds
844 * @return int time span as an integer number of seconds
846 * @see Date_Span::toDays(), Date_Span::toHours(),
847 * Date_Span::toMinutes()
849 public function toSeconds()
851 return $this->day * 86400 + $this->hour * 3600 +
852 $this->minute * 60 + $this->second;
860 * Converts time span to minutes
862 * @return float time span as a decimal number of minutes
864 * @see Date_Span::toDays(), Date_Span::toHours(),
865 * Date_Span::toSeconds()
867 public function toMinutes()
869 return $this->day * 1440 + $this->hour * 60 + $this->minute +
878 * Converts time span to hours
880 * @return float time span as a decimal number of hours
882 * @see Date_Span::toDays(), Date_Span::toMinutes(),
883 * Date_Span::toSeconds()
885 public function toHours()
887 return $this->day * 24 + $this->hour + $this->minute / 60 +
888 $this->second / 3600;
896 * Converts time span to days
898 * @return float time span as a decimal number of days
900 * @see Date_Span::toHours(), Date_Span::toMinutes(),
901 * Date_Span::toSeconds()
903 public function toDays()
905 return $this->day + $this->hour / 24 + $this->minute / 1440 +
906 $this->second / 86400;
916 * @param object $time time span to add
920 * @see Date_Span::subtract()
922 public function add($time)
924 return $this->setFromSeconds($this->toSeconds() +
933 * Subtracts a time span
935 * If the time span to subtract is larger than the original, the result
936 * is zero (there's no sense in negative time spans).
938 * @param object $time time span to subtract
942 * @see Date_Span::add()
944 public function subtract($time)
946 $sub = $this->toSeconds() - $time->toSeconds();
948 $this->setFromSeconds(0);
950 $this->setFromSeconds($sub);
959 * Tells if time span is equal to $time
961 * @param object $time time span to compare to
963 * @return bool true if the time spans are equal
965 * @see Date_Span::greater(), Date_Span::greaterEqual()
966 * Date_Span::lower(), Date_Span::lowerEqual()
968 public function equal($time)
970 return $this->toSeconds() == $time->toSeconds();
975 // {{{ greaterEqual()
978 * Tells if this time span is greater or equal than $time
980 * @param object $time time span to compare to
982 * @return bool true if this time span is greater or equal than $time
984 * @see Date_Span::greater(), Date_Span::lower(),
985 * Date_Span::lowerEqual(), Date_Span::equal()
987 public function greaterEqual($time)
989 return $this->toSeconds() >= $time->toSeconds();
997 * Tells if this time span is lower or equal than $time
999 * @param object $time time span to compare to
1001 * @return bool true if this time span is lower or equal than $time
1003 * @see Date_Span::lower(), Date_Span::greater(),
1004 * Date_Span::greaterEqual(), Date_Span::equal()
1006 public function lowerEqual($time)
1008 return $this->toSeconds() <= $time->toSeconds();
1016 * Tells if this time span is greater than $time
1018 * @param object $time time span to compare to
1020 * @return bool true if this time span is greater than $time
1022 * @see Date_Span::greaterEqual(), Date_Span::lower(),
1023 * Date_Span::lowerEqual(), Date_Span::equal()
1025 public function greater($time)
1027 return $this->toSeconds() > $time->toSeconds();
1035 * Tells if this time span is lower than $time
1037 * @param object $time time span to compare to
1039 * @return bool true if this time span is lower than $time
1041 * @see Date_Span::lowerEqual(), Date_Span::greater(),
1042 * Date_Span::greaterEqual(), Date_Span::equal()
1044 public function lower($time)
1046 return $this->toSeconds() < $time->toSeconds();
1054 * Compares two time spans
1056 * Suitable for use in sorting functions.
1058 * @param object $time1 the first time span
1059 * @param object $time2 the second time span
1061 * @return int 0 if the time spans are equal, -1 if time1 is lower
1062 * than time2, 1 if time1 is greater than time2
1066 public function compare($time1, $time2)
1068 if ($time1->equal($time2)) {
1070 } elseif ($time1->lower($time2)) {
1082 * Tells if the time span is empty (zero length)
1084 * @return bool true if empty
1087 public function isEmpty()
1089 return !$this->day && !$this->hour && !$this->minute && !$this->second;
1094 // {{{ setDefaultInputFormat()
1097 * Sets the default input format
1099 * @param mixed $format new default input format
1101 * @return mixed previous default input format
1104 * @see Date_Span::getDefaultInputFormat(), Date_Span::setDefaultFormat()
1106 public function setDefaultInputFormat($format)
1108 $old = $GLOBALS['_DATE_SPAN_INPUT_FORMAT'];
1109 $GLOBALS['_DATE_SPAN_INPUT_FORMAT'] = $format;
1115 // {{{ getDefaultInputFormat()
1118 * Returns the default input format
1120 * @return mixed default input format
1123 * @see Date_Span::setDefaultInputFormat(), Date_Span::getDefaultFormat()
1125 public function getDefaultInputFormat()
1127 return $GLOBALS['_DATE_SPAN_INPUT_FORMAT'];
1132 // {{{ setDefaultFormat()
1135 * Sets the default format
1137 * @param mixed $format new default format
1139 * @return mixed previous default format
1142 * @see Date_Span::getDefaultFormat(), Date_Span::setDefaultInputFormat()
1144 public function setDefaultFormat($format)
1146 $old = $GLOBALS['_DATE_SPAN_FORMAT'];
1147 $GLOBALS['_DATE_SPAN_FORMAT'] = $format;
1153 // {{{ getDefaultFormat()
1156 * Returns the default format
1158 * @return mixed default format
1161 * @see Date_Span::setDefaultFormat(), Date_Span::getDefaultInputFormat()
1163 public function getDefaultFormat()
1165 return $GLOBALS['_DATE_SPAN_FORMAT'];
1179 * c-hanging-comment-ender-p: nil