Date
[ class tree: Date ] [ index: Date ] [ all elements ]

Source for file Date.php

Documentation is available at Date.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
  3.  
  4. // {{{ Header
  5.  
  6. /**
  7.  * Generic date handling class for PEAR
  8.  *
  9.  * Handles time zones and changes from local standard to local Summer
  10.  * time (daylight-saving time) through the Date_TimeZone class.
  11.  * Supports several operations from Date_Calc on Date objects.
  12.  *
  13.  * PHP versions 4 and 5
  14.  *
  15.  * LICENSE:
  16.  *
  17.  * Copyright (c) 1997-2007 Baba Buehler, Pierre-Alain Joye, Firman
  18.  * Wandayandi, C.A. Woodcock
  19.  * All rights reserved.
  20.  *
  21.  * Redistribution and use in source and binary forms, with or without
  22.  * modification, are permitted under the terms of the BSD License.
  23.  *
  24.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  25.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  26.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  27.  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  28.  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  29.  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  30.  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  31.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  32.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  33.  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  34.  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  35.  * POSSIBILITY OF SUCH DAMAGE.
  36.  *
  37.  * @category   Date and Time
  38.  * @package    Date
  39.  * @author     Baba Buehler <baba@babaz.com>
  40.  * @author     Pierre-Alain Joye <pajoye@php.net>
  41.  * @author     Firman Wandayandi <firman@php.net>
  42.  * @author     C.A. Woodcock <c01234@netcomuk.co.uk>
  43.  * @copyright  1997-2007 Baba Buehler, Pierre-Alain Joye, Firman Wandayandi, C.A. Woodcock
  44.  * @license    http://www.opensource.org/licenses/bsd-license.php
  45.  *              BSD License
  46.  * @version    CVS: $Id: Date.php,v 1.89 2008/03/23 18:34:16 c01234 Exp $
  47.  * @link       http://pear.php.net/package/Date
  48.  */
  49.  
  50.  
  51. // }}}
  52. // {{{ Error constants
  53.  
  54. define('DATE_ERROR_INVALIDDATE'1);
  55. define('DATE_ERROR_INVALIDTIME'2);
  56. define('DATE_ERROR_INVALIDTIMEZONE'3);
  57. define('DATE_ERROR_INVALIDDATEFORMAT'4);
  58. define('DATE_ERROR_INVALIDFORMATSTRING'5);
  59.  
  60.  
  61. // }}}
  62. // {{{ Includes
  63.  
  64. require_once 'PEAR.php';
  65.  
  66. /**
  67.  * Load Date_TimeZone
  68.  */
  69. require_once 'Date/TimeZone.php';
  70.  
  71. /**
  72.  * Load Date_Calc
  73.  */
  74. require_once 'Date/Calc.php';
  75.  
  76. /**
  77.  * Load Date_Span
  78.  */
  79. require_once 'Date/Span.php';
  80.  
  81.  
  82. // }}}
  83. // {{{ General constants
  84.  
  85. /**
  86.  * Whether to capture the micro-time (in microseconds) by default
  87.  * in calls to 'Date::setNow()'.  Note that this makes a call to
  88.  * 'gettimeofday()', which may not work on all systems.
  89.  *
  90.  * @since    Constant available since Release 1.5.0
  91.  */
  92. define('DATE_CAPTURE_MICROTIME_BY_DEFAULT'false);
  93.  
  94. /**
  95.  * Whether to correct, by adding the local Summer time offset, the
  96.  * specified time if it falls in the 'skipped hour' (encountered
  97.  * when the clocks go forward).
  98.  *
  99.  * N.B. if specified as 'false', and if a time zone that adjusts
  100.  * for Summer time is specified, then an object of this class will
  101.  * be set to a semi-invalid state if an invalid time is set.  That
  102.  * is, an error will not be returned, unless the user then calls
  103.  * a function, directly or indirectly, that accesses the time
  104.  * part of the object.  So, for example, if the user calls:
  105.  *
  106.  *  <code>$date_object->format2('HH.MI.SS')</code> or:
  107.  *  <code>$date->object->addSeconds(30)</code>,
  108.  *
  109.  * an error will be returned if the time is invalid.  However,
  110.  * if the user calls:
  111.  *
  112.  *  <code>$date->object->addDays(1)</code>,
  113.  *
  114.  * for example, such that the time is no longer invalid, then the
  115.  * object will no longer be in this invalid state.  This behaviour
  116.  * is intended to minimize unexpected errors when a user uses the
  117.  * class to do addition with days only, and does not intend to
  118.  * access the time.
  119.  *
  120.  * Of course, this constant will be unused if the user chooses to
  121.  * work in UTC or a time zone without Summer time, in which case
  122.  * this situation will never arise.
  123.  *
  124.  * This constant is set to 'true' by default for backwards-compatibility
  125.  * reasons, however, you are recommended to set it to 'false'.  Note that the
  126.  * behaviour is not intended to match that of previous versions of the class
  127.  * in terms of ignoring the Summer time offset when making calculations which
  128.  * involve dates in both standard and Summer time - this was recognized as a
  129.  * bug - but in terms of returning a PEAR error object when the user sets the
  130.  * object to an invalid date (i.e. a time in the hour which is skipped when
  131.  * the clocks go forwards, which in Europe would be a time such as 01.30).
  132.  * Backwards compatibility here means that the behaviour is the same as it
  133.  * used to be, less the bug.
  134.  *
  135.  * Note that this problem is not an issue for the user if:
  136.  *
  137.  *  (a) the user uses a time zone that does not observe Summer time, e.g. UTC
  138.  *  (b) the user never accesses the time, that is, he never makes a call to
  139.  *       Date::getHour() or Date::format("%H"), for example, even if he sets
  140.  *       the time to something invalid
  141.  *  (c) the user sets DATE_CORRECTINVALIDTIME_DEFAULT to true
  142.  *
  143.  * @since    Constant available since Release 1.5.0
  144.  */
  145. define('DATE_CORRECTINVALIDTIME_DEFAULT'true);
  146.  
  147. /**
  148.  * Whether to validate dates (i.e. day-month-year, ignoring the time) by
  149.  * disallowing invalid dates (e.g. 31st February) being set by the following
  150.  * functions:
  151.  *
  152.  *  Date::setYear()
  153.  *  Date::setMonth()
  154.  *  Date::setDay()
  155.  *
  156.  * If the constant is set to 'true', then the date will be checked (by
  157.  * default), and if invalid, an error will be returned with the Date object
  158.  * left unmodified.
  159.  *
  160.  * This constant is set to 'false' by default for backwards-compatibility
  161.  * reasons, however, you are recommended to set it to 'true'.
  162.  *
  163.  * Note that setHour(), setMinute(), setSecond() and setPartSecond()
  164.  * allow an invalid date/time to be set regardless of the value of this
  165.  * constant.
  166.  *
  167.  * @since    Constant available since Release 1.5.0
  168.  */
  169. define('DATE_VALIDATE_DATE_BY_DEFAULT'false);
  170.  
  171. /**
  172.  * Whether, by default, to accept times including leap seconds (i.e. '23.59.60')
  173.  * when setting the date/time, and whether to count leap seconds in the
  174.  * following functions:
  175.  *
  176.  *  Date::addSeconds()
  177.  *  Date::subtractSeconds()
  178.  *  Date_Calc::addSeconds()
  179.  *  Date::round()
  180.  *  Date::roundSeconds()
  181.  *
  182.  * This constant is set to 'false' by default for backwards-compatibility
  183.  * reasons, however, you are recommended to set it to 'true'.
  184.  *
  185.  * Note that this constant does not affect Date::addSpan() and
  186.  * Date::subtractSpan() which will not count leap seconds in any case.
  187.  *
  188.  * @since    Constant available since Release 1.5.0
  189.  */
  190. define('DATE_COUNT_LEAP_SECONDS'false);
  191.  
  192.  
  193. // }}}
  194. // {{{ Output format constants (used in 'Date::getDate()')
  195.  
  196. /**
  197.  * "YYYY-MM-DD HH:MM:SS"
  198.  */
  199. define('DATE_FORMAT_ISO'1);
  200.  
  201. /**
  202.  * "YYYYMMSSTHHMMSS(Z|(+/-)HHMM)?"
  203.  */
  204. define('DATE_FORMAT_ISO_BASIC'2);
  205.  
  206. /**
  207.  * "YYYY-MM-SSTHH:MM:SS(Z|(+/-)HH:MM)?"
  208.  */
  209. define('DATE_FORMAT_ISO_EXTENDED'3);
  210.  
  211. /**
  212.  * "YYYY-MM-SSTHH:MM:SS(.S*)?(Z|(+/-)HH:MM)?"
  213.  */
  214. define('DATE_FORMAT_ISO_EXTENDED_MICROTIME'6);
  215.  
  216. /**
  217.  * "YYYYMMDDHHMMSS"
  218.  */
  219. define('DATE_FORMAT_TIMESTAMP'4);
  220.  
  221. /**
  222.  * long int, seconds since the unix epoch
  223.  */
  224. define('DATE_FORMAT_UNIXTIME'5);
  225.  
  226.  
  227. // }}}
  228. // {{{ Class: Date
  229.  
  230. /**
  231.  * Generic date handling class for PEAR
  232.  *
  233.  * Supports time zones with the Date_TimeZone class.  Supports several
  234.  * operations from Date_Calc on Date objects.
  235.  *
  236.  * Note to developers: the class stores the local time and date in the
  237.  * local standard time.  That is, it does not store the time as the
  238.  * local Summer time when and if the time zone is in Summer time.  It
  239.  * is much easier to store local standard time and remember to offset
  240.  * it when the user requests it.
  241.  *
  242.  * @category  Date and Time
  243.  * @package   Date
  244.  * @author    Baba Buehler <baba@babaz.com>
  245.  * @author    Pierre-Alain Joye <pajoye@php.net>
  246.  * @author    Firman Wandayandi <firman@php.net>
  247.  * @author    C.A. Woodcock <c01234@netcomuk.co.uk>
  248.  * @copyright 1997-2007 Baba Buehler, Pierre-Alain Joye, Firman Wandayandi, C.A. Woodcock
  249.  * @license   http://www.opensource.org/licenses/bsd-license.php
  250.  *             BSD License
  251.  * @version   Release: 1.5.0a1
  252.  * @link      http://pear.php.net/package/Date
  253.  */
  254. class Date
  255. {
  256.  
  257.     // {{{ Properties
  258.  
  259.     /**
  260.      * The year
  261.      *
  262.      * @var      int 
  263.      * @access   private
  264.      * @since    Property available since Release 1.0
  265.      */
  266.     var $year;
  267.  
  268.     /**
  269.      * The month
  270.      *
  271.      * @var      int 
  272.      * @access   private
  273.      * @since    Property available since Release 1.0
  274.      */
  275.     var $month;
  276.  
  277.     /**
  278.      * The day
  279.      *
  280.      * @var      int 
  281.      * @access   private
  282.      * @since    Property available since Release 1.0
  283.      */
  284.     var $day;
  285.  
  286.     /**
  287.      * The hour
  288.      *
  289.      * @var      int 
  290.      * @access   private
  291.      * @since    Property available since Release 1.0
  292.      */
  293.     var $hour;
  294.  
  295.     /**
  296.      * The minute
  297.      *
  298.      * @var      int 
  299.      * @access   private
  300.      * @since    Property available since Release 1.0
  301.      */
  302.     var $minute;
  303.  
  304.     /**
  305.      * The second
  306.      *
  307.      * @var      int 
  308.      * @access   private
  309.      * @since    Property available since Release 1.0
  310.      */
  311.     var $second;
  312.  
  313.     /**
  314.      * The parts of a second
  315.      *
  316.      * @var      float 
  317.      * @access   private
  318.      * @since    Property available since Release 1.4.3
  319.      */
  320.     var $partsecond;
  321.  
  322.     /**
  323.      * The year in local standard time
  324.      *
  325.      * @var      int 
  326.      * @access   private
  327.      * @since    Property available since Release 1.5.0
  328.      */
  329.     var $on_standardyear;
  330.  
  331.     /**
  332.      * The month in local standard time
  333.      *
  334.      * @var      int 
  335.      * @access   private
  336.      * @since    Property available since Release 1.5.0
  337.      */
  338.     var $on_standardmonth;
  339.  
  340.     /**
  341.      * The day in local standard time
  342.      *
  343.      * @var      int 
  344.      * @access   private
  345.      * @since    Property available since Release 1.5.0
  346.      */
  347.     var $on_standardday;
  348.  
  349.     /**
  350.      * The hour in local standard time
  351.      *
  352.      * @var      int 
  353.      * @access   private
  354.      * @since    Property available since Release 1.5.0
  355.      */
  356.     var $on_standardhour;
  357.  
  358.     /**
  359.      * The minute in local standard time
  360.      *
  361.      * @var      int 
  362.      * @access   private
  363.      * @since    Property available since Release 1.5.0
  364.      */
  365.     var $on_standardminute;
  366.  
  367.     /**
  368.      * The second in local standard time
  369.      *
  370.      * @var      int 
  371.      * @access   private
  372.      * @since    Property available since Release 1.5.0
  373.      */
  374.     var $on_standardsecond;
  375.  
  376.     /**
  377.      * The part-second in local standard time
  378.      *
  379.      * @var      float 
  380.      * @access   private
  381.      * @since    Property available since Release 1.5.0
  382.      */
  383.     var $on_standardpartsecond;
  384.  
  385.     /**
  386.      * Whether the object should accept and count leap seconds
  387.      *
  388.      * @var      bool 
  389.      * @access   private
  390.      * @since    Property available since Release 1.5.0
  391.      */
  392.     var $ob_countleapseconds;
  393.  
  394.     /**
  395.      * Whether the time is valid as a local time (an invalid time
  396.      * is one that lies in the 'skipped hour' at the point that
  397.      * the clocks go forward)
  398.      *
  399.      * @var      bool 
  400.      * @access   private
  401.      * @see      Date::isTimeValid()
  402.      * @since    Property available since Release 1.5.0
  403.      */
  404.     var $ob_invalidtime = null;
  405.  
  406.     /**
  407.      * Date_TimeZone object for this date
  408.      *
  409.      * @var      object     Date_TimeZone object
  410.      * @access   private
  411.      * @since    Property available since Release 1.0
  412.      */
  413.     var $tz;
  414.  
  415.     /**
  416.      * Defines the default weekday abbreviation length
  417.      *
  418.      * Formerly used by Date::format(), but now redundant - the abbreviation
  419.      * for the current locale of the machine is used.
  420.      *
  421.      * @var      int 
  422.      * @access   private
  423.      * @since    Property available since Release 1.4.4
  424.      */
  425.     var $getWeekdayAbbrnameLength = 3;
  426.  
  427.  
  428.     // }}}
  429.     // {{{ Constructor
  430.  
  431.     /**
  432.      * Constructor
  433.      *
  434.      * Creates a new Date Object initialized to the current date/time in the
  435.      * system-default timezone by default.  A date optionally
  436.      * passed in may be in the ISO 8601, TIMESTAMP or UNIXTIME format,
  437.      * or another Date object.  If no date is passed, the current date/time
  438.      * is used.
  439.      *
  440.      * If a date is passed and an exception is returned by 'setDate()'
  441.      * there is nothing that this function can do, so for this reason, it
  442.      * is advisable to pass no parameter and to make a separate call to
  443.      * 'setDate()'.  A date/time should only be passed if known to be a
  444.      * valid ISO 8601 string or a valid Unix timestamp.
  445.      *
  446.      * @param mixed $date                optional ISO 8601 date/time to initialize;
  447.      *                                     or, a Unix time stamp
  448.      * @param bool  $pb_countleapseconds whether to count leap seconds
  449.      *                                     (defaults to DATE_COUNT_LEAP_SECONDS)
  450.      *
  451.      * @return   void 
  452.      * @access   public
  453.      * @see      Date::setDate()
  454.      */
  455.     function Date($date = null,
  456.                   $pb_countleapseconds = DATE_COUNT_LEAP_SECONDS)
  457.     {
  458.         $this->ob_countleapseconds $pb_countleapseconds;
  459.  
  460.         if (is_a($date'Date')) {
  461.             $this->copy($date);
  462.         else {
  463.             if (!is_null($date)) {
  464.                 // 'setDate()' expects a time zone to be already set:
  465.                 //
  466.                 $this->_setTZToDefault();
  467.                 $this->setDate($date);
  468.             else {
  469.                 $this->setNow();
  470.             }
  471.         }
  472.     }
  473.  
  474.  
  475.     // }}}
  476.     // {{{ copy()
  477.  
  478.     /**
  479.      * Copy values from another Date object
  480.      *
  481.      * Makes this Date a copy of another Date object.  This is a
  482.      * PHP4-compatible implementation of '__clone()' in PHP5.
  483.      *
  484.      * @param object $date Date object to copy
  485.      *
  486.      * @return   void 
  487.      * @access   public
  488.      */
  489.     function copy($date)
  490.     {
  491.         $this->year       $date->year;
  492.         $this->month      $date->month;
  493.         $this->day        $date->day;
  494.         $this->hour       $date->hour;
  495.         $this->minute     $date->minute;
  496.         $this->second     $date->second;
  497.         $this->partsecond $date->partsecond;
  498.  
  499.         $this->on_standardyear       $date->on_standardyear;
  500.         $this->on_standardmonth      $date->on_standardmonth;
  501.         $this->on_standardday        $date->on_standardday;
  502.         $this->on_standardhour       $date->on_standardhour;
  503.         $this->on_standardminute     $date->on_standardminute;
  504.         $this->on_standardsecond     $date->on_standardsecond;
  505.         $this->on_standardpartsecond $date->on_standardpartsecond;
  506.  
  507.         $this->ob_countleapseconds $date->ob_countleapseconds;
  508.         $this->ob_invalidtime      $date->ob_invalidtime;
  509.  
  510.         $this->tz = new Date_TimeZone($date->getTZID());
  511.  
  512.         $this->getWeekdayAbbrnameLength $date->getWeekdayAbbrnameLength;
  513.     }
  514.  
  515.  
  516.     // }}}
  517.     // {{{ __clone()
  518.  
  519.     /**
  520.      * Copy values from another Date object
  521.      *
  522.      * Makes this Date a copy of another Date object.  For PHP5
  523.      * only.
  524.      *
  525.      * @return   void 
  526.      * @access   public
  527.      * @see      Date::copy()
  528.      */
  529.     function __clone()
  530.     {
  531.         // This line of code would be preferable, but will only
  532.         // compile in PHP5:
  533.         //
  534.         // $this->tz = clone $this->tz;
  535.  
  536.         $this->tz = new Date_TimeZone($this->getTZID());
  537.     }
  538.  
  539.  
  540.     // }}}
  541.     // {{{ setDate()
  542.  
  543.     /**
  544.      * Sets the fields of a Date object based on the input date and format
  545.      *
  546.      * Format parameter should be one of the specified DATE_FORMAT_* constants:
  547.      *
  548.      *  <code>DATE_FORMAT_ISO</code>
  549.      *                              - 'YYYY-MM-DD HH:MI:SS'
  550.      *  <code>DATE_FORMAT_ISO_BASIC</code>
  551.      *                              - 'YYYYMMSSTHHMMSS(Z|(+/-)HHMM)?'
  552.      *  <code>DATE_FORMAT_ISO_EXTENDED</code>
  553.      *                              - 'YYYY-MM-SSTHH:MM:SS(Z|(+/-)HH:MM)?'
  554.      *  <code>DATE_FORMAT_ISO_EXTENDED_MICROTIME</code>
  555.      *                              - 'YYYY-MM-SSTHH:MM:SS(.S*)?(Z|(+/-)HH:MM)?'
  556.      *  <code>DATE_FORMAT_TIMESTAMP</code>
  557.      *                              - 'YYYYMMDDHHMMSS'
  558.      *  <code>DATE_FORMAT_UNIXTIME'</code>
  559.      *                              - long integer of the no of seconds since
  560.      *                                 the Unix Epoch
  561.      *                                 (1st January 1970 00.00.00 GMT)
  562.      *
  563.      * @param string $date                   input date
  564.      * @param int    $format                 optional format constant
  565.      *                                         (DATE_FORMAT_*) of the input date.
  566.      *                                         This parameter is not needed,
  567.      *                                         except to force the setting of the
  568.      *                                         date from a Unix time-stamp
  569.      *                                         (DATE_FORMAT_UNIXTIME).
  570.      * @param bool   $pb_repeatedhourdefault value to return if repeated
  571.      *                                         hour is specified (defaults
  572.      *                                         to false)
  573.      *
  574.      * @return   void 
  575.      * @access   public
  576.      */
  577.     function setDate($date,
  578.                      $format = DATE_FORMAT_ISO,
  579.                      $pb_repeatedhourdefault = false)
  580.     {
  581.  
  582.         if (preg_match('/^([0-9]{4,4})-?(0[1-9]|1[0-2])-?(0[1-9]|[12][0-9]|3[01])' .
  583.                          '([T\s]?([01][0-9]|2[0-3]):?' .             // [hh]
  584.                          '([0-5][0-9]):?([0-5][0-9]|60)(\.\d+)?' .   // [mi]:[ss]
  585.                          '(Z|[+\-][0-9]{2,2}(:?[0-5][0-9])?)?)?$/i'// offset
  586.                          $date$regs&&
  587.             $format != DATE_FORMAT_UNIXTIME
  588.             {
  589.             // DATE_FORMAT_ISO, ISO_BASIC, ISO_EXTENDED, and TIMESTAMP
  590.             // These formats are extremely close to each other.  This regex
  591.             // is very loose and accepts almost any butchered format you could
  592.             // throw at it.  e.g. 2003-10-07 19:45:15 and 2003-10071945:15
  593.             // are the same thing in the eyes of this regex, even though the
  594.             // latter is not a valid ISO 8601 date.
  595.  
  596.             if (!Date_Calc::isValidDate($regs[3]$regs[2]$regs[1])) {
  597.                 return PEAR::raiseError("'" .
  598.                                         Date_Calc::dateFormat($regs[1],
  599.                                                               $regs[2],
  600.                                                               $regs[3],
  601.                                                               "%Y-%m-%d".
  602.                                         "' is invalid calendar date",
  603.                                         DATE_ERROR_INVALIDDATE);
  604.             }
  605.  
  606.             if (isset($regs[9])) {
  607.                 if ($regs[9== "Z"{
  608.                     $this->tz = new Date_TimeZone("UTC");
  609.                 else {
  610.                     $this->tz = new Date_TimeZone("UTC" $regs[9]);
  611.                 }
  612.             }
  613.  
  614.             $this->setLocalTime($regs[3],
  615.                                 $regs[2],
  616.                                 $regs[1],
  617.                                 isset($regs[5]$regs[5: 0,
  618.                                 isset($regs[6]$regs[6: 0,
  619.                                 isset($regs[7]$regs[7: 0,
  620.                                 isset($regs[8]$regs[8: 0.0,
  621.                                 $pb_repeatedhourdefault);
  622.  
  623.         else if (is_numeric($date)) {
  624.             // Unix Time; N.B. Unix Time is defined relative to GMT,
  625.             // so it needs to be adjusted for the current time zone;
  626.             // however we do not know if it is in Summer time until
  627.             // we have converted it from Unix time:
  628.             //
  629.  
  630.             // Get current time zone details:
  631.             //
  632.             $hs_id $this->getTZID();
  633.  
  634.             // Input Unix time as UTC:
  635.             //
  636.             $this->tz = new Date_TimeZone("UTC");
  637.             $this->setDate(gmdate("Y-m-d H:i:s"$date));
  638.  
  639.             // Convert back to correct time zone:
  640.             //
  641.             $this->convertTZByID($hs_id);
  642.         else {
  643.             return PEAR::raiseError("Date not in ISO 8601 format",
  644.                                     DATE_ERROR_INVALIDDATEFORMAT);
  645.         }
  646.     }
  647.  
  648.  
  649.     // }}}
  650.     // {{{ setNow()
  651.  
  652.     /**
  653.      * Sets to local current time and time zone
  654.      *
  655.      * @param bool $pb_setmicrotime whether to set micro-time (defaults to the
  656.      *                                value of the constant
  657.      *                                DATE_CAPTURE_MICROTIME_BY_DEFAULT)
  658.      *
  659.      * @return   void 
  660.      * @access   public
  661.      * @since    Method available since Release 1.5.0
  662.      */
  663.     function setNow($pb_setmicrotime = DATE_CAPTURE_MICROTIME_BY_DEFAULT)
  664.     {
  665.         $this->_setTZToDefault();
  666.  
  667.         if ($pb_setmicrotime{
  668.             $ha_unixtime gettimeofday();
  669.         else {
  670.             $ha_unixtime = array("sec" => time());
  671.         }
  672.  
  673.         $this->setDate(date("Y-m-d H:i:s"$ha_unixtime["sec"].
  674.                        (isset($ha_unixtime["usec"]?
  675.                         "." sprintf("%06d"$ha_unixtime["usec"]:
  676.                         ""));
  677.     }
  678.  
  679.  
  680.     // }}}
  681.     // {{{ round()
  682.  
  683.     /**
  684.      * Rounds the date according to the specified precision (defaults
  685.      * to nearest day)
  686.      *
  687.      * The precision parameter must be one of the following constants:
  688.      *
  689.      *  <code>DATE_PRECISION_YEAR</code>
  690.      *  <code>DATE_PRECISION_MONTH</code>
  691.      *  <code>DATE_PRECISION_DAY</code>
  692.      *  <code>DATE_PRECISION_HOUR</code>
  693.      *  <code>DATE_PRECISION_10MINUTES</code>
  694.      *  <code>DATE_PRECISION_MINUTE</code>
  695.      *  <code>DATE_PRECISION_10SECONDS</code>
  696.      *  <code>DATE_PRECISION_SECOND</code>
  697.      *
  698.      * N.B. the default is DATE_PRECISION_DAY
  699.      *
  700.      * The precision can also be specified as an integral offset from
  701.      * one of these constants, where the offset reflects a precision
  702.      * of 10 to the power of the offset greater than the constant.
  703.      * For example:
  704.      *
  705.      *  <code>DATE_PRECISION_YEAR - 1</code> rounds the date to the nearest 10
  706.      *                                      years
  707.      *  <code>DATE_PRECISION_YEAR - 3</code> rounds the date to the nearest 1000
  708.      *                                      years
  709.      *  <code>DATE_PRECISION_SECOND + 1</code> rounds the date to 1 decimal
  710.      *                                        point of a second
  711.      *  <code>DATE_PRECISION_SECOND + 3</code> rounds the date to 3 decimal
  712.      *                                        points of a second
  713.      *  <code>DATE_PRECISION_SECOND - 1</code> rounds the date to the nearest 10
  714.      *                                        seconds (thus it is equivalent to
  715.      *                                        DATE_PRECISION_10SECONDS)
  716.      *
  717.      * @param int  $pn_precision          a 'DATE_PRECISION_*' constant
  718.      * @param bool $pb_correctinvalidtime whether to correct, by adding the
  719.      *                                      local Summer time offset, the rounded
  720.      *                                      time if it falls in the skipped hour
  721.      *                                      (defaults to
  722.      *                                      DATE_CORRECTINVALIDTIME_DEFAULT)
  723.      *
  724.      * @return   void 
  725.      * @access   public
  726.      * @since    Method available since Release 1.5.0
  727.      */
  728.     function round($pn_precision = DATE_PRECISION_DAY,
  729.                    $pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT)
  730.     {
  731.         if ($pn_precision <= DATE_PRECISION_DAY{
  732.             list($hn_year,
  733.                  $hn_month,
  734.                  $hn_day,
  735.                  $hn_hour,
  736.                  $hn_minute,
  737.                  $hn_secondraw=
  738.                  Date_Calc::round($pn_precision,
  739.                                   $this->day,
  740.                                   $this->month,
  741.                                   $this->year,
  742.                                   $this->hour,
  743.                                   $this->minute,
  744.                                   $this->partsecond == 0.0 ?
  745.                                       $this->second :
  746.                                       $this->second $this->partsecond,
  747.                                   $this->ob_countleapseconds);
  748.             if (is_float($hn_secondraw)) {
  749.                 $hn_second     intval($hn_secondraw);
  750.                 $hn_partsecond $hn_secondraw $hn_second;
  751.             else {
  752.                 $hn_second     $hn_secondraw;
  753.                 $hn_partsecond = 0.0;
  754.             }
  755.  
  756.             $this->setLocalTime($hn_day,
  757.                                 $hn_month,
  758.                                 $hn_year,
  759.                                 $hn_hour,
  760.                                 $hn_minute,
  761.                                 $hn_second,
  762.                                 $hn_partsecond,
  763.                                 true// This is unlikely anyway, but the
  764.                                       // day starts with the repeated hour
  765.                                       // the first time around
  766.                                 $pb_correctinvalidtime);
  767.             return;
  768.         }
  769.  
  770.         // ($pn_precision >= DATE_PRECISION_HOUR)
  771.         //
  772.         if ($this->tz->getDSTSavings(% 3600000 == 0 ||
  773.             ($this->tz->getDSTSavings(% 60000 == 0 &&
  774.              $pn_precision >= DATE_PRECISION_MINUTE)
  775.             {
  776.             list($hn_year,
  777.                  $hn_month,
  778.                  $hn_day,
  779.                  $hn_hour,
  780.                  $hn_minute,
  781.                  $hn_secondraw=
  782.                  Date_Calc::round($pn_precision,
  783.                                   $this->on_standardday,
  784.                                   $this->on_standardmonth,
  785.                                   $this->on_standardyear,
  786.                                   $this->on_standardhour,
  787.                                   $this->on_standardminute,
  788.                                   $this->on_standardpartsecond == 0.0 ?
  789.                                       $this->on_standardsecond :
  790.                                       $this->on_standardsecond +
  791.                                           $this->on_standardpartsecond,
  792.                                   $this->ob_countleapseconds);
  793.             if (is_float($hn_secondraw)) {
  794.                 $hn_second     intval($hn_secondraw);
  795.                 $hn_partsecond $hn_secondraw $hn_second;
  796.             else {
  797.                 $hn_second     $hn_secondraw;
  798.                 $hn_partsecond = 0.0;
  799.             }
  800.  
  801.             $this->setStandardTime($hn_day,
  802.                                    $hn_month,
  803.                                    $hn_year,
  804.                                    $hn_hour,
  805.                                    $hn_minute,
  806.                                    $hn_second,
  807.                                    $hn_partsecond);
  808.             return;
  809.         }
  810.  
  811.         // Very unlikely anyway (as I write, the only time zone like this
  812.         // is Lord Howe Island in Australia (offset of half an hour)):
  813.         //
  814.         // (This algorithm could be better)
  815.         //
  816.         list($hn_year,
  817.              $hn_month,
  818.              $hn_day,
  819.              $hn_hour,
  820.              $hn_minute,
  821.              $hn_secondraw=
  822.              Date_Calc::round($pn_precision,
  823.                               $this->day,
  824.                               $this->month,
  825.                               $this->year,
  826.                               $this->hour,
  827.                               $this->minute,
  828.                               $this->partsecond == 0.0 ?
  829.                                   $this->second :
  830.                                   $this->second $this->partsecond,
  831.                               $this->ob_countleapseconds);
  832.         if (is_float($hn_secondraw)) {
  833.             $hn_second     intval($hn_secondraw);
  834.             $hn_partsecond $hn_secondraw $hn_second;
  835.         else {
  836.             $hn_second     $hn_secondraw;
  837.             $hn_partsecond = 0.0;
  838.         }
  839.  
  840.         $this->setLocalTime($hn_day,
  841.                             $hn_month,
  842.                             $hn_year,
  843.                             $hn_hour,
  844.                             $hn_minute,
  845.                             $hn_second,
  846.                             $hn_partsecond,
  847.                             false// This will be right half the time
  848.                             $pb_correctinvalidtime);   // This will be right
  849.                                                        // some of the time
  850.                                                        // (depends on Summer
  851.                                                        // time offset)
  852.     }
  853.  
  854.  
  855.     // }}}
  856.     // {{{ roundSeconds()
  857.  
  858.     /**
  859.      * Rounds seconds up or down to the nearest specified unit
  860.      *
  861.      * N.B. this function is equivalent to calling:
  862.      *  <code>'round(DATE_PRECISION_SECOND + $pn_precision)'</code>
  863.      *
  864.      * @param int  $pn_precision          number of digits after the decimal point
  865.      * @param bool $pb_correctinvalidtime whether to correct, by adding the
  866.      *                                      local Summer time offset, the rounded
  867.      *                                      time if it falls in the skipped hour
  868.      *                                      (defaults to
  869.      *                                      DATE_CORRECTINVALIDTIME_DEFAULT)
  870.      *
  871.      * @return   void 
  872.      * @access   public
  873.      * @since    Method available since Release 1.5.0
  874.      */
  875.     function roundSeconds($pn_precision = 0,
  876.                           $pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT)
  877.     {
  878.         $this->round(DATE_PRECISION_SECOND + $pn_precision,
  879.                      $pb_correctinvalidtime);
  880.     }
  881.  
  882.  
  883.     // }}}
  884.     // {{{ trunc()
  885.  
  886.     /**
  887.      * Truncates the date according to the specified precision (by
  888.      * default, it truncates the time part of the date)
  889.      *
  890.      * The precision parameter must be one of the following constants:
  891.      *
  892.      *  <code>DATE_PRECISION_YEAR</code>
  893.      *  <code>DATE_PRECISION_MONTH</code>
  894.      *  <code>DATE_PRECISION_DAY</code>
  895.      *  <code>DATE_PRECISION_HOUR</code>
  896.      *  <code>DATE_PRECISION_10MINUTES</code>
  897.      *  <code>DATE_PRECISION_MINUTE</code>
  898.      *  <code>DATE_PRECISION_10SECONDS</code>
  899.      *  <code>DATE_PRECISION_SECOND</code>
  900.      *
  901.      * N.B. the default is DATE_PRECISION_DAY
  902.      *
  903.      * The precision can also be specified as an integral offset from
  904.      * one of these constants, where the offset reflects a precision
  905.      * of 10 to the power of the offset greater than the constant.
  906.      * For example:
  907.      *
  908.      *  <code>DATE_PRECISION_YEAR</code> truncates the month, day and time
  909.      *                                  part of the year
  910.      *  <code>DATE_PRECISION_YEAR - 1</code> truncates the unit part of the
  911.      *                                      year, e.g. 1987 becomes 1980
  912.      *  <code>DATE_PRECISION_YEAR - 3</code> truncates the hundreds part of the
  913.      *                                      year, e.g. 1987 becomes 1000
  914.      *  <code>DATE_PRECISION_SECOND + 1</code> truncates the part of the second
  915.      *                                        less than 0.1 of a second, e.g.
  916.      *                                        3.26301 becomes 3.2 seconds
  917.      *  <code>DATE_PRECISION_SECOND + 3</code> truncates the part of the second
  918.      *                                        less than 0.001 of a second, e.g.
  919.      *                                        3.26301 becomes 3.263 seconds
  920.      *  <code>DATE_PRECISION_SECOND - 1</code> truncates the unit part of the
  921.      *                                        seconds (thus it is equivalent to
  922.      *                                        DATE_PRECISION_10SECONDS)
  923.      *
  924.      * @param int  $pn_precision          a 'DATE_PRECISION_*' constant
  925.      * @param bool $pb_correctinvalidtime whether to correct, by adding the
  926.      *                                      local Summer time offset, the
  927.      *                                      truncated time if it falls in the
  928.      *                                      skipped hour (defaults to
  929.      *                                      DATE_CORRECTINVALIDTIME_DEFAULT)
  930.      *
  931.      * @return   void 
  932.      * @access   public
  933.      * @since    Method available since Release 1.5.0
  934.      */
  935.     function trunc($pn_precision = DATE_PRECISION_DAY,
  936.                    $pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT)
  937.     {
  938.         if ($pn_precision <= DATE_PRECISION_DAY{
  939.             if ($pn_precision <= DATE_PRECISION_YEAR{
  940.                 $hn_month      = 0;
  941.                 $hn_day        = 0;
  942.                 $hn_hour       = 0;
  943.                 $hn_minute     = 0;
  944.                 $hn_second     = 0;
  945.                 $hn_partsecond = 0.0;
  946.  
  947.                 $hn_invprecision DATE_PRECISION_YEAR - $pn_precision;
  948.                 if ($hn_invprecision > 0{
  949.                     $hn_year intval($this->year pow(10$hn_invprecision)) *
  950.                                pow(10$hn_invprecision);
  951.                     //
  952.                     // (Conversion to int necessary for PHP <= 4.0.6)
  953.                 else {
  954.                     $hn_year $this->year;
  955.                 }
  956.             else if ($pn_precision == DATE_PRECISION_MONTH{
  957.                 $hn_year       $this->year;
  958.                 $hn_month      $this->month;
  959.                 $hn_day        = 0;
  960.                 $hn_hour       = 0;
  961.                 $hn_minute     = 0;
  962.                 $hn_second     = 0;
  963.                 $hn_partsecond = 0.0;
  964.             else if ($pn_precision == DATE_PRECISION_DAY{
  965.                 $hn_year       $this->year;
  966.                 $hn_month      $this->month;
  967.                 $hn_day        $this->day;
  968.                 $hn_hour       = 0;
  969.                 $hn_minute     = 0;
  970.                 $hn_second     = 0;
  971.                 $hn_partsecond = 0.0;
  972.             }
  973.  
  974.             $this->setLocalTime($hn_day,
  975.                                 $hn_month,
  976.                                 $hn_year,
  977.                                 $hn_hour,
  978.                                 $hn_minute,
  979.                                 $hn_second,
  980.                                 $hn_partsecond,
  981.                                 true// This is unlikely anyway, but the
  982.                                       // day starts with the repeated
  983.                                       // hour the first time around
  984.                                 $pb_correctinvalidtime);
  985.             return;
  986.         }
  987.  
  988.         // Precision is at least equal to DATE_PRECISION_HOUR
  989.         //
  990.         if ($pn_precision == DATE_PRECISION_HOUR{
  991.             $this->addSeconds($this->partsecond == 0.0 ?
  992.                               -$this->second :
  993.                               -$this->second $this->partsecond);
  994.             //
  995.             // (leap seconds irrelevant)
  996.  
  997.             $this->addMinutes(-$this->minute);
  998.         else if ($pn_precision <= DATE_PRECISION_MINUTE{
  999.             if ($pn_precision == DATE_PRECISION_10MINUTES{
  1000.                 $this->addMinutes(-$this->minute % 10);
  1001.             }
  1002.  
  1003.             $this->addSeconds($this->partsecond == 0.0 ?
  1004.                               -$this->second :
  1005.                               -$this->second $this->partsecond);
  1006.             //
  1007.             // (leap seconds irrelevant)
  1008.  
  1009.         else if ($pn_precision == DATE_PRECISION_10SECONDS{
  1010.             $this->addSeconds($this->partsecond == 0.0 ?
  1011.                               -$this->second % 10 :
  1012.                               (-$this->second % 10$this->partsecond);
  1013.             //
  1014.             // (leap seconds irrelevant)
  1015.  
  1016.         else {
  1017.             // Assume Summer time offset cannot be composed of part-seconds:
  1018.             //
  1019.             $hn_precision  $pn_precision DATE_PRECISION_SECOND;
  1020.             $hn_partsecond intval($this->on_standardpartsecond *
  1021.                                     pow(10$hn_precision)) /
  1022.                                     pow(10$hn_precision);
  1023.             $this->setStandardTime($this->on_standardday,
  1024.                                    $this->on_standardmonth,
  1025.                                    $this->on_standardyear,
  1026.                                    $this->on_standardhour,
  1027.                                    $this->on_standardminute,
  1028.                                    $this->on_standardsecond,
  1029.                                    $hn_partsecond);
  1030.         }
  1031.     }
  1032.  
  1033.  
  1034.     // }}}
  1035.     // {{{ truncSeconds()
  1036.  
  1037.     /**
  1038.      * Truncates seconds according to the specified precision
  1039.      *
  1040.      * N.B. this function is equivalent to calling:
  1041.      *  <code>'Date::trunc(DATE_PRECISION_SECOND + $pn_precision)'</code>
  1042.      *
  1043.      * @param int  $pn_precision          number of digits after the decimal point
  1044.      * @param bool $pb_correctinvalidtime whether to correct, by adding the
  1045.      *                                      local Summer time offset, the
  1046.      *                                      truncated time if it falls in the
  1047.      *                                      skipped hour (defaults to
  1048.      *                                      DATE_CORRECTINVALIDTIME_DEFAULT)
  1049.      *
  1050.      * @return   void 
  1051.      * @access   public
  1052.      * @since    Method available since Release 1.5.0
  1053.      */
  1054.     function truncSeconds($pn_precision = 0,
  1055.                           $pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT)
  1056.     {
  1057.         $this->trunc(DATE_PRECISION_SECOND + $pn_precision,
  1058.                      $pb_correctinvalidtime);
  1059.     }
  1060.  
  1061.  
  1062.     // }}}
  1063.     // {{{ getDate()
  1064.  
  1065.     /**
  1066.      * Gets a string (or other) representation of this date
  1067.      *
  1068.      * Returns a date in the format specified by the DATE_FORMAT_* constants.
  1069.      *
  1070.      * @param int $format format constant (DATE_FORMAT_*) of the output date
  1071.      *
  1072.      * @return   string     the date in the requested format
  1073.      * @access   public
  1074.      */
  1075.     function getDate($format = DATE_FORMAT_ISO)
  1076.     {
  1077.         switch ($format{
  1078.         case DATE_FORMAT_ISO:
  1079.             return $this->format("%Y-%m-%d %T");
  1080.             break;
  1081.         case DATE_FORMAT_ISO_BASIC:
  1082.             $format "%Y%m%dT%H%M%S";
  1083.             if ($this->getTZID(== 'UTC'{
  1084.                 $format .= "Z";
  1085.             }
  1086.             return $this->format($format);
  1087.             break;
  1088.         case DATE_FORMAT_ISO_EXTENDED:
  1089.             $format "%Y-%m-%dT%H:%M:%S";
  1090.             if ($this->getTZID(== 'UTC'{
  1091.                 $format .= "Z";
  1092.             }
  1093.             return $this->format($format);
  1094.             break;
  1095.         case DATE_FORMAT_ISO_EXTENDED_MICROTIME:
  1096.             $format "%Y-%m-%dT%H:%M:%s";
  1097.             if ($this->getTZID(== 'UTC'{
  1098.                 $format .= "Z";
  1099.             }
  1100.             return $this->format($format);
  1101.             break;
  1102.         case DATE_FORMAT_TIMESTAMP:
  1103.             return $this->format("%Y%m%d%H%M%S");
  1104.             break;
  1105.         case DATE_FORMAT_UNIXTIME:
  1106.             // Enter a time in UTC, so use 'gmmktime()' (the alternative
  1107.             // is to offset additionally by the local time, but the object
  1108.             // is not necessarily using local time):
  1109.             //
  1110.             if ($this->ob_invalidtime)
  1111.                 return $this->_getErrorInvalidTime();
  1112.  
  1113.             return gmmktime($this->on_standardhour,
  1114.                             $this->on_standardminute,
  1115.                             $this->on_standardsecond,
  1116.                             $this->on_standardmonth,
  1117.                             $this->on_standardday,
  1118.                             $this->on_standardyear-
  1119.                    $this->tz->getRawOffset(/ 1000; // N.B. Unix-time excludes
  1120.                                                      // leap seconds by
  1121.                                                      // definition
  1122.             break;
  1123.         }
  1124.     }
  1125.  
  1126.  
  1127.     // }}}
  1128.     // {{{ format()
  1129.  
  1130.     /**
  1131.      *  Date pretty printing, similar to strftime()
  1132.      *
  1133.      *  Formats the date in the given format, much like
  1134.      *  strftime().  Most strftime() options are supported.<br><br>
  1135.      *
  1136.      *  Formatting options:<br><br>
  1137.      *
  1138.      *  <code>%a  </code>  abbreviated weekday name (Sun, Mon, Tue) <br>
  1139.      *  <code>%A  </code>  full weekday name (Sunday, Monday, Tuesday) <br>
  1140.      *  <code>%b  </code>  abbreviated month name (Jan, Feb, Mar) <br>
  1141.      *  <code>%B  </code>  full month name (January, February, March) <br>
  1142.      *  <code>%C  </code>  century number (the year divided by 100 and truncated
  1143.      *                     to an integer, range 00 to 99) <br>
  1144.      *  <code>%d  </code>  day of month (range 00 to 31) <br>
  1145.      *  <code>%D  </code>  equivalent to "%m/%d/%y" <br>
  1146.      *  <code>%e  </code>  day of month without leading noughts (range 0 to 31) <br>
  1147.      *  <code>%E  </code>  Julian day - no of days since Monday, 24th November,
  1148.      *                     4714 B.C. (in the proleptic Gregorian calendar) <br>
  1149.      *  <code>%g  </code>  like %G, but without the century <br>
  1150.      *  <code>%G  </code>  the 4-digit year corresponding to the ISO week
  1151.      *                     number (see %V). This has the same format and value
  1152.      *                     as %Y, except that if the ISO week number belongs
  1153.      *                     to the previous or next year, that year is used
  1154.      *                     instead. <br>
  1155.      *  <code>%h  </code>  hour as decimal number without leading noughts (0
  1156.      *                     to 23) <br>
  1157.      *  <code>%H  </code>  hour as decimal number (00 to 23) <br>
  1158.      *  <code>%i  </code>  hour as decimal number on 12-hour clock without
  1159.      *                     leading noughts (1 to 12) <br>
  1160.      *  <code>%I  </code>  hour as decimal number on 12-hour clock (01 to 12) <br>
  1161.      *  <code>%j  </code>  day of year (range 001 to 366) <br>
  1162.      *  <code>%m  </code>  month as decimal number (range 01 to 12) <br>
  1163.      *  <code>%M  </code>  minute as a decimal number (00 to 59) <br>
  1164.      *  <code>%n  </code>  newline character ("\n") <br>
  1165.      *  <code>%o  </code>  raw timezone offset expressed as '+/-HH:MM' <br>
  1166.      *  <code>%O  </code>  dst-corrected timezone offset expressed as '+/-HH:MM' <br>
  1167.      *  <code>%p  </code>  either 'am' or 'pm' depending on the time <br>
  1168.      *  <code>%P  </code>  either 'AM' or 'PM' depending on the time <br>
  1169.      *  <code>%r  </code>  time in am/pm notation; equivalent to "%I:%M:%S %p" <br>
  1170.      *  <code>%R  </code>  time in 24-hour notation; equivalent to "%H:%M" <br>
  1171.      *  <code>%s  </code>  seconds including the micro-time (the decimal
  1172.      *                     representation less than one second to six decimal
  1173.      *                     places<br>
  1174.      *  <code>%S  </code>  seconds as a decimal number (00 to 59) <br>
  1175.      *  <code>%t  </code>  tab character ("\t") <br>
  1176.      *  <code>%T  </code>  current time; equivalent to "%H:%M:%S" <br>
  1177.      *  <code>%u  </code>  day of week as decimal (1 to 7; where 1 = Monday) <br>
  1178.      *  <code>%U  </code>  week number of the current year as a decimal
  1179.      *                     number, starting with the first Sunday as the first
  1180.      *                     day of the first week (i.e. the first full week of
  1181.      *                     the year, and the week that contains 7th January)
  1182.      *                     (00 to 53) <br>
  1183.      *  <code>%V  </code>  the ISO 8601:1988 week number of the current year
  1184.      *                     as a decimal number, range 01 to 53, where week 1
  1185.      *                     is the first week that has at least 4 days in the
  1186.      *                     current year, and with Monday as the first day of
  1187.      *                     the week.  (Use %G or %g for the year component
  1188.      *                     that corresponds to the week number for the
  1189.      *                     specified timestamp.)
  1190.      *  <code>%w  </code>  day of week as decimal (0 to 6; where 0 = Sunday) <br>
  1191.      *  <code>%W  </code>  week number of the current year as a decimal
  1192.      *                     number, starting with the first Monday as the first
  1193.      *                     day of the first week (i.e. the first full week of
  1194.      *                     the year, and the week that contains 7th January)
  1195.      *                     (00 to 53) <br>
  1196.      *  <code>%y  </code>  year as decimal (range 00 to 99) <br>
  1197.      *  <code>%Y  </code>  year as decimal including century (range 0000 to
  1198.      *                     9999) <br>
  1199.      *  <code>%Z  </code>  Abbreviated form of time zone name, e.g. 'GMT', or
  1200.      *                     the abbreviation for Summer time if the date falls
  1201.      *                     in Summer time, e.g. 'BST'. <br>
  1202.      *  <code>%%  </code>  literal '%' <br>
  1203.      * <br>
  1204.      *
  1205.      * The following codes render a different output to that of 'strftime()':
  1206.      *
  1207.      *  <code>%e</code> in 'strftime()' a single digit is preceded by a space
  1208.      *  <code>%h</code> in 'strftime()' is equivalent to '%b'
  1209.      *  <code>%U</code> '%U' and '%W' are different in 'strftime()' in that
  1210.      *                  if week 1 does not start on 1st January, '00' is
  1211.      *                  returned, whereas this function returns '53', that is,
  1212.      *                  the week is counted as the last of the previous year.
  1213.      *  <code>%W</code>
  1214.      *
  1215.      * @param string $format the format string for returned date/time
  1216.      *
  1217.      * @return   string     date/time in given format
  1218.      * @access   public
  1219.      */
  1220.     function format($format)
  1221.     {
  1222.         $output "";
  1223.  
  1224.         $hn_isoyear = null;
  1225.         $hn_isoweek = null;
  1226.         $hn_isoday  = null;
  1227.  
  1228.         for ($strpos = 0; $strpos strlen($format)$strpos++{
  1229.             $char substr($format$strpos1);
  1230.             if ($char == "%"{
  1231.                 $nextchar substr($format$strpos + 11);
  1232.                 switch ($nextchar{
  1233.                 case "a":
  1234.                     $output .= Date_Calc::getWeekdayAbbrname($this->day,
  1235.                                    $this->month$this->year,
  1236.                                    $this->getWeekdayAbbrnameLength);
  1237.                     break;
  1238.                 case "A":
  1239.                     $output .= Date_Calc::getWeekdayFullname($this->day,
  1240.                                    $this->month$this->year);
  1241.                     break;
  1242.                 case "b":
  1243.                     $output .= Date_Calc::getMonthAbbrname($this->month);
  1244.                     break;
  1245.                 case "B":
  1246.                     $output .= Date_Calc::getMonthFullname($this->month);
  1247.                     break;
  1248.                 case "C":
  1249.                     $output .= sprintf("%02d"intval($this->year / 100));
  1250.                     break;
  1251.                 case "d":
  1252.                     $output .= sprintf("%02d"$this->day);
  1253.                     break;
  1254.                 case "D":
  1255.                     $output .= sprintf("%02d/%02d/%02d"$this->month,
  1256.                                    $this->day$this->year);
  1257.                     break;
  1258.                 case "e":
  1259.                     $output .= $this->day;
  1260.                     break;
  1261.                 case "E":
  1262.                     $output .= Date_Calc::dateToDays($this->day$this->month,
  1263.                                    $this->year);
  1264.                     break;
  1265.                 case "g":
  1266.                     if (is_null($hn_isoyear))
  1267.                         list($hn_isoyear$hn_isoweek$hn_isoday=
  1268.                             Date_Calc::isoWeekDate($this->day,
  1269.                                                    $this->month,
  1270.                                                    $this->year);
  1271.  
  1272.                     $output .= sprintf("%02d"$hn_isoyear % 100);
  1273.                     break;
  1274.                 case "G":
  1275.                     if (is_null($hn_isoyear))
  1276.                         list($hn_isoyear$hn_isoweek$hn_isoday=
  1277.                             Date_Calc::isoWeekDate($this->day,
  1278.                                                    $this->month,
  1279.                                                    $this->year);
  1280.  
  1281.                     $output .= sprintf("%04d"$hn_isoyear);
  1282.                     break;
  1283.                 case 'h':
  1284.                     if ($this->ob_invalidtime)
  1285.                         return $this->_getErrorInvalidTime();
  1286.                     $output .= sprintf("%d"$this->hour);
  1287.                     break;
  1288.                 case "H":
  1289.                     if ($this->ob_invalidtime)
  1290.                         return $this->_getErrorInvalidTime();
  1291.                     $output .= sprintf("%02d"$this->hour);
  1292.                     break;
  1293.                 case "i":
  1294.                 case "I":
  1295.                     if ($this->ob_invalidtime)
  1296.                         return $this->_getErrorInvalidTime();
  1297.                     $hour    $this->hour + 1 > 12 ?
  1298.                                $this->hour - 12 :
  1299.                                $this->hour;
  1300.                     $output .= $hour == 0 ?
  1301.                                12 :
  1302.                                ($nextchar == "i" ?
  1303.                                 $hour :
  1304.                                 sprintf('%02d'$hour));
  1305.                     break;
  1306.                 case "j":
  1307.                     $output .= sprintf("%03d",
  1308.                                        Date_Calc::dayOfYear($this->day,
  1309.                                                             $this->month,
  1310.                                                             $this->year));
  1311.                     break;
  1312.                 case "m":
  1313.                     $output .= sprintf("%02d"$this->month);
  1314.                     break;
  1315.                 case "M":
  1316.                     $output .= sprintf("%02d"$this->minute);
  1317.                     break;
  1318.                 case "n":
  1319.                     $output .= "\n";
  1320.                     break;
  1321.                 case "O":
  1322.                     if ($this->ob_invalidtime)
  1323.                         return $this->_getErrorInvalidTime();
  1324.                     $offms     $this->getTZOffset();
  1325.                     $direction $offms >= 0 ? "+" "-";
  1326.                     $offmins   abs($offms/ 1000 / 60;
  1327.                     $hours     $offmins / 60;
  1328.                     $minutes   $offmins % 60;
  1329.  
  1330.                     $output .= sprintf("%s%02d:%02d"$direction$hours$minutes);
  1331.                     break;
  1332.                 case "o":
  1333.                     $offms     $this->tz->getRawOffset($this);
  1334.                     $direction $offms >= 0 ? "+" "-";
  1335.                     $offmins   abs($offms/ 1000 / 60;
  1336.                     $hours     $offmins / 60;
  1337.                     $minutes   $offmins % 60;
  1338.  
  1339.                     $output .= sprintf("%s%02d:%02d"$direction$hours$minutes);
  1340.                     break;
  1341.                 case "p":
  1342.                     if ($this->ob_invalidtime)
  1343.                         return $this->_getErrorInvalidTime();
  1344.                     $output .= $this->hour >= 12 ? "pm" "am";
  1345.                     break;
  1346.                 case "P":
  1347.                     if ($this->ob_invalidtime)
  1348.                         return $this->_getErrorInvalidTime();
  1349.                     $output .= $this->hour >= 12 ? "PM" "AM";
  1350.                     break;
  1351.                 case "r":
  1352.                     if ($this->ob_invalidtime)
  1353.                         return $this->_getErrorInvalidTime();
  1354.                     $hour    $this->hour + 1 > 12 ?
  1355.                                $this->hour - 12 :
  1356.                                $this->hour;
  1357.                     $output .= sprintf("%02d:%02d:%02d %s",
  1358.                                        $hour == 0 ?  12 : $hour,
  1359.                                        $this->minute,
  1360.                                        $this->second,
  1361.                                        $this->hour >= 12 ? "PM" "AM");
  1362.                     break;
  1363.                 case "R":
  1364.                     if ($this->ob_invalidtime)
  1365.                         return $this->_getErrorInvalidTime();
  1366.                     $output .= sprintf("%02d:%02d"$this->hour$this->minute);
  1367.                     break;
  1368.                 case "s":
  1369.                     $output .= str_replace(',',
  1370.                                            '.',
  1371.                                            sprintf("%09f",
  1372.                                                    (float)((float) $this->second +
  1373.                                                            $this->partsecond)));
  1374.                     break;
  1375.                 case "S":
  1376.                     $output .= sprintf("%02d"$this->second);
  1377.                     break;
  1378.                 case "t":
  1379.                     $output .= "\t";
  1380.                     break;
  1381.                 case "T":
  1382.                     if ($this->ob_invalidtime)
  1383.                         return $this->_getErrorInvalidTime();
  1384.                     $output .= sprintf("%02d:%02d:%02d",
  1385.                                        $this->hour,
  1386.                                        $this->minute,
  1387.                                        $this->second);
  1388.                     break;
  1389.                 case "u":
  1390.                     $hn_dayofweek $this->getDayOfWeek();
  1391.                     $output      .= $hn_dayofweek == 0 ? 7 : $hn_dayofweek;
  1392.                     break;
  1393.                 case "U":
  1394.                     $ha_week Date_Calc::weekOfYear7th($this->day,
  1395.                                                         $this->month,
  1396.                                                         $this->year,
  1397.                                                         0);
  1398.                     $output .= sprintf("%02d"$ha_week[1]);
  1399.                     break;
  1400.                 case "V":
  1401.                     if (is_null($hn_isoyear))
  1402.                         list($hn_isoyear$hn_isoweek$hn_isoday=
  1403.                             Date_Calc::isoWeekDate($this->day,
  1404.                                                    $this->month,
  1405.                                                    $this->year);
  1406.  
  1407.                     $output .= $hn_isoweek;
  1408.                     break;
  1409.                 case "w":
  1410.                     $output .= $this->getDayOfWeek();
  1411.                     break;
  1412.                 case "W":
  1413.                     $ha_week Date_Calc::weekOfYear7th($this->day,
  1414.                                                         $this->month,
  1415.                                                         $this->year,
  1416.                                                         1);
  1417.                     $output .= sprintf("%02d"$ha_week[1]);
  1418.                     break;
  1419.                 case 'y':
  1420.                     $output .= sprintf('%0' .
  1421.                                        ($this->year < 0 ? '3' '2'.
  1422.                                        'd',
  1423.                                        $this->year % 100);
  1424.                     break;
  1425.                 case "Y":
  1426.                     $output .= sprintf('%0' .
  1427.                                        ($this->year < 0 ? '5' '4'.
  1428.                                        'd',
  1429.                                        $this->year);
  1430.                     break;
  1431.                 case "Z":
  1432.                     if ($this->ob_invalidtime)
  1433.                         return $this->_getErrorInvalidTime();
  1434.                     $output .= $this->getTZShortName();
  1435.                     break;
  1436.                 case "%":
  1437.                     $output .= "%";
  1438.                     break;
  1439.                 default:
  1440.                     $output .= $char.$nextchar;
  1441.                 }
  1442.                 $strpos++;
  1443.             else {
  1444.                 $output .= $char;
  1445.             }
  1446.         }
  1447.         return $output;
  1448.  
  1449.     }
  1450.  
  1451.  
  1452.     // }}}
  1453.     // {{{ _getOrdinalSuffix()
  1454.  
  1455.     /**
  1456.      * Returns appropriate ordinal suffix (i.e. 'th', 'st', 'nd' or 'rd')
  1457.      *
  1458.      * @param int  $pn_num       number with which to determine suffix
  1459.      * @param bool $pb_uppercase boolean specifying if the suffix should be
  1460.      *                             capitalized
  1461.      *
  1462.      * @return   string 
  1463.      * @access   private
  1464.      * @since    Method available since Release 1.5.0
  1465.      */
  1466.     function _getOrdinalSuffix($pn_num$pb_uppercase = true)
  1467.     {
  1468.         switch (($pn_numabs abs($pn_num)) % 100{
  1469.         case 11:
  1470.         case 12:
  1471.         case 13:
  1472.             $hs_suffix "th";
  1473.             break;
  1474.         default:
  1475.             switch ($pn_numabs % 10{
  1476.             case 1:
  1477.                 $hs_suffix "st";
  1478.                 break;
  1479.             case 2:
  1480.                 $hs_suffix "nd";
  1481.                 break;
  1482.             case 3:
  1483.                 $hs_suffix "rd";
  1484.                 break;
  1485.             default:
  1486.                 $hs_suffix "th";
  1487.             }
  1488.         }
  1489.  
  1490.         return $pb_uppercase strtoupper($hs_suffix$hs_suffix;
  1491.     }
  1492.  
  1493.  
  1494.     // }}}
  1495.     // {{{ _spellNumber()
  1496.  
  1497.     /**
  1498.      * Converts a number to its word representation
  1499.      *
  1500.      * Private helper function, particularly for 'format2()'.  N.B. The
  1501.      * second argument is the 'SP' code which can be specified in the
  1502.      * format string for 'format2()' and is interpreted as follows:
  1503.      *  'SP' - returns upper-case spelling, e.g. 'FOUR HUNDRED'
  1504.      *  'Sp' - returns spelling with first character of each word
  1505.      *         capitalized, e.g. 'Four Hundred'
  1506.      *  'sp' - returns lower-case spelling, e.g. 'four hundred'
  1507.      *
  1508.      * @param int    $pn_num            number to be converted to words
  1509.      * @param bool   $pb_ordinal        boolean specifying if the number should
  1510.      *                                    be ordinal
  1511.      * @param string $ps_capitalization string for specifying capitalization
  1512.      *                                    options
  1513.      * @param string $ps_locale         language name abbreviation used for
  1514.      *                                    formatting numbers as spelled-out words
  1515.      *
  1516.      * @return   string 
  1517.      * @access   private
  1518.      * @since    Method available since Release 1.5.0
  1519.      */
  1520.     function _spellNumber($pn_num,
  1521.                           $pb_ordinal = false,
  1522.                           $ps_capitalization "SP",
  1523.                           $ps_locale "en_GB")
  1524.     {
  1525.         include_once "Numbers/Words.php";
  1526.         $hs_words = Numbers_Words::toWords($pn_num$ps_locale);
  1527.         if (Pear::isError($hs_words)) {
  1528.             return $hs_words;
  1529.         }
  1530.  
  1531.         if ($pb_ordinal && substr($ps_locale02== "en"{
  1532.             if (($pn_rem ($pn_numabs abs($pn_num)) % 100== 12{
  1533.                 $hs_words substr($hs_words0-2"fth";
  1534.             else if ($pn_rem >= 11 && $pn_rem <= 15{
  1535.                 $hs_words .= "th";
  1536.             else {
  1537.                 switch ($pn_numabs % 10{
  1538.                 case 1:
  1539.                     $hs_words substr($hs_words0-3"first";
  1540.                     break;
  1541.                 case 2:
  1542.                     $hs_words substr($hs_words0-3"second";
  1543.                     break;
  1544.                 case 3:
  1545.                     $hs_words substr($hs_words0-3"ird";
  1546.                     break;
  1547.                 case 5:
  1548.                     $hs_words substr($hs_words0-2"fth";
  1549.                     break;
  1550.                 default:
  1551.                     switch (substr($hs_words-1)) {
  1552.                     case "e":
  1553.                         $hs_words substr($hs_words0-1"th";
  1554.                         break;
  1555.                     case "t":
  1556.                         $hs_words .= "h";
  1557.                         break;
  1558.                     case "y":
  1559.                         $hs_words substr($hs_words0-1"ieth";
  1560.                         break;
  1561.                     default:
  1562.                         $hs_words .= "th";
  1563.                     }
  1564.                 }
  1565.             }
  1566.         }
  1567.  
  1568.         if (($hs_char substr($ps_capitalization01)) ==
  1569.             strtolower($hs_char)) {
  1570.             $hb_upper = false;
  1571.             $hs_words strtolower($hs_words);
  1572.         else if (($hs_char substr($ps_capitalization11)) ==
  1573.                    strtolower($hs_char)) {
  1574.             $hb_upper = false;
  1575.             $hs_words ucwords($hs_words);
  1576.         else {
  1577.             $hb_upper = true;
  1578.             $hs_words strtoupper($hs_words);
  1579.         }
  1580.  
  1581.         return $hs_words;
  1582.     }
  1583.  
  1584.  
  1585.     // }}}
  1586.     // {{{ _formatNumber()
  1587.  
  1588.     /**
  1589.      * Formats a number according to the specified format string
  1590.      *
  1591.      * Private helper function, for 'format2()', which interprets the
  1592.      * codes 'SP' and 'TH' and the combination of the two as follows:
  1593.      *
  1594.      *  <code>TH</code> Ordinal number
  1595.      *  <code>SP</code> Spelled cardinal number
  1596.      *  <code>SPTH</code> Spelled ordinal number (combination of 'SP' and 'TH'
  1597.      *                   in any order)
  1598.      *  <code>THSP</code>
  1599.      *
  1600.      * Code 'SP' can have the following three variations (which can also be used
  1601.      * in combination with 'TH'):
  1602.      *
  1603.      *  <code>SP</code> returns upper-case spelling, e.g. 'FOUR HUNDRED'
  1604.      *  <code>Sp</code> returns spelling with first character of each word
  1605.      *                 capitalized, e.g. 'Four Hundred'
  1606.      *  <code>sp</code> returns lower-case spelling, e.g. 'four hundred'
  1607.      *
  1608.      * Code 'TH' can have the following two variations (although in combination
  1609.      * with code 'SP', the case specification of 'SP' takes precedence):
  1610.      *
  1611.      *  <code>TH</code> returns upper-case ordinal suffix, e.g. 400TH
  1612.      *  <code>th</code> returns lower-case ordinal suffix, e.g. 400th
  1613.      *
  1614.      * N.B. The format string is passed by reference, in order to pass back
  1615.      * the part of the format string that matches the valid codes 'SP' and
  1616.      * 'TH'.  If none of these are found, then it is set to an empty string;
  1617.      * If both codes are found then a string is returned with code 'SP'
  1618.      * preceding code 'TH' (i.e. 'SPTH', 'Spth' or 'spth').
  1619.      *
  1620.      * @param int    $pn_num         integer to be converted to words
  1621.      * @param string &$ps_format     string of formatting codes (max. length 4)
  1622.      * @param int    $pn_numofdigits no of digits to display if displayed as
  1623.      *                                 numeral (i.e. not spelled out), not
  1624.      *                                 including the sign (if negative); to
  1625.      *                                 allow all digits specify 0
  1626.      * @param bool   $pb_nopad       boolean specifying whether to suppress
  1627.      *                                 padding with leading noughts (if displayed
  1628.      *                                 as numeral)
  1629.      * @param bool   $pb_nosign      boolean specifying whether to suppress the
  1630.      *                                 display of the sign (if negative)
  1631.      * @param string $ps_locale      language name abbreviation used for
  1632.      *                                 formatting
  1633.      * @param string $ps_thousandsep optional thousand-separator (e.g. a comma)
  1634.      *                                 numbers as spelled-out words
  1635.      * @param int    $pn_padtype     optional integer to specify padding (if
  1636.      *                                 displayed as numeral) - can be
  1637.      *                                 STR_PAD_LEFT or STR_PAD_RIGHT
  1638.      *
  1639.      * @return   string 
  1640.      * @access   private
  1641.      * @since    Method available since Release 1.5.0
  1642.      */
  1643.     function _formatNumber($pn_num,
  1644.                            &$ps_format,
  1645.                            $pn_numofdigits,
  1646.                            $pb_nopad = false,
  1647.                            $pb_nosign = false,
  1648.                            $ps_locale "en_GB",
  1649.                            $ps_thousandsep = null,
  1650.                            $pn_padtype = STR_PAD_LEFT)
  1651.     {
  1652.         $hs_code1 substr($ps_format02);
  1653.         $hs_code2 substr($ps_format22);
  1654.  
  1655.         $hs_sp = null;
  1656.         $hs_th = null;
  1657.         if (strtoupper($hs_code1== "SP"{
  1658.             $hs_sp $hs_code1;
  1659.             if (strtoupper($hs_code2== "TH"{
  1660.                 $hs_th $hs_code2;
  1661.             }
  1662.         else if (strtoupper($hs_code1== "TH"{
  1663.             $hs_th $hs_code1;
  1664.             if (strtoupper($hs_code2== "SP"{
  1665.                 $hs_sp $hs_code2;
  1666.             }
  1667.         }
  1668.  
  1669.         $hn_absnum abs($pn_num);
  1670.         if ($pn_numofdigits > 0 && strlen($hn_absnum$pn_numofdigits{
  1671.             $hn_absnum intval(substr($hn_absnum-$pn_numofdigits));
  1672.         }
  1673.         $hs_num $hn_absnum;
  1674.  
  1675.         if (!is_null($hs_sp)) {
  1676.             // Spell out number:
  1677.             //
  1678.             $ps_format $hs_sp .
  1679.                          (is_null($hs_th"" ($hs_sp == "SP" "TH" "th"));
  1680.             return $this->_spellNumber(!$pb_nosign && $pn_num < 0 ?
  1681.                                            $hn_absnum * -1 :
  1682.                                            $hn_absnum,
  1683.                                        !is_null($hs_th),
  1684.                                        $hs_sp,
  1685.                                        $ps_locale);
  1686.         else {
  1687.             // Display number as Arabic numeral:
  1688.             //
  1689.             if (!$pb_nopad{
  1690.                 $hs_num str_pad($hs_num$pn_numofdigits"0"$pn_padtype);
  1691.             }
  1692.  
  1693.             if (!is_null($ps_thousandsep)) {
  1694.                 for ($i strlen($hs_num- 3; $i > 0; $i -= 3{
  1695.                     $hs_num substr($hs_num0$i.
  1696.                               $ps_thousandsep .
  1697.                               substr($hs_num$i);
  1698.                 }
  1699.             }
  1700.  
  1701.             if (!$pb_nosign{
  1702.                 if ($pn_num < 0)
  1703.                     $hs_num "-" $hs_num;
  1704.                 else if (!$pb_nopad)
  1705.                     $hs_num " " $hs_num;
  1706.             }
  1707.  
  1708.             if (!is_null($hs_th)) {
  1709.                 $ps_format $hs_th;
  1710.                 return $hs_num .
  1711.                        $this->_getOrdinalSuffix($pn_num,
  1712.                                                 substr($hs_th01== "T");
  1713.             else {
  1714.                 $ps_format "";
  1715.                 return $hs_num;
  1716.             }
  1717.         }
  1718.     }
  1719.  
  1720.  
  1721.     // }}}
  1722.     // {{{ format2()
  1723.  
  1724.     /**
  1725.      * Extended version of 'format()' with variable-length formatting codes
  1726.      *
  1727.      * Most codes reproduce the no of digits equal to the length of the code,
  1728.      * for example, 'YYY' will return the last 3 digits of the year, and so
  1729.      * the year 2007 will produce '007', and the year 89 will produce '089',
  1730.      * unless the no-padding code is used as in 'NPYYY', which will return
  1731.      * '89'.
  1732.      *
  1733.      * For negative values, the sign will be discarded, unless the 'S' code
  1734.      * is used in combination, but note that for positive values the value
  1735.      * will be padded with a leading space unless it is suppressed with
  1736.      * the no-padding modifier, for example for 2007:
  1737.      *
  1738.      *  <code>YYYY</code> returns '2007'
  1739.      *  <code>SYYYY</code> returns ' 2007'
  1740.      *  <code>NPSYYYY</code> returns '2007'
  1741.      *
  1742.      * The no-padding modifier 'NP' can be used with numeric codes to
  1743.      * suppress leading (or trailing in the case of code 'F') noughts, and
  1744.      * with character-returning codes such as 'DAY' to suppress trailing
  1745.      * spaces, which will otherwise be padded to the maximum possible length
  1746.      * of the return-value of the code; for example, for Monday:
  1747.      *
  1748.      *  <code>Day</code> returns 'Monday   ' because the maximum length of
  1749.      *                  this code is 'Wednesday';
  1750.      *  <code>NPDay</code> returns 'Monday'
  1751.      *
  1752.      * N.B. this code affects the code immediately following only, and
  1753.      * without this code the default is always to apply padding.
  1754.      *
  1755.      * Most character-returning codes, such as 'MONTH', will
  1756.      * set the capitalization according to the code, so for example:
  1757.      *
  1758.      *  <code>MONTH</code> returns upper-case spelling, e.g. 'JANUARY'
  1759.      *  <code>Month</code> returns spelling with first character of each word
  1760.      *                    capitalized, e.g. 'January'
  1761.      *  <code>month</code> returns lower-case spelling, e.g. 'january'
  1762.      *
  1763.      * Where it makes sense, numeric codes can be combined with a following
  1764.      * 'SP' code which spells out the number, or with a 'TH' code, which
  1765.      * renders the code as an ordinal ('TH' only works in English), for
  1766.      * example, for 31st December:
  1767.      *
  1768.      *  <code>DD</code> returns '31'
  1769.      *  <code>DDTH</code> returns '31ST'
  1770.      *  <code>DDth</code> returns '31st'
  1771.      *  <code>DDSP</code> returns 'THIRTY-ONE'
  1772.      *  <code>DDSp</code> returns 'Thirty-one'
  1773.      *  <code>DDsp</code> returns 'thirty-one'
  1774.      *  <code>DDSPTH</code> returns 'THIRTY-FIRST'
  1775.      *  <code>DDSpth</code> returns 'Thirty-first'
  1776.      *  <code>DDspth</code> returns 'thirty-first'
  1777.      *
  1778.      *
  1779.      * All formatting options:
  1780.      *
  1781.      *  <code>-</code> All punctuation and white-space is reproduced unchanged
  1782.      *  <code>/</code>
  1783.      *  <code>,</code>
  1784.      *  <code>.</code>
  1785.      *  <code>;</code>
  1786.      *  <code>:</code>
  1787.      *  <code> </code>
  1788.      *  <code>"text"</code> Quoted text is reproduced unchanged (escape using
  1789.      *                     '\')
  1790.      *  <code>AD</code> AD indicator with or without full stops; N.B. if you
  1791.      *                 are using 'Astronomical' year numbering then 'A.D./B.C.'
  1792.      *                 indicators will be out for negative years
  1793.      *  <code>A.D.</code>
  1794.      *  <code>AM</code> Meridian indicator with or without full stops
  1795.      *  <code>A.M.</code>
  1796.      *  <code>BC</code> BC indicator with or without full stops
  1797.      *  <code>B.C.</code>
  1798.      *  <code>BCE</code> BCE indicator with or without full stops
  1799.      *  <code>B.C.E.</code>
  1800.      *  <code>CC</code> Century, i.e. the year divided by 100, discarding the
  1801.      *                 remainder; 'S' prefixes negative years with a minus sign
  1802.      *  <code>SCC</code>
  1803.      *  <code>CE</code> CE indicator with or without full stops
  1804.      *  <code>C.E.</code>
  1805.      *  <code>D</code> Day of week (0-6), where 0 represents Sunday
  1806.      *  <code>DAY</code> Name of day, padded with blanks to display width of the
  1807.      *                  widest name of day in the locale of the machine
  1808.      *  <code>DD</code> Day of month (1-31)
  1809.      *  <code>DDD</code> Day of year (1-366)
  1810.      *  <code>DY</code> Abbreviated name of day
  1811.      *  <code>FFF</code> Fractional seconds; no radix character is printed.  The
  1812.      *                  no of 'F's determines the no of digits of the
  1813.      *                  part-second to return; e.g. 'HH:MI:SS.FF'
  1814.      *  <code>F[integer]</code> The integer after 'F' specifies the number of
  1815.      *                         digits of the part-second to return.  This is an
  1816.      *                         alternative to using F[integer], and 'F3' is thus
  1817.      *                         equivalent to using 'FFF'.
  1818.      *  <code>HH</code> Hour of day (0-23)
  1819.      *  <code>HH12</code> Hour of day (1-12)
  1820.      *  <code>HH24</code> Hour of day (0-23)
  1821.      *  <code>ID</code> Day of week (1-7) based on the ISO standard
  1822.      *  <code>IW</code> Week of year (1-52 or 1-53) based on the ISO standard
  1823.      *  <code>IYYY</code> 4-digit year based on the ISO 8601 standard; 'S'
  1824.      *                   prefixes negative years with a minus sign
  1825.      *  <code>SIYYY</code>
  1826.      *  <code>IYY</code> Last 3, 2, or 1 digit(s) of ISO year
  1827.      *  <code>IY</code>
  1828.      *  <code>I</code>
  1829.      *  <code>J</code> Julian day - the number of days since Monday, 24th
  1830.      *                November, 4714 B.C. (proleptic Gregorian calendar)
  1831.      *  <code>MI</code> Minute (0-59)
  1832.      *  <code>MM</code> Month (01-12; January = 01)
  1833.      *  <code>MON</code> Abbreviated name of month
  1834.      *  <code>MONTH</code> Name of month, padded with blanks to display width of
  1835.      *                    the widest name of month in the date language used for
  1836.      *  <code>PM</code> Meridian indicator with or without full stops
  1837.      *  <code>P.M.</code>
  1838.      *  <code>Q</code> Quarter of year (1, 2, 3, 4; January - March = 1)
  1839.      *  <code>RM</code> Roman numeral month (I-XII; January = I); N.B. padded
  1840.      *                 with leading spaces.
  1841.      *  <code>SS</code> Second (0-59)
  1842.      *  <code>SSSSS</code> Seconds past midnight (0-86399)
  1843.      *  <code>TZC</code> Abbreviated form of time zone name, e.g. 'GMT', or the
  1844.      *                  abbreviation for Summer time if the date falls in Summer
  1845.      *                  time, e.g. 'BST'.
  1846.      *                  N.B. this is not a unique identifier - for this purpose
  1847.      *                  use the time zone region (code 'TZR').
  1848.      *  <code>TZH</code> Time zone hour; 'S' prefixes the hour with the correct
  1849.      *                  sign, (+/-), which otherwise is not displayed.  Note
  1850.      *                  that the leading nought can be suppressed with the
  1851.      *                  no-padding code 'NP').  Also note that if you combine
  1852.      *                  with the 'SP' code, the sign will not be spelled out.
  1853.      *                  (I.e. 'STZHSp' will produce '+One', for example, and
  1854.      *                  not 'Plus One'.
  1855.      *                  'TZH:TZM' will produce, for example, '+05:30'.  (Also
  1856.      *                  see 'TZM' format code)
  1857.      *  <code>STZH</code>
  1858.      *  <code>TZI</code> Whether or not the date is in Summer time (daylight
  1859.      *                  saving time).  Returns '1' if Summer time, else '0'.
  1860.      *  <code>TZM</code> Time zone minute, without any +/- sign.  (Also see
  1861.      *                  'TZH' format element)
  1862.      *  <code>TZN</code> Long form of time zone name, e.g.
  1863.      *                  'Greenwich Mean Time', or the name of the Summer time if
  1864.      *                  the date falls in Summer time, e.g.
  1865.      *                  'British Summer Time'.  N.B. this is not a unique
  1866.      *                  identifier - for this purpose use the time zone region
  1867.      *                  (code 'TZR').
  1868.      *  <code>TZO</code> Time zone offset in ISO 8601 form - that is, 'Z' if
  1869.      *                  UTC, else [+/-][hh]:[mm] (which would be equivalent
  1870.      *                  to 'STZH:TZM').  Note that this result is right padded
  1871.      *                  with spaces by default, (i.e. if 'Z').
  1872.      *  <code>TZS</code> Time zone offset in seconds; 'S' prefixes negative
  1873.      *                  sign with minus sign '-' if negative, and no sign if
  1874.      *                  positive (i.e. -43200 to 50400).
  1875.      *  <code>STZS</code>
  1876.      *  <code>TZR</code> Time zone region, that is, the name or ID of the time
  1877.      *                  zone e.g. 'Europe/London'.  This value is unique for
  1878.      *                  each time zone.
  1879.      *  <code>U</code> Seconds since the Unix Epoch -
  1880.      *                January 1 1970 00:00:00 GMT
  1881.      *  <code>W</code> 'Absolute' week of month (1-5), counting week 1 as
  1882.      *                1st-7th of the year, regardless of the day
  1883.      *  <code>W1</code> Week of year (1-54), counting week 1 as the week that
  1884.      *                 contains 1st January
  1885.      *  <code>W4</code> Week of year (1-53), counting week 1 as the week that
  1886.      *                 contains 4th January (i.e. first week with at least 4
  1887.      *                 days)
  1888.      *  <code>W7</code> Week of year (1-53), counting week 1 as the week that
  1889.      *                 contains 7th January (i.e. first full week)
  1890.      *  <code>WW</code> 'Absolute' week of year (1-53), counting week 1 as
  1891.      *                 1st-7th of the year, regardless of the day
  1892.      *  <code>YEAR</code> Year, spelled out; 'S' prefixes negative years with
  1893.      *                  'MINUS'; N.B. 'YEAR' differs from 'YYYYSP' in that the
  1894.      *                   first will render 1923, for example, as 'NINETEEN
  1895.      *                   TWENTY-THREE, and the second as 'ONE THOUSAND NINE
  1896.      *                   HUNDRED TWENTY-THREE'
  1897.      *  <code>SYEAR</code>
  1898.      *  <code>YYYY</code> 4-digit year; 'S' prefixes negative years with a minus
  1899.      *                   sign
  1900.      *  <code>SYYYY</code>
  1901.      *  <code>YYY</code> Last 3, 2, or 1 digit(s) of year
  1902.      *  <code>YY</code>
  1903.      *  <code>Y</code>
  1904.      *  <code>Y,YYY</code> Year with thousands-separator in this position; five
  1905.      *                    possible separators
  1906.      *  <code>Y.YYY</code>
  1907.      *  <code>Y·YYY</code> N.B. space-dot (mid-dot, interpunct) is valid only in
  1908.      *                    ISO 8859-1 (so take care when using UTF-8 in
  1909.      *                    particular)
  1910.      *  <code>Y'YYY</code>
  1911.      *  <code>Y YYY</code>
  1912.      *
  1913.      * In addition the following codes can be used in combination with other
  1914.      * codes;
  1915.      *  Codes that modify the next code in the format string:
  1916.      *
  1917.      *  <code>NP</code> 'No Padding' - Returns a value with no trailing blanks
  1918.      *                 and no leading or trailing noughts; N.B. that the
  1919.      *                 default is to include this padding in the return string.
  1920.      *                 N.B. affects the code immediately following only.
  1921.      *
  1922.      *  Codes that modify the previous code in the format string (can only
  1923.      *  be used with integral codes such as 'MM'):
  1924.      *
  1925.      *  <code>TH</code> Ordinal number
  1926.      *  <code>SP</code> Spelled cardinal number
  1927.      *  <code>SPTH</code> Spelled ordinal number (combination of 'SP' and 'TH'
  1928.      *                   in any order)
  1929.      *  <code>THSP</code>
  1930.      *
  1931.      * Code 'SP' can have the following three variations (which can also be used
  1932.      * in combination with 'TH'):
  1933.      *
  1934.      *  <code>SP</code> returns upper-case spelling, e.g. 'FOUR HUNDRED'
  1935.      *  <code>Sp</code> returns spelling with first character of each word
  1936.      *                 capitalized, e.g. 'Four Hundred'
  1937.      *  <code>sp</code> returns lower-case spelling, e.g. 'four hundred'
  1938.      *
  1939.      * Code 'TH' can have the following two variations (although in combination
  1940.      * with code 'SP', the case specification of 'SP' takes precedence):
  1941.      *
  1942.      *  <code>TH</code> returns upper-case ordinal suffix, e.g. 400TH
  1943.      *  <code>th</code> returns lower-case ordinal suffix, e.g. 400th
  1944.      *
  1945.      * @param string $ps_format format string for returned date/time
  1946.      * @param string $ps_locale language name abbreviation used for formatting
  1947.      *                            numbers as spelled-out words
  1948.      *
  1949.      * @return   string     date/time in given format
  1950.      * @access   public
  1951.      * @since    Method available since Release 1.5.0
  1952.      */
  1953.     function format2($ps_format$ps_locale "en_GB")
  1954.     {
  1955.         if (!preg_match('/^("([^"\\\\]|\\\\\\\\|\\\\")*"|(D{1,3}|S?C+|' .
  1956.                         'HH(12|24)?|I[DW]|S?IY*|J|M[IM]|Q|SS(SSS)?|S?TZ[HS]|' .
  1957.                         'TZM|U|W[W147]?|S?Y{1,3}([,.·\' ]?YYY)*)(SP(TH)?|' .
  1958.                         'TH(SP)?)?|AD|A\.D\.|AM|A\.M\.|BCE?|B\.C\.(E\.)?|CE|' .
  1959.                         'C\.E\.|DAY|DY|F(F*|[1-9][0-9]*)|MON(TH)?|NP|PM|' .
  1960.                         'P\.M\.|RM|TZ[CINOR]|S?YEAR|[^A-Z0-9"])*$/i',
  1961.                         $ps_format)) {
  1962.             return PEAR::raiseError("Invalid date format '$ps_format'",
  1963.                                     DATE_ERROR_INVALIDFORMATSTRING);
  1964.         }
  1965.  
  1966.         $ret "";
  1967.         $i   = 0;
  1968.  
  1969.         $hb_nopadflag    = false;
  1970.         $hb_showsignflag = false;
  1971.  
  1972.         $hn_weekdaypad = null;
  1973.         $hn_monthpad   = null;
  1974.         $hn_isoyear    = null;
  1975.         $hn_isoweek    = null;
  1976.         $hn_isoday     = null;
  1977.         $hn_tzoffset   = null;
  1978.  
  1979.         while ($i strlen($ps_format)) {
  1980.             $hb_lower = false;
  1981.  
  1982.             if ($hb_nopadflag{
  1983.                 $hb_nopad = true;
  1984.             else {
  1985.                 $hb_nopad = false;
  1986.             }
  1987.             if ($hb_showsignflag{
  1988.                 $hb_nosign = false;
  1989.             else {
  1990.                 $hb_nosign = true;
  1991.             }
  1992.             $hb_nopadflag    = false;
  1993.             $hb_showsignflag = false;
  1994.  
  1995.             switch ($hs_char substr($ps_format$i1)) {
  1996.             case "-":
  1997.             case "/":
  1998.             case ",":
  1999.             case ".":
  2000.             case ";":
  2001.             case ":":
  2002.             case " ":
  2003.                 $ret .= $hs_char;
  2004.                 $i   += 1;
  2005.                 break;
  2006.             case "\"":
  2007.                 preg_match('/(([^"\\\\]|\\\\\\\\|\\\\")*)"/',
  2008.                            $ps_format,
  2009.                            $ha_matches,
  2010.                            PREG_OFFSET_CAPTURE,
  2011.                            $i + 1);
  2012.                 $ret .= str_replace(array('\\\\''\\"'),
  2013.                                     array('\\''"'),
  2014.                                     $ha_matches[1][0]);
  2015.                 $i   += strlen($ha_matches[0][0]+ 1;
  2016.                 break;
  2017.             case "a":
  2018.                 $hb_lower = true;
  2019.             case "A":
  2020.                 if (strtoupper(substr($ps_format$i4)) == "A.D."{
  2021.                     $ret .= $this->year >= 0 ?
  2022.                             ($hb_lower "a.d." "A.D.":
  2023.                             ($hb_lower "b.c." "B.C.");
  2024.                     $i   += 4;
  2025.                 else if (strtoupper(substr($ps_format$i2)) == "AD"{
  2026.                     $ret .= $this->year >= 0 ?
  2027.                             ($hb_lower "ad" "AD":
  2028.                             ($hb_lower "bc" "BC");
  2029.                     $i   += 2;
  2030.                 else {
  2031.                     if ($this->ob_invalidtime)
  2032.                         return $this->_getErrorInvalidTime();
  2033.                     if (strtoupper(substr($ps_format$i4)) == "A.M."{
  2034.                         $ret .= $this->hour < 12 ?
  2035.                                 ($hb_lower "a.m." "A.M.":
  2036.                                 ($hb_lower "p.m." "P.M.");
  2037.                         $i   += 4;
  2038.                     else if (strtoupper(substr($ps_format$i2)) == "AM"{
  2039.                         $ret .= $this->hour < 12 ?
  2040.                                 ($hb_lower "am" "AM":
  2041.                                 ($hb_lower "pm" "PM");
  2042.                         $i   += 2;
  2043.                     }
  2044.                 }
  2045.  
  2046.                 break;
  2047.             case "b":
  2048.                 $hb_lower = true;
  2049.             case "B":
  2050.                 // Check for 'B.C.E.' first:
  2051.                 //
  2052.                 if (strtoupper(substr($ps_format$i6)) == "B.C.E."{
  2053.                     if ($this->year >= 0{
  2054.                         $hs_era $hb_lower "c.e." "C.E.";
  2055.                         $ret   .= $hb_nopad ?
  2056.                                   $hs_era :
  2057.                                   str_pad($hs_era6" "STR_PAD_RIGHT);
  2058.                     else {
  2059.                         $ret .= $hb_lower "b.c.e." "B.C.E.";
  2060.                     }
  2061.                     $i += 6;
  2062.                 else if (strtoupper(substr($ps_format$i3)) == "BCE"{
  2063.                     if ($this->year >= 0{
  2064.                         $hs_era $hb_lower "ce" "CE";
  2065.                         $ret   .= $hb_nopad ?
  2066.                                   $hs_era :
  2067.                                   str_pad($hs_era3" "STR_PAD_RIGHT);
  2068.                     else {
  2069.                         $ret .= $hb_lower "bce" "BCE";
  2070.                     }
  2071.                     $i += 3;
  2072.                 else if (strtoupper(substr($ps_format$i4)) == "B.C."{
  2073.                     $ret .= $this->year >= 0 ?
  2074.                             ($hb_lower "a.d." "A.D.":
  2075.                             ($hb_lower "b.c." "B.C.");
  2076.                     $i   += 4;
  2077.                 else if (strtoupper(substr($ps_format$i2)) == "BC"{
  2078.                     $ret .= $this->year >= 0 ?
  2079.                             ($hb_lower "ad" "AD":
  2080.                             ($hb_lower "bc" "BC");
  2081.                     $i   += 2;
  2082.                 }
  2083.  
  2084.                 break;
  2085.             case "c":
  2086.                 $hb_lower = true;
  2087.             case "C":
  2088.                 if (strtoupper(substr($ps_format$i4)) == "C.E."{
  2089.                     if ($this->year >= 0{
  2090.                         $hs_era $hb_lower "c.e." "C.E.";
  2091.                         $ret   .= $hb_nopad ?
  2092.                                   $hs_era :
  2093.                                   str_pad($hs_era6" "STR_PAD_RIGHT);
  2094.                     else {
  2095.                         $ret .= $hb_lower "b.c.e." "B.C.E.";
  2096.                     }
  2097.                     $i += 4;
  2098.                 else if (strtoupper(substr($ps_format$i2)) == "CE"{
  2099.                     if ($this->year >= 0{
  2100.                         $hs_era $hb_lower "ce" "CE";
  2101.                         $ret   .= $hb_nopad ?
  2102.                                   $hs_era :
  2103.                                   str_pad($hs_era3" "STR_PAD_RIGHT);
  2104.                     else {
  2105.                         $ret .= $hb_lower "bce" "BCE";
  2106.                     }
  2107.                     $i += 2;
  2108.                 else {
  2109.                     // Code C(CCC...):
  2110.                     //
  2111.                     $hn_codelen = 1;
  2112.                     while (strtoupper(substr($ps_format,
  2113.                                              $i $hn_codelen,
  2114.                                              1)) == "C")
  2115.                         ++$hn_codelen;
  2116.  
  2117.                     // Check next code is not 'CE' or 'C.E.'
  2118.                     //
  2119.                     if ($hn_codelen > 1 &&
  2120.                         (strtoupper(substr($ps_format,
  2121.                                            $i $hn_codelen - 1,
  2122.                                            4)) == "C.E." ||
  2123.                          strtoupper(substr($ps_format,
  2124.                                            $i $hn_codelen - 1,
  2125.                                            2)) == "CE"
  2126.                          ))
  2127.                         --$hn_codelen;
  2128.  
  2129.                     $hn_century      intval($this->year / 100);
  2130.                     $hs_numberformat substr($ps_format$i $hn_codelen4);
  2131.                     $hs_century      $this->_formatNumber($hn_century,
  2132.                                                             $hs_numberformat,
  2133.                                                             $hn_codelen,
  2134.                                                             $hb_nopad,
  2135.                                                             $hb_nosign,
  2136.                                                             $ps_locale);
  2137.                     if (Pear::isError($hs_century))
  2138.                         return $hs_century;
  2139.  
  2140.                     $ret .= $hs_century;
  2141.                     $i   += $hn_codelen strlen($hs_numberformat);
  2142.                 }
  2143.  
  2144.                 break;
  2145.             case "d":
  2146.                 $hb_lower = true;
  2147.             case "D":
  2148.                 if (strtoupper(substr($ps_format$i3)) == "DAY"{
  2149.                     $hs_day Date_Calc::getWeekdayFullname($this->day,
  2150.                                                             $this->month,
  2151.                                                             $this->year);
  2152.  
  2153.                     if (!$hb_nopad{
  2154.                         if (is_null($hn_weekdaypad)) {
  2155.                             // Set week-day padding variable:
  2156.                             //
  2157.                             $hn_weekdaypad = 0;
  2158.                             foreach (Date_Calc::getWeekDays(as $hs_weekday)
  2159.                                 $hn_weekdaypad max($hn_weekdaypad,
  2160.                                                      strlen($hs_weekday));
  2161.                         }
  2162.                         $hs_day str_pad($hs_day,
  2163.                                           $hn_weekdaypad,
  2164.                                           " ",
  2165.                                           STR_PAD_RIGHT);
  2166.                     }
  2167.  
  2168.                     $ret .= $hb_lower ?
  2169.                             strtolower($hs_day:
  2170.                             (substr($ps_format$i + 11== "A" ?
  2171.                              strtoupper($hs_day:
  2172.                              $hs_day);
  2173.                     $i   += 3;
  2174.                 else if (strtoupper(substr($ps_format$i2)) == "DY"{
  2175.                     $hs_day Date_Calc::getWeekdayAbbrname($this->day,
  2176.                                                             $this->month,
  2177.                                                             $this->year);
  2178.                     $ret   .= $hb_lower ?
  2179.                               strtolower($hs_day:
  2180.                               (substr($ps_format$i + 11== "Y" ?
  2181.                                strtoupper($hs_day:
  2182.                                $hs_day);
  2183.                     $i     += 2;
  2184.                 else if (strtoupper(substr($ps_format$i3)) == "DDD" &&
  2185.                            strtoupper(substr($ps_format$i + 23)) != "DAY" &&
  2186.                            strtoupper(substr($ps_format$i + 22)) != "DY"
  2187.                            {
  2188.                     $hn_day Date_Calc::dayOfYear($this->day,
  2189.                                                    $this->month,
  2190.                                                    $this->year);
  2191.                     $hs_numberformat substr($ps_format$i + 34);
  2192.                     $hs_day $this->_formatNumber($hn_day,
  2193.                                                    $hs_numberformat,
  2194.                                                    3,
  2195.                                                    $hb_nopad,
  2196.                                                    true,
  2197.                                                    $ps_locale);
  2198.                     if (Pear::isError($hs_day))
  2199.                         return $hs_day;
  2200.  
  2201.                     $ret .= $hs_day;
  2202.                     $i   += 3 + strlen($hs_numberformat);
  2203.                 else if (strtoupper(substr($ps_format$i2)) == "DD" &&
  2204.                            strtoupper(substr($ps_format$i + 13))&nbs