Overview

Packages

  • Date

Classes

  • Horde_Date
  • Horde_Date_Exception
  • Horde_Date_Recurrence
  • Horde_Date_Repeater
  • Horde_Date_Repeater_Day
  • Horde_Date_Repeater_DayName
  • Horde_Date_Repeater_DayPortion
  • Horde_Date_Repeater_Fortnight
  • Horde_Date_Repeater_Hour
  • Horde_Date_Repeater_Minute
  • Horde_Date_Repeater_Month
  • Horde_Date_Repeater_MonthName
  • Horde_Date_Repeater_Season
  • Horde_Date_Repeater_SeasonName
  • Horde_Date_Repeater_Second
  • Horde_Date_Repeater_Time
  • Horde_Date_Repeater_Week
  • Horde_Date_Repeater_Weekend
  • Horde_Date_Repeater_Year
  • Horde_Date_Span
  • Horde_Date_Translation
  • Horde_Date_Utils

Exceptions

  • Horde_Date_Repeater_Exception
  • Overview
  • Package
  • Class
  • Tree
   1: <?php
   2: /**
   3:  * Horde Date wrapper/logic class, including some calculation
   4:  * functions.
   5:  *
   6:  * @category Horde
   7:  * @package  Date
   8:  *
   9:  * @TODO in format():
  10:  *   http://php.net/intldateformatter
  11:  *
  12:  * @TODO on timezones:
  13:  *   http://trac.agavi.org/ticket/1008
  14:  *   http://trac.agavi.org/changeset/3659
  15:  *
  16:  * @TODO on switching to PHP::DateTime:
  17:  *   The only thing ever stored in the database *IS* Unix timestamps. Doing
  18:  *   anything other than that is unmanageable, yet some frameworks use 'server
  19:  *   based' times in their systems, simply because they do not bother with
  20:  *   daylight saving and only 'serve' one timezone!
  21:  *
  22:  *   The second you have to manage 'real' time across timezones then daylight
  23:  *   saving becomes essential, BUT only on the display side! Since the browser
  24:  *   only provides a time offset, this is useless and to be honest should simply
  25:  *   be ignored ( until it is upgraded to provide the correct information ;)
  26:  *   ). So we need a 'display' function that takes a simple numeric epoch, and a
  27:  *   separate timezone id into which the epoch is to be 'converted'. My W3C
  28:  *   mapping works simply because ADOdb then converts that to it's own simple
  29:  *   offset abbreviation - in my case GMT or BST. As long as DateTime passes the
  30:  *   full 64 bit number the date range from 100AD is also preserved ( and
  31:  *   further back if 2 digit years are disabled ). If I want to display the
  32:  *   'real' timezone with this 'time' then I just add it in place of ADOdb's
  33:  *   'timezone'. I am tempted to simply adjust the ADOdb class to take a
  34:  *   timezone in place of the simple GMT switch it currently uses.
  35:  *
  36:  *   The return path is just the reverse and simply needs to take the client
  37:  *   display offset off prior to storage of the UTC epoch. SO we use
  38:  *   DateTimeZone to get an offset value for the clients timezone and simply add
  39:  *   or subtract this from a timezone agnostic display on the client end when
  40:  *   entering new times.
  41:  *
  42:  *
  43:  *   It's not really feasible to store dates in specific timezone, as most
  44:  *   national/local timezones support DST - and that is a pain to support, as
  45:  *   eg.  sorting breaks when some timestamps get repeated. That's why it's
  46:  *   usually better to store datetimes as either UTC datetime or plain unix
  47:  *   timestamp. I usually go with the former - using database datetime type.
  48:  */
  49: 
  50: /**
  51:  * @category Horde
  52:  * @package  Date
  53:  */
  54: class Horde_Date
  55: {
  56:     const DATE_SUNDAY = 0;
  57:     const DATE_MONDAY = 1;
  58:     const DATE_TUESDAY = 2;
  59:     const DATE_WEDNESDAY = 3;
  60:     const DATE_THURSDAY = 4;
  61:     const DATE_FRIDAY = 5;
  62:     const DATE_SATURDAY = 6;
  63: 
  64:     const MASK_SUNDAY = 1;
  65:     const MASK_MONDAY = 2;
  66:     const MASK_TUESDAY = 4;
  67:     const MASK_WEDNESDAY = 8;
  68:     const MASK_THURSDAY = 16;
  69:     const MASK_FRIDAY = 32;
  70:     const MASK_SATURDAY = 64;
  71:     const MASK_WEEKDAYS = 62;
  72:     const MASK_WEEKEND = 65;
  73:     const MASK_ALLDAYS = 127;
  74: 
  75:     const MASK_SECOND = 1;
  76:     const MASK_MINUTE = 2;
  77:     const MASK_HOUR = 4;
  78:     const MASK_DAY = 8;
  79:     const MASK_MONTH = 16;
  80:     const MASK_YEAR = 32;
  81:     const MASK_ALLPARTS = 63;
  82: 
  83:     const DATE_DEFAULT = 'Y-m-d H:i:s';
  84:     const DATE_JSON = 'Y-m-d\TH:i:s';
  85: 
  86:     /**
  87:      * Year
  88:      *
  89:      * @var integer
  90:      */
  91:     protected $_year;
  92: 
  93:     /**
  94:      * Month
  95:      *
  96:      * @var integer
  97:      */
  98:     protected $_month;
  99: 
 100:     /**
 101:      * Day
 102:      *
 103:      * @var integer
 104:      */
 105:     protected $_mday;
 106: 
 107:     /**
 108:      * Hour
 109:      *
 110:      * @var integer
 111:      */
 112:     protected $_hour = 0;
 113: 
 114:     /**
 115:      * Minute
 116:      *
 117:      * @var integer
 118:      */
 119:     protected $_min = 0;
 120: 
 121:     /**
 122:      * Second
 123:      *
 124:      * @var integer
 125:      */
 126:     protected $_sec = 0;
 127: 
 128:     /**
 129:      * String representation of the date's timezone.
 130:      *
 131:      * @var string
 132:      */
 133:     protected $_timezone;
 134: 
 135:     /**
 136:      * Default format for __toString()
 137:      *
 138:      * @var string
 139:      */
 140:     protected $_defaultFormat = self::DATE_DEFAULT;
 141: 
 142:     /**
 143:      * Default specs that are always supported.
 144:      * @var string
 145:      */
 146:     protected static $_defaultSpecs = '%CdDeHImMnRStTyY';
 147: 
 148:     /**
 149:      * Internally supported strftime() specifiers.
 150:      * @var string
 151:      */
 152:     protected static $_supportedSpecs = '';
 153: 
 154:     /**
 155:      * Map of required correction masks.
 156:      *
 157:      * @see __set()
 158:      *
 159:      * @var array
 160:      */
 161:     protected static $_corrections = array(
 162:         'year'  => self::MASK_YEAR,
 163:         'month' => self::MASK_MONTH,
 164:         'mday'  => self::MASK_DAY,
 165:         'hour'  => self::MASK_HOUR,
 166:         'min'   => self::MASK_MINUTE,
 167:         'sec'   => self::MASK_SECOND,
 168:     );
 169: 
 170:     protected $_formatCache = array();
 171: 
 172:     /**
 173:      * Builds a new date object. If $date contains date parts, use them to
 174:      * initialize the object.
 175:      *
 176:      * Recognized formats:
 177:      * - arrays with keys 'year', 'month', 'mday', 'day'
 178:      *   'hour', 'min', 'minute', 'sec'
 179:      * - objects with properties 'year', 'month', 'mday', 'hour', 'min', 'sec'
 180:      * - yyyy-mm-dd hh:mm:ss
 181:      * - yyyymmddhhmmss
 182:      * - yyyymmddThhmmssZ
 183:      * - yyyymmdd (might conflict with unix timestamps between 31 Oct 1966 and
 184:      *   03 Mar 1973)
 185:      * - unix timestamps
 186:      * - anything parsed by strtotime()/DateTime.
 187:      *
 188:      * @throws Horde_Date_Exception
 189:      */
 190:     public function __construct($date = null, $timezone = null)
 191:     {
 192:         if (!self::$_supportedSpecs) {
 193:             self::$_supportedSpecs = self::$_defaultSpecs;
 194:             if (function_exists('nl_langinfo')) {
 195:                 self::$_supportedSpecs .= 'bBpxX';
 196:             }
 197:         }
 198: 
 199:         if (func_num_args() > 2) {
 200:             // Handle args in order: year month day hour min sec tz
 201:             $this->_initializeFromArgs(func_get_args());
 202:             return;
 203:         }
 204: 
 205:         $this->_initializeTimezone($timezone);
 206: 
 207:         if (is_null($date)) {
 208:             return;
 209:         }
 210: 
 211:         if (is_string($date)) {
 212:             $date = trim($date, '"');
 213:         }
 214: 
 215:         if (is_object($date)) {
 216:             $this->_initializeFromObject($date);
 217:         } elseif (is_array($date)) {
 218:             $this->_initializeFromArray($date);
 219:         } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})T? ?(\d{2}):?(\d{2}):?(\d{2})(?:\.\d+)?(Z?)$/', $date, $parts)) {
 220:             $this->_year  = (int)$parts[1];
 221:             $this->_month = (int)$parts[2];
 222:             $this->_mday  = (int)$parts[3];
 223:             $this->_hour  = (int)$parts[4];
 224:             $this->_min   = (int)$parts[5];
 225:             $this->_sec   = (int)$parts[6];
 226:             if ($parts[7]) {
 227:                 $this->_initializeTimezone('UTC');
 228:             }
 229:         } elseif (preg_match('/^(\d{4})-?(\d{2})-?(\d{2})$/', $date, $parts) &&
 230:                   $parts[2] > 0 && $parts[2] <= 12 &&
 231:                   $parts[3] > 0 && $parts[3] <= 31) {
 232:             $this->_year  = (int)$parts[1];
 233:             $this->_month = (int)$parts[2];
 234:             $this->_mday  = (int)$parts[3];
 235:             $this->_hour = $this->_min = $this->_sec = 0;
 236:         } elseif ((string)(int)$date == $date) {
 237:             // Try as a timestamp.
 238:             $parts = @getdate($date);
 239:             if ($parts) {
 240:                 $this->_year  = $parts['year'];
 241:                 $this->_month = $parts['mon'];
 242:                 $this->_mday  = $parts['mday'];
 243:                 $this->_hour  = $parts['hours'];
 244:                 $this->_min   = $parts['minutes'];
 245:                 $this->_sec   = $parts['seconds'];
 246:             }
 247:         } else {
 248:             // Use date_create() so we can catch errors with PHP 5.2. Use
 249:             // "new DateTime() once we require 5.3.
 250:             $parsed = date_create($date);
 251:             if (!$parsed) {
 252:                 throw new Horde_Date_Exception(sprintf(Horde_Date_Translation::t("Failed to parse time string (%s)"), $date));
 253:             }
 254:             $parsed->setTimezone(new DateTimeZone(date_default_timezone_get()));
 255:             $this->_year  = (int)$parsed->format('Y');
 256:             $this->_month = (int)$parsed->format('m');
 257:             $this->_mday  = (int)$parsed->format('d');
 258:             $this->_hour  = (int)$parsed->format('H');
 259:             $this->_min   = (int)$parsed->format('i');
 260:             $this->_sec   = (int)$parsed->format('s');
 261:             $this->_initializeTimezone(date_default_timezone_get());
 262:         }
 263:     }
 264: 
 265:     /**
 266:      * Returns a simple string representation of the date object
 267:      *
 268:      * @return string  This object converted to a string.
 269:      */
 270:     public function __toString()
 271:     {
 272:         try {
 273:             return $this->format($this->_defaultFormat);
 274:         } catch (Exception $e) {
 275:             return '';
 276:         }
 277:     }
 278: 
 279:     /**
 280:      * Returns a DateTime object representing this object.
 281:      *
 282:      * @return DateTime
 283:      */
 284:     public function toDateTime()
 285:     {
 286:         $date = new DateTime(null, new DateTimeZone($this->_timezone));
 287:         $date->setDate($this->_year, $this->_month, $this->_mday);
 288:         $date->setTime($this->_hour, $this->_min, $this->_sec);
 289:         return $date;
 290:     }
 291: 
 292:     /**
 293:      * Converts a date in the proleptic Gregorian calendar to the no of days
 294:      * since 24th November, 4714 B.C.
 295:      *
 296:      * Returns the no of days since Monday, 24th November, 4714 B.C. in the
 297:      * proleptic Gregorian calendar (which is 24th November, -4713 using
 298:      * 'Astronomical' year numbering, and 1st January, 4713 B.C. in the
 299:      * proleptic Julian calendar).  This is also the first day of the 'Julian
 300:      * Period' proposed by Joseph Scaliger in 1583, and the number of days
 301:      * since this date is known as the 'Julian Day'.  (It is not directly
 302:      * to do with the Julian calendar, although this is where the name
 303:      * is derived from.)
 304:      *
 305:      * The algorithm is valid for all years (positive and negative), and
 306:      * also for years preceding 4714 B.C.
 307:      *
 308:      * Algorithm is from PEAR::Date_Calc
 309:      *
 310:      * @author Monte Ohrt <monte@ispi.net>
 311:      * @author Pierre-Alain Joye <pajoye@php.net>
 312:      * @author Daniel Convissor <danielc@php.net>
 313:      * @author C.A. Woodcock <c01234@netcomuk.co.uk>
 314:      *
 315:      * @return integer  The number of days since 24th November, 4714 B.C.
 316:      */
 317:     public function toDays()
 318:     {
 319:         if (function_exists('GregorianToJD')) {
 320:             return gregoriantojd($this->_month, $this->_mday, $this->_year);
 321:         }
 322: 
 323:         $day = $this->_mday;
 324:         $month = $this->_month;
 325:         $year = $this->_year;
 326: 
 327:         if ($month > 2) {
 328:             // March = 0, April = 1, ..., December = 9,
 329:             // January = 10, February = 11
 330:             $month -= 3;
 331:         } else {
 332:             $month += 9;
 333:             --$year;
 334:         }
 335: 
 336:         $hb_negativeyear = $year < 0;
 337:         $century         = intval($year / 100);
 338:         $year            = $year % 100;
 339: 
 340:         if ($hb_negativeyear) {
 341:             // Subtract 1 because year 0 is a leap year;
 342:             // And N.B. that we must treat the leap years as occurring
 343:             // one year earlier than they do, because for the purposes
 344:             // of calculation, the year starts on 1st March:
 345:             //
 346:             return intval((14609700 * $century + ($year == 0 ? 1 : 0)) / 400) +
 347:                    intval((1461 * $year + 1) / 4) +
 348:                    intval((153 * $month + 2) / 5) +
 349:                    $day + 1721118;
 350:         } else {
 351:             return intval(146097 * $century / 4) +
 352:                    intval(1461 * $year / 4) +
 353:                    intval((153 * $month + 2) / 5) +
 354:                    $day + 1721119;
 355:         }
 356:     }
 357: 
 358:     /**
 359:      * Converts number of days since 24th November, 4714 B.C. (in the proleptic
 360:      * Gregorian calendar, which is year -4713 using 'Astronomical' year
 361:      * numbering) to Gregorian calendar date.
 362:      *
 363:      * Returned date belongs to the proleptic Gregorian calendar, using
 364:      * 'Astronomical' year numbering.
 365:      *
 366:      * The algorithm is valid for all years (positive and negative), and
 367:      * also for years preceding 4714 B.C. (i.e. for negative 'Julian Days'),
 368:      * and so the only limitation is platform-dependent (for 32-bit systems
 369:      * the maximum year would be something like about 1,465,190 A.D.).
 370:      *
 371:      * N.B. Monday, 24th November, 4714 B.C. is Julian Day '0'.
 372:      *
 373:      * Algorithm is from PEAR::Date_Calc
 374:      *
 375:      * @author Monte Ohrt <monte@ispi.net>
 376:      * @author Pierre-Alain Joye <pajoye@php.net>
 377:      * @author Daniel Convissor <danielc@php.net>
 378:      * @author C.A. Woodcock <c01234@netcomuk.co.uk>
 379:      *
 380:      * @param int    $days   the number of days since 24th November, 4714 B.C.
 381:      * @param string $format the string indicating how to format the output
 382:      *
 383:      * @return  Horde_Date  A Horde_Date object representing the date.
 384:      */
 385:     public static function fromDays($days)
 386:     {
 387:         if (function_exists('JDToGregorian')) {
 388:             list($month, $day, $year) = explode('/', JDToGregorian($days));
 389:         } else {
 390:             $days = intval($days);
 391: 
 392:             $days   -= 1721119;
 393:             $century = floor((4 * $days - 1) / 146097);
 394:             $days    = floor(4 * $days - 1 - 146097 * $century);
 395:             $day     = floor($days / 4);
 396: 
 397:             $year = floor((4 * $day +  3) / 1461);
 398:             $day  = floor(4 * $day +  3 - 1461 * $year);
 399:             $day  = floor(($day +  4) / 4);
 400: 
 401:             $month = floor((5 * $day - 3) / 153);
 402:             $day   = floor(5 * $day - 3 - 153 * $month);
 403:             $day   = floor(($day +  5) /  5);
 404: 
 405:             $year = $century * 100 + $year;
 406:             if ($month < 10) {
 407:                 $month +=3;
 408:             } else {
 409:                 $month -=9;
 410:                 ++$year;
 411:             }
 412:         }
 413: 
 414:         return new Horde_Date($year, $month, $day);
 415:     }
 416: 
 417:     /**
 418:      * Getter for the date and time properties.
 419:      *
 420:      * @param string $name  One of 'year', 'month', 'mday', 'hour', 'min' or
 421:      *                      'sec'.
 422:      *
 423:      * @return integer  The property value, or null if not set.
 424:      */
 425:     public function __get($name)
 426:     {
 427:         if ($name == 'day') {
 428:             $name = 'mday';
 429:         }
 430:         if ($name[0] == '_') {
 431:             return null;
 432:         }
 433:         return $this->{'_' . $name};
 434:     }
 435: 
 436:     /**
 437:      * Setter for the date and time properties.
 438:      *
 439:      * @param string $name    One of 'year', 'month', 'mday', 'hour', 'min' or
 440:      *                        'sec'.
 441:      * @param integer $value  The property value.
 442:      */
 443:     public function __set($name, $value)
 444:     {
 445:         if ($name == 'timezone') {
 446:             $this->_initializeTimezone($value);
 447:             return;
 448:         }
 449:         if ($name == 'day') {
 450:             $name = 'mday';
 451:         }
 452: 
 453:         if ($name != 'year' && $name != 'month' && $name != 'mday' &&
 454:             $name != 'hour' && $name != 'min' && $name != 'sec') {
 455:             throw new InvalidArgumentException('Undefined property ' . $name);
 456:         }
 457: 
 458:         $down = $value < $this->{'_' . $name};
 459:         $this->{'_' . $name} = $value;
 460:         $this->_correct(self::$_corrections[$name], $down);
 461:         $this->_formatCache = array();
 462:     }
 463: 
 464:     /**
 465:      * Returns whether a date or time property exists.
 466:      *
 467:      * @param string $name  One of 'year', 'month', 'mday', 'hour', 'min' or
 468:      *                      'sec'.
 469:      *
 470:      * @return boolen  True if the property exists and is set.
 471:      */
 472:     public function __isset($name)
 473:     {
 474:         if ($name == 'day') {
 475:             $name = 'mday';
 476:         }
 477:         return ($name == 'year' || $name == 'month' || $name == 'mday' ||
 478:                 $name == 'hour' || $name == 'min' || $name == 'sec') &&
 479:             isset($this->{'_' . $name});
 480:     }
 481: 
 482:     /**
 483:      * Adds a number of seconds or units to this date, returning a new Date
 484:      * object.
 485:      */
 486:     public function add($factor)
 487:     {
 488:         $d = clone($this);
 489:         if (is_array($factor) || is_object($factor)) {
 490:             foreach ($factor as $property => $value) {
 491:                 $d->$property += $value;
 492:             }
 493:         } else {
 494:             $d->sec += $factor;
 495:         }
 496: 
 497:         return $d;
 498:     }
 499: 
 500:     /**
 501:      * Subtracts a number of seconds or units from this date, returning a new
 502:      * Horde_Date object.
 503:      */
 504:     public function sub($factor)
 505:     {
 506:         if (is_array($factor)) {
 507:             foreach ($factor as &$value) {
 508:                 $value *= -1;
 509:             }
 510:         } else {
 511:             $factor *= -1;
 512:         }
 513: 
 514:         return $this->add($factor);
 515:     }
 516: 
 517:     /**
 518:      * Converts this object to a different timezone.
 519:      *
 520:      * @param string $timezone  The new timezone.
 521:      *
 522:      * @return Horde_Date  This object.
 523:      */
 524:     public function setTimezone($timezone)
 525:     {
 526:         $date = $this->toDateTime();
 527:         $date->setTimezone(new DateTimeZone($timezone));
 528:         $this->_timezone = $timezone;
 529:         $this->_year     = (int)$date->format('Y');
 530:         $this->_month    = (int)$date->format('m');
 531:         $this->_mday     = (int)$date->format('d');
 532:         $this->_hour     = (int)$date->format('H');
 533:         $this->_min      = (int)$date->format('i');
 534:         $this->_sec      = (int)$date->format('s');
 535:         $this->_formatCache = array();
 536:         return $this;
 537:     }
 538: 
 539:     /**
 540:      * Sets the default date format used in __toString()
 541:      *
 542:      * @param string $format
 543:      */
 544:     public function setDefaultFormat($format)
 545:     {
 546:         $this->_defaultFormat = $format;
 547:     }
 548: 
 549:     /**
 550:      * Returns the day of the week (0 = Sunday, 6 = Saturday) of this date.
 551:      *
 552:      * @return integer  The day of the week.
 553:      */
 554:     public function dayOfWeek()
 555:     {
 556:         if ($this->_month > 2) {
 557:             $month = $this->_month - 2;
 558:             $year = $this->_year;
 559:         } else {
 560:             $month = $this->_month + 10;
 561:             $year = $this->_year - 1;
 562:         }
 563: 
 564:         $day = (floor((13 * $month - 1) / 5) +
 565:                 $this->_mday + ($year % 100) +
 566:                 floor(($year % 100) / 4) +
 567:                 floor(($year / 100) / 4) - 2 *
 568:                 floor($year / 100) + 77);
 569: 
 570:         return (int)($day - 7 * floor($day / 7));
 571:     }
 572: 
 573:     /**
 574:      * Returns the day number of the year (1 to 365/366).
 575:      *
 576:      * @return integer  The day of the year.
 577:      */
 578:     public function dayOfYear()
 579:     {
 580:         return $this->format('z') + 1;
 581:     }
 582: 
 583:     /**
 584:      * Returns the week of the month.
 585:      *
 586:      * @return integer  The week number.
 587:      */
 588:     public function weekOfMonth()
 589:     {
 590:         return ceil($this->_mday / 7);
 591:     }
 592: 
 593:     /**
 594:      * Returns the week of the year, first Monday is first day of first week.
 595:      *
 596:      * @return integer  The week number.
 597:      */
 598:     public function weekOfYear()
 599:     {
 600:         return $this->format('W');
 601:     }
 602: 
 603:     /**
 604:      * Returns the number of weeks in the given year (52 or 53).
 605:      *
 606:      * @param integer $year  The year to count the number of weeks in.
 607:      *
 608:      * @return integer $numWeeks   The number of weeks in $year.
 609:      */
 610:     public static function weeksInYear($year)
 611:     {
 612:         // Find the last Thursday of the year.
 613:         $date = new Horde_Date($year . '-12-31');
 614:         while ($date->dayOfWeek() != self::DATE_THURSDAY) {
 615:             --$date->mday;
 616:         }
 617:         return $date->weekOfYear();
 618:     }
 619: 
 620:     /**
 621:      * Sets the date of this object to the $nth weekday of $weekday.
 622:      *
 623:      * @param integer $weekday  The day of the week (0 = Sunday, etc).
 624:      * @param integer $nth      The $nth $weekday to set to (defaults to 1).
 625:      */
 626:     public function setNthWeekday($weekday, $nth = 1)
 627:     {
 628:         if ($weekday < self::DATE_SUNDAY || $weekday > self::DATE_SATURDAY) {
 629:             return;
 630:         }
 631: 
 632:         $this->_mday = 1;
 633:         $first = $this->dayOfWeek();
 634:         if ($weekday < $first) {
 635:             $this->_mday = 8 + $weekday - $first;
 636:         } else {
 637:             $this->_mday = $weekday - $first + 1;
 638:         }
 639:         $diff = 7 * $nth - 7;
 640:         $this->_mday += $diff;
 641:         $this->_correct(self::MASK_DAY, $diff < 0);
 642:     }
 643: 
 644:     /**
 645:      * Is the date currently represented by this object a valid date?
 646:      *
 647:      * @return boolean  Validity, counting leap years, etc.
 648:      */
 649:     public function isValid()
 650:     {
 651:         return ($this->_year >= 0 && $this->_year <= 9999);
 652:     }
 653: 
 654:     /**
 655:      * Compares this date to another date object to see which one is
 656:      * greater (later). Assumes that the dates are in the same
 657:      * timezone.
 658:      *
 659:      * @param mixed $other  The date to compare to.
 660:      *
 661:      * @return integer  ==  0 if they are on the same date
 662:      *                  >=  1 if $this is greater (later)
 663:      *                  <= -1 if $other is greater (later)
 664:      */
 665:     public function compareDate($other)
 666:     {
 667:         if (!($other instanceof Horde_Date)) {
 668:             $other = new Horde_Date($other);
 669:         }
 670: 
 671:         if ($this->_year != $other->year) {
 672:             return $this->_year - $other->year;
 673:         }
 674:         if ($this->_month != $other->month) {
 675:             return $this->_month - $other->month;
 676:         }
 677: 
 678:         return $this->_mday - $other->mday;
 679:     }
 680: 
 681:     /**
 682:      * Returns whether this date is after the other.
 683:      *
 684:      * @param mixed $other  The date to compare to.
 685:      *
 686:      * @return boolean  True if this date is after the other.
 687:      */
 688:     public function after($other)
 689:     {
 690:         return $this->compareDate($other) > 0;
 691:     }
 692: 
 693:     /**
 694:      * Returns whether this date is before the other.
 695:      *
 696:      * @param mixed $other  The date to compare to.
 697:      *
 698:      * @return boolean  True if this date is before the other.
 699:      */
 700:     public function before($other)
 701:     {
 702:         return $this->compareDate($other) < 0;
 703:     }
 704: 
 705:     /**
 706:      * Returns whether this date is the same like the other.
 707:      *
 708:      * @param mixed $other  The date to compare to.
 709:      *
 710:      * @return boolean  True if this date is the same like the other.
 711:      */
 712:     public function equals($other)
 713:     {
 714:         return $this->compareDate($other) == 0;
 715:     }
 716: 
 717:     /**
 718:      * Compares this to another date object by time, to see which one
 719:      * is greater (later). Assumes that the dates are in the same
 720:      * timezone.
 721:      *
 722:      * @param mixed $other  The date to compare to.
 723:      *
 724:      * @return integer  ==  0 if they are at the same time
 725:      *                  >=  1 if $this is greater (later)
 726:      *                  <= -1 if $other is greater (later)
 727:      */
 728:     public function compareTime($other)
 729:     {
 730:         if (!($other instanceof Horde_Date)) {
 731:             $other = new Horde_Date($other);
 732:         }
 733: 
 734:         if ($this->_hour != $other->hour) {
 735:             return $this->_hour - $other->hour;
 736:         }
 737:         if ($this->_min != $other->min) {
 738:             return $this->_min - $other->min;
 739:         }
 740: 
 741:         return $this->_sec - $other->sec;
 742:     }
 743: 
 744:     /**
 745:      * Compares this to another date object, including times, to see
 746:      * which one is greater (later). Assumes that the dates are in the
 747:      * same timezone.
 748:      *
 749:      * @param mixed $other  The date to compare to.
 750:      *
 751:      * @return integer  ==  0 if they are equal
 752:      *                  >=  1 if $this is greater (later)
 753:      *                  <= -1 if $other is greater (later)
 754:      */
 755:     public function compareDateTime($other)
 756:     {
 757:         if (!($other instanceof Horde_Date)) {
 758:             $other = new Horde_Date($other);
 759:         }
 760: 
 761:         if ($diff = $this->compareDate($other)) {
 762:             return $diff;
 763:         }
 764: 
 765:         return $this->compareTime($other);
 766:     }
 767: 
 768:     /**
 769:      * Returns number of days between this date and another.
 770:      *
 771:      * @param Horde_Date $other  The other day to diff with.
 772:      *
 773:      * @return integer  The absolute number of days between the two dates.
 774:      */
 775:     public function diff($other)
 776:     {
 777:         return abs($this->toDays() - $other->toDays());
 778:     }
 779: 
 780:     /**
 781:      * Returns the time offset for local time zone.
 782:      *
 783:      * @param boolean $colon  Place a colon between hours and minutes?
 784:      *
 785:      * @return string  Timezone offset as a string in the format +HH:MM.
 786:      */
 787:     public function tzOffset($colon = true)
 788:     {
 789:         return $colon ? $this->format('P') : $this->format('O');
 790:     }
 791: 
 792:     /**
 793:      * Returns the unix timestamp representation of this date.
 794:      *
 795:      * @return integer  A unix timestamp.
 796:      */
 797:     public function timestamp()
 798:     {
 799:         if ($this->_year >= 1970 && $this->_year < 2038) {
 800:             return mktime($this->_hour, $this->_min, $this->_sec,
 801:                           $this->_month, $this->_mday, $this->_year);
 802:         }
 803:         return $this->format('U');
 804:     }
 805: 
 806:     /**
 807:      * Returns the unix timestamp representation of this date, 12:00am.
 808:      *
 809:      * @return integer  A unix timestamp.
 810:      */
 811:     public function datestamp()
 812:     {
 813:         if ($this->_year >= 1970 && $this->_year < 2038) {
 814:             return mktime(0, 0, 0, $this->_month, $this->_mday, $this->_year);
 815:         }
 816:         $date = new DateTime($this->format('Y-m-d'));
 817:         return $date->format('U');
 818:     }
 819: 
 820:     /**
 821:      * Formats date and time to be passed around as a short url parameter.
 822:      *
 823:      * @return string  Date and time.
 824:      */
 825:     public function dateString()
 826:     {
 827:         return sprintf('%04d%02d%02d', $this->_year, $this->_month, $this->_mday);
 828:     }
 829: 
 830:     /**
 831:      * Formats date and time to the ISO format used by JSON.
 832:      *
 833:      * @return string  Date and time.
 834:      */
 835:     public function toJson()
 836:     {
 837:         return $this->format(self::DATE_JSON);
 838:     }
 839: 
 840:     /**
 841:      * Formats date and time to the RFC 2445 iCalendar DATE-TIME format.
 842:      *
 843:      * @param boolean $floating  Whether to return a floating date-time
 844:      *                           (without time zone information).
 845:      *
 846:      * @return string  Date and time.
 847:      */
 848:     public function toiCalendar($floating = false)
 849:     {
 850:         if ($floating) {
 851:             return $this->format('Ymd\THis');
 852:         }
 853:         $dateTime = $this->toDateTime();
 854:         $dateTime->setTimezone(new DateTimeZone('UTC'));
 855:         return $dateTime->format('Ymd\THis\Z');
 856:     }
 857: 
 858:     /**
 859:      * Formats time using the specifiers available in date() or in the DateTime
 860:      * class' format() method.
 861:      *
 862:      * To format in languages other than English, use strftime() instead.
 863:      *
 864:      * @param string $format
 865:      *
 866:      * @return string  Formatted time.
 867:      */
 868:     public function format($format)
 869:     {
 870:         if (!isset($this->_formatCache[$format])) {
 871:             $this->_formatCache[$format] = $this->toDateTime()->format($format);
 872:         }
 873:         return $this->_formatCache[$format];
 874:     }
 875: 
 876:     /**
 877:      * Formats date and time using strftime() format.
 878:      *
 879:      * @return string  strftime() formatted date and time.
 880:      */
 881:     public function strftime($format)
 882:     {
 883:         if (preg_match('/%[^' . self::$_supportedSpecs . ']/', $format)) {
 884:             return strftime($format, $this->timestamp());
 885:         } else {
 886:             return $this->_strftime($format);
 887:         }
 888:     }
 889: 
 890:     /**
 891:      * Formats date and time using a limited set of the strftime() format.
 892:      *
 893:      * @return string  strftime() formatted date and time.
 894:      */
 895:     protected function _strftime($format)
 896:     {
 897:         return preg_replace(
 898:             array('/%b/e',
 899:                   '/%B/e',
 900:                   '/%C/e',
 901:                   '/%d/e',
 902:                   '/%D/e',
 903:                   '/%e/e',
 904:                   '/%H/e',
 905:                   '/%I/e',
 906:                   '/%m/e',
 907:                   '/%M/e',
 908:                   '/%n/',
 909:                   '/%p/e',
 910:                   '/%R/e',
 911:                   '/%S/e',
 912:                   '/%t/',
 913:                   '/%T/e',
 914:                   '/%x/e',
 915:                   '/%X/e',
 916:                   '/%y/e',
 917:                   '/%Y/',
 918:                   '/%%/'),
 919:             array('$this->_strftime(Horde_Nls::getLangInfo(constant(\'ABMON_\' . (int)$this->_month)))',
 920:                   '$this->_strftime(Horde_Nls::getLangInfo(constant(\'MON_\' . (int)$this->_month)))',
 921:                   '(int)($this->_year / 100)',
 922:                   'sprintf(\'%02d\', $this->_mday)',
 923:                   '$this->_strftime(\'%m/%d/%y\')',
 924:                   'sprintf(\'%2d\', $this->_mday)',
 925:                   'sprintf(\'%02d\', $this->_hour)',
 926:                   'sprintf(\'%02d\', $this->_hour == 0 ? 12 : ($this->_hour > 12 ? $this->_hour - 12 : $this->_hour))',
 927:                   'sprintf(\'%02d\', $this->_month)',
 928:                   'sprintf(\'%02d\', $this->_min)',
 929:                   "\n",
 930:                   '$this->_strftime(Horde_Nls::getLangInfo($this->_hour < 12 ? AM_STR : PM_STR))',
 931:                   '$this->_strftime(\'%H:%M\')',
 932:                   'sprintf(\'%02d\', $this->_sec)',
 933:                   "\t",
 934:                   '$this->_strftime(\'%H:%M:%S\')',
 935:                   '$this->_strftime(Horde_Nls::getLangInfo(D_FMT))',
 936:                   '$this->_strftime(Horde_Nls::getLangInfo(T_FMT))',
 937:                   'substr(sprintf(\'%04d\', $this->_year), -2)',
 938:                   (int)$this->_year,
 939:                   '%'),
 940:             $format);
 941:     }
 942: 
 943:     /**
 944:      * Corrects any over- or underflows in any of the date's members.
 945:      *
 946:      * @param integer $mask  We may not want to correct some overflows.
 947:      * @param integer $down  Whether to correct the date up or down.
 948:      */
 949:     protected function _correct($mask = self::MASK_ALLPARTS, $down = false)
 950:     {
 951:         if ($mask & self::MASK_SECOND) {
 952:             if ($this->_sec < 0 || $this->_sec > 59) {
 953:                 $mask |= self::MASK_MINUTE;
 954: 
 955:                 $this->_min += (int)($this->_sec / 60);
 956:                 $this->_sec %= 60;
 957:                 if ($this->_sec < 0) {
 958:                     $this->_min--;
 959:                     $this->_sec += 60;
 960:                 }
 961:             }
 962:         }
 963: 
 964:         if ($mask & self::MASK_MINUTE) {
 965:             if ($this->_min < 0 || $this->_min > 59) {
 966:                 $mask |= self::MASK_HOUR;
 967: 
 968:                 $this->_hour += (int)($this->_min / 60);
 969:                 $this->_min %= 60;
 970:                 if ($this->_min < 0) {
 971:                     $this->_hour--;
 972:                     $this->_min += 60;
 973:                 }
 974:             }
 975:         }
 976: 
 977:         if ($mask & self::MASK_HOUR) {
 978:             if ($this->_hour < 0 || $this->_hour > 23) {
 979:                 $mask |= self::MASK_DAY;
 980: 
 981:                 $this->_mday += (int)($this->_hour / 24);
 982:                 $this->_hour %= 24;
 983:                 if ($this->_hour < 0) {
 984:                     $this->_mday--;
 985:                     $this->_hour += 24;
 986:                 }
 987:             }
 988:         }
 989: 
 990:         if ($mask & self::MASK_MONTH) {
 991:             $this->_correctMonth($down);
 992:             /* When correcting the month, always correct the day too. Months
 993:              * have different numbers of days. */
 994:             $mask |= self::MASK_DAY;
 995:         }
 996: 
 997:         if ($mask & self::MASK_DAY) {
 998:             while ($this->_mday > 28 &&
 999:                    $this->_mday > Horde_Date_Utils::daysInMonth($this->_month, $this->_year)) {
1000:                 if ($down) {
1001:                     $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month + 1, $this->_year) - Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
1002:                 } else {
1003:                     $this->_mday -= Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
1004:                     $this->_month++;
1005:                 }
1006:                 $this->_correctMonth($down);
1007:             }
1008:             while ($this->_mday < 1) {
1009:                 --$this->_month;
1010:                 $this->_correctMonth($down);
1011:                 $this->_mday += Horde_Date_Utils::daysInMonth($this->_month, $this->_year);
1012:             }
1013:         }
1014:     }
1015: 
1016:     /**
1017:      * Corrects the current month.
1018:      *
1019:      * This cannot be done in _correct() because that would also trigger a
1020:      * correction of the day, which would result in an infinite loop.
1021:      *
1022:      * @param integer $down  Whether to correct the date up or down.
1023:      */
1024:     protected function _correctMonth($down = false)
1025:     {
1026:         $this->_year += (int)($this->_month / 12);
1027:         $this->_month %= 12;
1028:         if ($this->_month < 1) {
1029:             $this->_year--;
1030:             $this->_month += 12;
1031:         }
1032:     }
1033: 
1034:     /**
1035:      * Handles args in order: year month day hour min sec tz
1036:      */
1037:     protected function _initializeFromArgs($args)
1038:     {
1039:         $tz = (isset($args[6])) ? array_pop($args) : null;
1040:         $this->_initializeTimezone($tz);
1041: 
1042:         $args = array_slice($args, 0, 6);
1043:         $keys = array('year' => 1, 'month' => 1, 'mday' => 1, 'hour' => 0, 'min' => 0, 'sec' => 0);
1044:         $date = array_combine(array_slice(array_keys($keys), 0, count($args)), $args);
1045:         $date = array_merge($keys, $date);
1046: 
1047:         $this->_initializeFromArray($date);
1048:     }
1049: 
1050:     protected function _initializeFromArray($date)
1051:     {
1052:         if (isset($date['year']) && is_string($date['year']) && strlen($date['year']) == 2) {
1053:             if ($date['year'] > 70) {
1054:                 $date['year'] += 1900;
1055:             } else {
1056:                 $date['year'] += 2000;
1057:             }
1058:         }
1059: 
1060:         foreach ($date as $key => $val) {
1061:             if (in_array($key, array('year', 'month', 'mday', 'hour', 'min', 'sec'))) {
1062:                 $this->{'_'. $key} = (int)$val;
1063:             }
1064:         }
1065: 
1066:         // If $date['day'] is present and numeric we may have been passed
1067:         // a Horde_Form_datetime array.
1068:         if (isset($date['day']) &&
1069:             (string)(int)$date['day'] == $date['day']) {
1070:             $this->_mday = (int)$date['day'];
1071:         }
1072:         // 'minute' key also from Horde_Form_datetime
1073:         if (isset($date['minute']) &&
1074:             (string)(int)$date['minute'] == $date['minute']) {
1075:             $this->_min = (int)$date['minute'];
1076:         }
1077: 
1078:         $this->_correct();
1079:     }
1080: 
1081:     protected function _initializeFromObject($date)
1082:     {
1083:         if ($date instanceof DateTime) {
1084:             $this->_year  = (int)$date->format('Y');
1085:             $this->_month = (int)$date->format('m');
1086:             $this->_mday  = (int)$date->format('d');
1087:             $this->_hour  = (int)$date->format('H');
1088:             $this->_min   = (int)$date->format('i');
1089:             $this->_sec   = (int)$date->format('s');
1090:             $this->_initializeTimezone($date->getTimezone()->getName());
1091:         } else {
1092:             $is_horde_date = $date instanceof Horde_Date;
1093:             foreach (array('year', 'month', 'mday', 'hour', 'min', 'sec') as $key) {
1094:                 if ($is_horde_date || isset($date->$key)) {
1095:                     $this->{'_' . $key} = (int)$date->$key;
1096:                 }
1097:             }
1098:             if (!$is_horde_date) {
1099:                 $this->_correct();
1100:             } else {
1101:                 $this->_initializeTimezone($date->timezone);
1102:             }
1103:         }
1104:     }
1105: 
1106:     protected function _initializeTimezone($timezone)
1107:     {
1108:         if (empty($timezone)) {
1109:             $timezone = date_default_timezone_get();
1110:         }
1111:         $this->_timezone = $timezone;
1112:     }
1113: 
1114: }
1115: 
API documentation generated by ApiGen