add toDays(), fromDays(), and diff() functions, and convert to PHP 5 style
authorChuck Hagenbuch <chuck@horde.org>
Sat, 30 May 2009 01:01:38 +0000 (21:01 -0400)
committerChuck Hagenbuch <chuck@horde.org>
Sat, 30 May 2009 01:02:36 +0000 (21:02 -0400)
framework/Date/lib/Horde/Date.php

index 564a426..6612c6a 100644 (file)
  *   usually better to store datetimes as either UTC datetime or plain unix
  *   timestamp. I usually go with the former - using database datetime type.
  */
+
+/**
+ * @category Horde
+ * @package  Horde_Date
+ */
 class Horde_Date
 {
     const DATE_SUNDAY = 0;
@@ -162,7 +167,7 @@ class Horde_Date
         'sec'   => self::MASK_SECOND,
     );
 
-    var $_formatCache = array();
+    protected $_formatCache = array();
 
     /**
      * Build a new date object. If $date contains date parts, use them to
@@ -179,7 +184,7 @@ class Horde_Date
      *   03 Mar 1973)
      * - unix timestamps
      */
-    function __construct($date = null, $timezone = null)
+    public function __construct($date = null, $timezone = null)
     {
         if (!self::$_supportedSpecs) {
             self::$_supportedSpecs = self::$_defaultSpecs;
@@ -252,7 +257,7 @@ class Horde_Date
      *
      * @return string  This object converted to a string.
      */
-    function __toString()
+    public function __toString()
     {
         try {
             return $this->format($this->_defaultFormat);
@@ -266,7 +271,7 @@ class Horde_Date
      *
      * @return DateTime
      */
-    function toDateTime()
+    public function toDateTime()
     {
         $date = new DateTime(null, new DateTimeZone($this->_timezone));
         $date->setDate($this->_year, $this->_month, $this->_mday);
@@ -275,6 +280,123 @@ class Horde_Date
     }
 
     /**
+     * Converts a date in the proleptic Gregorian calendar to the no of days
+     * since 24th November, 4714 B.C.
+     *
+     * Returns the no of days since Monday, 24th November, 4714 B.C. in the
+     * proleptic Gregorian calendar (which is 24th November, -4713 using
+     * 'Astronomical' year numbering, and 1st January, 4713 B.C. in the
+     * proleptic Julian calendar).  This is also the first day of the 'Julian
+     * Period' proposed by Joseph Scaliger in 1583, and the number of days
+     * since this date is known as the 'Julian Day'.  (It is not directly
+     * to do with the Julian calendar, although this is where the name
+     * is derived from.)
+     *
+     * The algorithm is valid for all years (positive and negative), and
+     * also for years preceding 4714 B.C.
+     *
+     * Algorithm is from PEAR::Date_Calc
+     *
+     * @author Monte Ohrt <monte@ispi.net>
+     * @author Pierre-Alain Joye <pajoye@php.net>
+     * @author Daniel Convissor <danielc@php.net>
+     * @author C.A. Woodcock <c01234@netcomuk.co.uk>
+     *
+     * @return integer  The number of days since 24th November, 4714 B.C.
+     */
+    public function toDays()
+    {
+        $day = $this->_mday;
+        $month = $this->_month;
+        $year = $this->_year;
+
+        if ($month > 2) {
+            // March = 0, April = 1, ..., December = 9,
+            // January = 10, February = 11
+            $month -= 3;
+        } else {
+            $month += 9;
+            --$year;
+        }
+
+        $hb_negativeyear = $year < 0;
+        $century         = intval($year / 100);
+        $year            = $year % 100;
+
+        if ($hb_negativeyear) {
+            // Subtract 1 because year 0 is a leap year;
+            // And N.B. that we must treat the leap years as occurring
+            // one year earlier than they do, because for the purposes
+            // of calculation, the year starts on 1st March:
+            //
+            return intval((14609700 * $century + ($year == 0 ? 1 : 0)) / 400) +
+                   intval((1461 * $year + 1) / 4) +
+                   intval((153 * $month + 2) / 5) +
+                   $day + 1721118;
+        } else {
+            return intval(146097 * $century / 4) +
+                   intval(1461 * $year / 4) +
+                   intval((153 * $month + 2) / 5) +
+                   $day + 1721119;
+        }
+    }
+
+    /**
+     * Converts number of days since 24th November, 4714 B.C. (in the proleptic
+     * Gregorian calendar, which is year -4713 using 'Astronomical' year
+     * numbering) to Gregorian calendar date.
+     *
+     * Returned date belongs to the proleptic Gregorian calendar, using
+     * 'Astronomical' year numbering.
+     *
+     * The algorithm is valid for all years (positive and negative), and
+     * also for years preceding 4714 B.C. (i.e. for negative 'Julian Days'),
+     * and so the only limitation is platform-dependent (for 32-bit systems
+     * the maximum year would be something like about 1,465,190 A.D.).
+     *
+     * N.B. Monday, 24th November, 4714 B.C. is Julian Day '0'.
+     *
+     * Algorithm is from PEAR::Date_Calc
+     *
+     * @author Monte Ohrt <monte@ispi.net>
+     * @author Pierre-Alain Joye <pajoye@php.net>
+     * @author Daniel Convissor <danielc@php.net>
+     * @author C.A. Woodcock <c01234@netcomuk.co.uk>
+     *
+     * @param int    $days   the number of days since 24th November, 4714 B.C.
+     * @param string $format the string indicating how to format the output
+     *
+     * @return  Horde_Date  A Horde_Date object representing the date.
+     */
+    public static function fromDays($days)
+    {
+        $days = intval($days);
+
+        $days   -= 1721119;
+        $century = floor((4 * $days - 1) / 146097);
+        $days    = floor(4 * $days - 1 - 146097 * $century);
+        $day     = floor($days / 4);
+
+        $year = floor((4 * $day +  3) / 1461);
+        $day  = floor(4 * $day +  3 - 1461 * $year);
+        $day  = floor(($day +  4) / 4);
+
+        $month = floor((5 * $day - 3) / 153);
+        $day   = floor(5 * $day - 3 - 153 * $month);
+        $day   = floor(($day +  5) /  5);
+
+        $year = $century * 100 + $year;
+        if ($month < 10) {
+            $month +=3;
+        } else {
+            $month -=9;
+            ++$year;
+        }
+
+        return new Horde_Date($year, $month, $day);
+    }
+
+    /**
      * Getter for the date and time properties.
      *
      * @param string $name  One of 'year', 'month', 'mday', 'hour', 'min' or
@@ -282,7 +404,7 @@ class Horde_Date
      *
      * @return integer  The property value, or null if not set.
      */
-    function __get($name)
+    public function __get($name)
     {
         if ($name == 'day') {
             $name = 'mday';
@@ -298,7 +420,7 @@ class Horde_Date
      *                        'sec'.
      * @param integer $value  The property value.
      */
-    function __set($name, $value)
+    public function __set($name, $value)
     {
         if ($name == 'day') {
             $name = 'mday';
@@ -322,7 +444,7 @@ class Horde_Date
      *
      * @return boolen  True if the property exists and is set.
      */
-    function __isset($name)
+    public function __isset($name)
     {
         if ($name == 'day') {
             $name = 'mday';
@@ -398,13 +520,11 @@ class Horde_Date
     /**
      * Returns whether a year is a leap year.
      *
-     * @static
-     *
      * @param integer $year  The year.
      *
      * @return boolan  True if the year is a leap year.
      */
-    function isLeapYear($year)
+    public static function isLeapYear($year)
     {
         if (strlen($year) != 4 || preg_match('/\D/', $year)) {
             return false;
@@ -422,7 +542,7 @@ class Horde_Date
      *
      * @return Horde_Date  The date of the first day of the given week.
      */
-    function firstDayOfWeek($week, $year)
+    public static function firstDayOfWeek($week, $year)
     {
         return new Horde_Date(sprintf('%04dW%02d', $year, $week));
     }
@@ -430,14 +550,12 @@ class Horde_Date
     /**
      * Returns the number of days in the specified month.
      *
-     * @static
-     *
      * @param integer $month  The month
      * @param integer $year   The year.
      *
      * @return integer  The number of days in the month.
      */
-    function daysInMonth($month, $year)
+    public static function daysInMonth($month, $year)
     {
         static $cache = array();
         if (!isset($cache[$year][$month])) {
@@ -452,7 +570,7 @@ class Horde_Date
      *
      * @return integer  The day of the week.
      */
-    function dayOfWeek()
+    public function dayOfWeek()
     {
         if ($this->_month > 2) {
             $month = $this->_month - 2;
@@ -476,7 +594,7 @@ class Horde_Date
      *
      * @return integer  The day of the year.
      */
-    function dayOfYear()
+    public function dayOfYear()
     {
         return $this->format('z') + 1;
     }
@@ -486,7 +604,7 @@ class Horde_Date
      *
      * @return integer  The week number.
      */
-    function weekOfMonth()
+    public function weekOfMonth()
     {
         return ceil($this->_mday / 7);
     }
@@ -496,7 +614,7 @@ class Horde_Date
      *
      * @return integer  The week number.
      */
-    function weekOfYear()
+    public function weekOfYear()
     {
         return $this->format('W');
     }
@@ -504,13 +622,11 @@ class Horde_Date
     /**
      * Return the number of weeks in the given year (52 or 53).
      *
-     * @static
-     *
      * @param integer $year  The year to count the number of weeks in.
      *
      * @return integer $numWeeks   The number of weeks in $year.
      */
-    function weeksInYear($year)
+    public static function weeksInYear($year)
     {
         // Find the last Thursday of the year.
         $date = new Horde_Date($year . '-12-31');
@@ -526,7 +642,7 @@ class Horde_Date
      * @param integer $weekday  The day of the week (0 = Sunday, etc).
      * @param integer $nth      The $nth $weekday to set to (defaults to 1).
      */
-    function setNthWeekday($weekday, $nth = 1)
+    public function setNthWeekday($weekday, $nth = 1)
     {
         if ($weekday < self::DATE_SUNDAY || $weekday > self::DATE_SATURDAY) {
             return;
@@ -548,82 +664,12 @@ class Horde_Date
      *
      * @return boolean  Validity, counting leap years, etc.
      */
-    function isValid()
+    public function isValid()
     {
         return ($this->_year >= 0 && $this->_year <= 9999);
     }
 
     /**
-     * Correct any over- or underflows in any of the date's members.
-     *
-     * @param integer $mask  We may not want to correct some overflows.
-     */
-    protected function _correct($mask = self::MASK_ALLPARTS)
-    {
-        if ($mask & self::MASK_SECOND) {
-            if ($this->_sec < 0 || $this->_sec > 59) {
-                $mask |= self::MASK_MINUTE;
-
-                $this->_min += (int)($this->_sec / 60);
-                $this->_sec %= 60;
-                if ($this->_sec < 0) {
-                    $this->_min--;
-                    $this->_sec += 60;
-                }
-            }
-        }
-
-        if ($mask & self::MASK_MINUTE) {
-            if ($this->_min < 0 || $this->_min > 59) {
-                $mask |= self::MASK_HOUR;
-
-                $this->_hour += (int)($this->_min / 60);
-                $this->_min %= 60;
-                if ($this->_min < 0) {
-                    $this->_hour--;
-                    $this->_min += 60;
-                }
-            }
-        }
-
-        if ($mask & self::MASK_HOUR) {
-            if ($this->_hour < 0 || $this->_hour > 23) {
-                $mask |= self::MASK_DAY;
-
-                $this->_mday += (int)($this->_hour / 24);
-                $this->_hour %= 24;
-                if ($this->_hour < 0) {
-                    $this->_mday--;
-                    $this->_hour += 24;
-                }
-            }
-        }
-
-        if ($mask & self::MASK_MONTH) {
-            $this->_year += (int)($this->_month / 12);
-            $this->_month %= 12;
-            if ($this->_month < 1) {
-                $this->_year--;
-                $this->_month += 12;
-            }
-        }
-
-        if ($mask & self::MASK_DAY) {
-            while ($this->_mday > 28 &&
-                   $this->_mday > Horde_Date::daysInMonth($this->_month, $this->_year)) {
-                $this->_mday -= Horde_Date::daysInMonth($this->_month, $this->_year);
-                ++$this->_month;
-                $this->_correct(self::MASK_MONTH);
-            }
-            while ($this->_mday < 1) {
-                --$this->_month;
-                $this->_correct(self::MASK_MONTH);
-                $this->_mday += Horde_Date::daysInMonth($this->_month, $this->_year);
-            }
-        }
-    }
-
-    /**
      * Compare this date to another date object to see which one is
      * greater (later). Assumes that the dates are in the same
      * timezone.
@@ -634,7 +680,7 @@ class Horde_Date
      *                  >=  1 if $this is greater (later)
      *                  <= -1 if $other is greater (later)
      */
-    function compareDate($other)
+    public function compareDate($other)
     {
         if (!is_a($other, 'Horde_Date')) {
             $other = new Horde_Date($other);
@@ -657,7 +703,7 @@ class Horde_Date
      *
      * @return boolean  True if this date is after the other.
      */
-    function after($other)
+    public function after($other)
     {
         return $this->compareDate($other) > 0;
     }
@@ -669,7 +715,7 @@ class Horde_Date
      *
      * @return boolean  True if this date is before the other.
      */
-    function before($other)
+    public function before($other)
     {
         return $this->compareDate($other) < 0;
     }
@@ -681,7 +727,7 @@ class Horde_Date
      *
      * @return boolean  True if this date is the same like the other.
      */
-    function equals($other)
+    public function equals($other)
     {
         return $this->compareDate($other) == 0;
     }
@@ -697,7 +743,7 @@ class Horde_Date
      *                  >=  1 if $this is greater (later)
      *                  <= -1 if $other is greater (later)
      */
-    function compareTime($other)
+    public function compareTime($other)
     {
         if (!is_a($other, 'Horde_Date')) {
             $other = new Horde_Date($other);
@@ -724,7 +770,7 @@ class Horde_Date
      *                  >=  1 if $this is greater (later)
      *                  <= -1 if $other is greater (later)
      */
-    function compareDateTime($other)
+    public function compareDateTime($other)
     {
         if (!is_a($other, 'Horde_Date')) {
             $other = new Horde_Date($other);
@@ -738,13 +784,25 @@ class Horde_Date
     }
 
     /**
+     * Returns number of days between this date and another.
+     *
+     * @param Horde_Date $other  The other day to diff with.
+     *
+     * @return integer  The absolute number of days between the two dates.
+     */
+    public function diff($other)
+    {
+        return abs($this->toDays() - $other->toDays());
+    }
+
+    /**
      * Get the time offset for local time zone.
      *
      * @param boolean $colon  Place a colon between hours and minutes?
      *
      * @return string  Timezone offset as a string in the format +HH:MM.
      */
-    function tzOffset($colon = true)
+    public function tzOffset($colon = true)
     {
         return $colon ? $this->format('P') : $this->format('O');
     }
@@ -754,7 +812,7 @@ class Horde_Date
      *
      * @return integer  A unix timestamp.
      */
-    function timestamp()
+    public function timestamp()
     {
         if ($this->_year >= 1970 && $this->_year < 2038) {
             return mktime($this->_hour, $this->_min, $this->_sec,
@@ -768,7 +826,7 @@ class Horde_Date
      *
      * @return integer  A unix timestamp.
      */
-    function datestamp()
+    public function datestamp()
     {
         if ($this->_year >= 1970 && $this->_year < 2038) {
             return mktime(0, 0, 0, $this->_month, $this->_mday, $this->_year);
@@ -782,7 +840,7 @@ class Horde_Date
      *
      * @return string  Date and time.
      */
-    function dateString()
+    public function dateString()
     {
         return sprintf('%04d%02d%02d', $this->_year, $this->_month, $this->_mday);
     }
@@ -792,7 +850,7 @@ class Horde_Date
      *
      * @return string  Date and time.
      */
-    function toJson()
+    public function toJson()
     {
         return $this->format(self::DATE_JSON);
     }
@@ -805,7 +863,7 @@ class Horde_Date
      *
      * @return string  Formatted time.
      */
-    function format($format)
+    public function format($format)
     {
         if (!isset($this->_formatCache[$format])) {
             $this->_formatCache[$format] = $this->toDateTime()->format($format);
@@ -818,7 +876,7 @@ class Horde_Date
      *
      * @return string  strftime() formatted date and time.
      */
-    function strftime($format)
+    public function strftime($format)
     {
         if (preg_match('/%[^' . self::$_supportedSpecs . ']/', $format)) {
             return strftime($format, $this->timestamp());
@@ -832,7 +890,7 @@ class Horde_Date
      *
      * @return string  strftime() formatted date and time.
      */
-    function _strftime($format)
+    protected function _strftime($format)
     {
         if (preg_match('/%[bBpxX]/', $format)) {
             require_once 'Horde/NLS.php';
@@ -885,6 +943,76 @@ class Horde_Date
     }
 
     /**
+     * Correct any over- or underflows in any of the date's members.
+     *
+     * @param integer $mask  We may not want to correct some overflows.
+     */
+    protected function _correct($mask = self::MASK_ALLPARTS)
+    {
+        if ($mask & self::MASK_SECOND) {
+            if ($this->_sec < 0 || $this->_sec > 59) {
+                $mask |= self::MASK_MINUTE;
+
+                $this->_min += (int)($this->_sec / 60);
+                $this->_sec %= 60;
+                if ($this->_sec < 0) {
+                    $this->_min--;
+                    $this->_sec += 60;
+                }
+            }
+        }
+
+        if ($mask & self::MASK_MINUTE) {
+            if ($this->_min < 0 || $this->_min > 59) {
+                $mask |= self::MASK_HOUR;
+
+                $this->_hour += (int)($this->_min / 60);
+                $this->_min %= 60;
+                if ($this->_min < 0) {
+                    $this->_hour--;
+                    $this->_min += 60;
+                }
+            }
+        }
+
+        if ($mask & self::MASK_HOUR) {
+            if ($this->_hour < 0 || $this->_hour > 23) {
+                $mask |= self::MASK_DAY;
+
+                $this->_mday += (int)($this->_hour / 24);
+                $this->_hour %= 24;
+                if ($this->_hour < 0) {
+                    $this->_mday--;
+                    $this->_hour += 24;
+                }
+            }
+        }
+
+        if ($mask & self::MASK_MONTH) {
+            $this->_year += (int)($this->_month / 12);
+            $this->_month %= 12;
+            if ($this->_month < 1) {
+                $this->_year--;
+                $this->_month += 12;
+            }
+        }
+
+        if ($mask & self::MASK_DAY) {
+            while ($this->_mday > 28 &&
+                   $this->_mday > Horde_Date::daysInMonth($this->_month, $this->_year)) {
+                $this->_mday -= Horde_Date::daysInMonth($this->_month, $this->_year);
+                ++$this->_month;
+                $this->_correct(self::MASK_MONTH);
+            }
+            while ($this->_mday < 1) {
+                --$this->_month;
+                $this->_correct(self::MASK_MONTH);
+                $this->_mday += Horde_Date::daysInMonth($this->_month, $this->_year);
+            }
+        }
+    }
+
+    /**
      * Handle args in order: year month day hour min sec tz
      */
     protected function _initializeFromArgs($args)
@@ -927,6 +1055,7 @@ class Horde_Date
             (string)(int)$date['minute'] == $date['minute']) {
             $this->_min = (int)$date['minute'];
         }
+
         $this->_correct();
     }