]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/Date.php
[PEAR] Modernize Validate code
[quix0rs-gnu-social.git] / extlib / 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 {@link Date_TimeZone} class.
11  * Supports several operations from {@link Date_Calc} on Date objects.
12  *
13  * PHP versions 4 and 5
14  *
15  * LICENSE:
16  *
17  * Copyright (c) 1997-2008 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$
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 {@link Date::setNow()}.  Note that this makes a call to
88  * {@link http://www.php.net/gettimeofday gettimeofday()}, which may
89  * not work on all systems.
90  *
91  * @since    Constant available since Release 1.5.0
92  */
93 define('DATE_CAPTURE_MICROTIME_BY_DEFAULT', false);
94
95 /**
96  * Whether to correct, by adding the local Summer time offset, the
97  * specified time if it falls in the 'skipped hour' (encountered
98  * when the clocks go forward).
99  *
100  * N.B. if specified as 'false', and if a time zone that adjusts
101  * for Summer time is specified, then an object of this class will
102  * be set to a semi-invalid state if an invalid time is set.  That
103  * is, an error will not be returned, unless the user then calls
104  * a function, directly or indirectly, that accesses the time
105  * part of the object.  So, for example, if the user calls:
106  *
107  * <code>$date_object->formatLikeSQL('HH.MI.SS');</code>
108  *
109  * or:
110  *
111  * <code>$date_object->addSeconds(30);</code>
112  *
113  * an error will be returned if the time is invalid.  However,
114  * if the user calls:
115  *
116  * <code>$date_object->addDays(1);</code>
117  *
118  * for example, such that the time is no longer invalid, then the
119  * object will no longer be in this invalid state.  This behaviour
120  * is intended to minimize unexpected errors when a user uses the
121  * class to do addition with days only, and does not intend to
122  * access the time.
123  *
124  * Of course, this constant will be unused if the user chooses to
125  * work in UTC or a time zone without Summer time, in which case
126  * this situation will never arise.
127  *
128  * This constant is set to 'true' by default for backwards-compatibility
129  * reasons, however, you are recommended to set it to 'false'.  Note that the
130  * behaviour is not intended to match that of previous versions of the class
131  * in terms of ignoring the Summer time offset when making calculations which
132  * involve dates in both standard and Summer time - this was recognized as a
133  * bug - but in terms of returning a PEAR error object when the user sets the
134  * object to an invalid date (i.e. a time in the hour which is skipped when
135  * the clocks go forwards, which in Europe would be a time such as 01.30).
136  * Backwards compatibility here means that the behaviour is the same as it
137  * used to be, less the bug.
138  *
139  * Note that this problem is not an issue for the user if any of these
140  * conditions are satisfied:
141  *
142  * <ol>
143  *  <li>the user uses a time zone that does not observe Summer time, e.g. UTC</li>
144  *  <li>the user never accesses the time, that is, he never makes a call to
145  *       {@link Date::getHour()} or {@link Date::formatLikeStrftime()} using
146  *       format code '<b>%H</b>', for example, even if he sets the time to
147  *       something invalid</li>
148  *  <li>the user sets DATE_CORRECTINVALIDTIME_DEFAULT to true</li>
149  * </ol>
150  *
151  * @since    Constant available since Release 1.5.0
152  * @see      Date::isValidTime(), DATE_VALIDATE_DATE_BY_DEFAULT
153  */
154 define('DATE_CORRECTINVALIDTIME_DEFAULT', true);
155
156 /**
157  * Whether to validate dates (i.e. day/month/year, ignoring the time) by
158  * disallowing invalid dates (e.g. 31st February) being set by the following
159  * functions:
160  *
161  *  - {@link Date::setYear()}
162  *  - {@link Date::setMonth()}
163  *  - {@link Date::setDay()}
164  *
165  * If the constant is set to 'true', then the date will be checked (by
166  * default), and if invalid, an error will be returned with the Date object
167  * left unmodified.
168  *
169  * This constant is set to 'false' by default for backwards-compatibility
170  * reasons, however, you are recommended to set it to 'true'.
171  *
172  * Note that {@link Date::setHour()}, {@link Date::setMinute()},
173  * {@link Date::setSecond()} and {@link Date::setPartSecond()}
174  * allow an invalid date/time to be set regardless of the value of this
175  * constant.
176  *
177  * @see      Date::isValidDate(), Date::isValidTime(), Date::isNull(),
178  *            DATE_CORRECTINVALIDTIME_DEFAULT
179  * @since    Constant available since Release 1.5.0
180  */
181 define('DATE_VALIDATE_DATE_BY_DEFAULT', false);
182
183 /**
184  * Whether, by default, to accept times including leap seconds (i.e. '23.59.60')
185  * when setting the date/time, and whether to count leap seconds in the
186  * following functions:
187  *
188  *  - {@link Date::addSeconds()}
189  *  - {@link Date::subtractSeconds()}
190  *  - {@link Date_Calc::addSeconds()}
191  *  - {@link Date::round()}
192  *  - {@link Date::roundSeconds()}
193  *
194  * This constant is set to 'false' by default for backwards-compatibility
195  * reasons, however, you are recommended to set it to 'true'.
196  *
197  * Note that this constant does not affect {@link Date::addSpan()} and
198  * {@link Date::subtractSpan()} which will not count leap seconds in any case.
199  *
200  * @since    Constant available since Release 1.5.0
201  */
202 define('DATE_COUNT_LEAP_SECONDS', false);
203
204 /**
205  * Method to call when user invokes {@link Date::format()}
206  *
207  * @since    Constant available since Release 1.5.1
208  */
209 define('DATE_FORMAT_METHOD', 'formatLikeStrftime');
210
211
212 // }}}
213 // {{{ Output format constants (used in {@link Date::getDate()})
214
215 /**
216  * "YYYY-MM-DD HH:MM:SS"
217  */
218 define('DATE_FORMAT_ISO', 1);
219
220 /**
221  * "YYYYMMDDTHHMMSS(Z|(+/-)HHMM)?"
222  */
223 define('DATE_FORMAT_ISO_BASIC', 2);
224
225 /**
226  * "YYYY-MM-DDTHH:MM:SS(Z|(+/-)HH:MM)?"
227  */
228 define('DATE_FORMAT_ISO_EXTENDED', 3);
229
230 /**
231  * "YYYY-MM-DDTHH:MM:SS(.S*)?(Z|(+/-)HH:MM)?"
232  */
233 define('DATE_FORMAT_ISO_EXTENDED_MICROTIME', 6);
234
235 /**
236  * "YYYYMMDDHHMMSS"
237  */
238 define('DATE_FORMAT_TIMESTAMP', 4);
239
240 /**
241  * long int, seconds since the unix epoch
242  */
243 define('DATE_FORMAT_UNIXTIME', 5);
244
245
246 // }}}
247 // {{{ Class: Date
248
249 /**
250  * Generic date handling class for PEAR
251  *
252  * Supports time zones with the Date_TimeZone class.  Supports several
253  * operations from Date_Calc on Date objects.
254  *
255  * Note to developers: the class stores the local time and date in the
256  * local standard time.  That is, it does not store the time as the
257  * local Summer time when and if the time zone is in Summer time.  It
258  * is much easier to store local standard time and remember to offset
259  * it when the user requests it.
260  *
261  * @category  Date and Time
262  * @package   Date
263  * @author    Baba Buehler <baba@babaz.com>
264  * @author    Pierre-Alain Joye <pajoye@php.net>
265  * @author    Firman Wandayandi <firman@php.net>
266  * @author    C.A. Woodcock <c01234@netcomuk.co.uk>
267  * @copyright 1997-2007 Baba Buehler, Pierre-Alain Joye, Firman Wandayandi, C.A. Woodcock
268  * @license   http://www.opensource.org/licenses/bsd-license.php
269  *            BSD License
270  * @version   Release: 1.5.0a1
271  * @link      http://pear.php.net/package/Date
272  */
273 class Date
274 {
275
276     // {{{ Properties
277
278     /**
279      * The year
280      *
281      * @var      int
282      * @access   private
283      * @since    Property available since Release 1.0
284      */
285     public $year;
286
287     /**
288      * The month
289      *
290      * @var      int
291      * @access   private
292      * @since    Property available since Release 1.0
293      */
294     public $month;
295
296     /**
297      * The day
298      *
299      * @var      int
300      * @access   private
301      * @since    Property available since Release 1.0
302      */
303     public $day;
304
305     /**
306      * The hour
307      *
308      * @var      int
309      * @access   private
310      * @since    Property available since Release 1.0
311      */
312     public $hour;
313
314     /**
315      * The minute
316      *
317      * @var      int
318      * @access   private
319      * @since    Property available since Release 1.0
320      */
321     public $minute;
322
323     /**
324      * The second
325      *
326      * @var      int
327      * @access   private
328      * @since    Property available since Release 1.0
329      */
330     public $second;
331
332     /**
333      * The parts of a second
334      *
335      * @var      float
336      * @access   private
337      * @since    Property available since Release 1.4.3
338      */
339     public $partsecond;
340
341     /**
342      * The year in local standard time
343      *
344      * @var      int
345      * @access   private
346      * @since    Property available since Release 1.5.0
347      */
348     public $on_standardyear;
349
350     /**
351      * The month in local standard time
352      *
353      * @var      int
354      * @access   private
355      * @since    Property available since Release 1.5.0
356      */
357     public $on_standardmonth;
358
359     /**
360      * The day in local standard time
361      *
362      * @var      int
363      * @access   private
364      * @since    Property available since Release 1.5.0
365      */
366     public $on_standardday;
367
368     /**
369      * The hour in local standard time
370      *
371      * @var      int
372      * @access   private
373      * @since    Property available since Release 1.5.0
374      */
375     public $on_standardhour;
376
377     /**
378      * The minute in local standard time
379      *
380      * @var      int
381      * @access   private
382      * @since    Property available since Release 1.5.0
383      */
384     public $on_standardminute;
385
386     /**
387      * The second in local standard time
388      *
389      * @var      int
390      * @access   private
391      * @since    Property available since Release 1.5.0
392      */
393     public $on_standardsecond;
394
395     /**
396      * The part-second in local standard time
397      *
398      * @var      float
399      * @access   private
400      * @since    Property available since Release 1.5.0
401      */
402     public $on_standardpartsecond;
403
404     /**
405      * Whether the object should accept and count leap seconds
406      *
407      * @var      bool
408      * @access   private
409      * @since    Property available since Release 1.5.0
410      */
411     public $ob_countleapseconds;
412
413     /**
414      * Whether the time is valid as a local time (an invalid time
415      * is one that lies in the 'skipped hour' at the point that
416      * the clocks go forward)
417      *
418      * @var      bool
419      * @access   private
420      * @see      Date::isValidTime()
421      * @since    Property available since Release 1.5.0
422      */
423     public $ob_invalidtime = null;
424
425     /**
426      * Date_TimeZone object for this date
427      *
428      * @var      object     Date_TimeZone object
429      * @access   private
430      * @since    Property available since Release 1.0
431      */
432     public $tz;
433
434     /**
435      * Defines the default weekday abbreviation length
436      *
437      * Formerly used by {@link Date::formatLikeStrftime()}, but now
438      * redundant - the abbreviation for the current locale of the machine
439      * is used.
440      *
441      * @var      int
442      * @access   private
443      * @since    Property available since Release 1.4.4
444      */
445     public $getWeekdayAbbrnameLength = 3;
446
447
448     // }}}
449     // {{{ Constructor
450
451     /**
452      * Constructor
453      *
454      * Creates a new Date Object initialized to the current date/time in the
455      * system-default timezone by default.  A date optionally
456      * passed in may be in the ISO 8601, TIMESTAMP or UNIXTIME format,
457      * or another Date object.  If no date is passed, the current date/time
458      * is used.
459      *
460      * If a date is passed and an exception is returned by {@link Date::setDate()}
461      * there is nothing that this function can do, so for this reason, it
462      * is advisable to pass no parameter and to make a separate call to
463      * Date::setDate().  A date/time should only be passed if known to be a
464      * valid ISO 8601 string or a valid Unix timestamp.
465      *
466      * @param mixed $date optional ISO 8601 date/time to initialize;
467      *                                    or, a Unix time stamp
468      * @param bool $pb_countleapseconds whether to count leap seconds
469      *                                    (defaults to
470      *                                    {@link DATE_COUNT_LEAP_SECONDS})
471      *
472      * @return   void
473      * @access   public
474      * @see      Date::setDate()
475      */
476     public function Date(
477         $date = null,
478         $pb_countleapseconds = DATE_COUNT_LEAP_SECONDS
479     )
480     {
481         $this->ob_countleapseconds = $pb_countleapseconds;
482
483         if (is_a($date, 'Date')) {
484             $this->copy($date);
485         } else {
486             if (!is_null($date)) {
487                 // 'setDate()' expects a time zone to be already set:
488                 //
489                 $this->_setTZToDefault();
490                 $this->setDate($date);
491             } else {
492                 $this->setNow();
493             }
494         }
495     }
496
497
498     // }}}
499     // {{{ copy()
500
501     /**
502      * Copy values from another Date object
503      *
504      * Makes this Date a copy of another Date object.  This is a
505      * PHP4-compatible implementation of {@link Date::__clone()} in PHP5.
506      *
507      * @param object $date Date object to copy
508      *
509      * @return   void
510      * @access   public
511      */
512     public function copy($date)
513     {
514         $this->year = $date->year;
515         $this->month = $date->month;
516         $this->day = $date->day;
517         $this->hour = $date->hour;
518         $this->minute = $date->minute;
519         $this->second = $date->second;
520         $this->partsecond = $date->partsecond;
521
522         $this->on_standardyear = $date->on_standardyear;
523         $this->on_standardmonth = $date->on_standardmonth;
524         $this->on_standardday = $date->on_standardday;
525         $this->on_standardhour = $date->on_standardhour;
526         $this->on_standardminute = $date->on_standardminute;
527         $this->on_standardsecond = $date->on_standardsecond;
528         $this->on_standardpartsecond = $date->on_standardpartsecond;
529
530         $this->ob_countleapseconds = $date->ob_countleapseconds;
531         $this->ob_invalidtime = $date->ob_invalidtime;
532
533         $this->tz = new Date_TimeZone($date->getTZID());
534
535         $this->getWeekdayAbbrnameLength = $date->getWeekdayAbbrnameLength;
536     }
537
538
539     // }}}
540     // {{{ __clone()
541
542     /**
543      * Copy values from another Date object
544      *
545      * Makes this Date a copy of another Date object.  For PHP5
546      * only.
547      *
548      * @return   void
549      * @access   public
550      * @see      Date::copy()
551      */
552     public function __clone()
553     {
554         // This line of code would be preferable, but will only
555         // compile in PHP5:
556         //
557         // $this->tz = clone $this->tz;
558
559         $this->tz = new Date_TimeZone($this->getTZID());
560     }
561
562
563     // }}}
564     // {{{ isNull()
565
566     /**
567      * Returns whether the object is null (i.e. no date has been set)
568      *
569      * If the object is set to an invalid date, then this function will
570      * still return 'false'.  To check whether the date is valid use
571      * either {@link Date::isValidDate()} (to check the day/month/year
572      * part of the object only) or {@link Date::isValidTime()} (to check
573      * the time, in addition to the day/month/year part).
574      *
575      * @return   bool
576      * @access   public
577      * @see      Date::setDate(), Date::isValidDate(), Date::isValidTime()
578      * @since    Method available since Release 1.5.0
579      */
580     public function isNull()
581     {
582         return is_null($this->year);
583     }
584
585
586     // }}}
587     // {{{ isValidDate()
588
589     /**
590      * Returns whether the date (i.e. day/month/year) is valid
591      *
592      * It is not possible to set the object to an invalid date using
593      * {@link Date::setDate()}, but it is possible to do so using the
594      * following functions:
595      *
596      *  - {@link Date::setYear()}
597      *  - {@link Date::setMonth()}
598      *  - {@link Date::setDay()}
599      *
600      * However you can prevent this possibility (by default) by setting
601      * {@link DATE_VALIDATE_DATE_BY_DEFAULT} to 'true', in which case
602      * these three functions will return an error if they specify an
603      * invalid date, and the object will be unmodified.
604      *
605      * Note that this function only checks the day/month/year part of
606      * the object.  Even if this is valid, it is still possible for the
607      * time to be invalid (see {@link DATE_CORRECTINVALIDTIME_DEFAULT}).
608      * To check the time as well, use {@link Date::isValidTime()}.
609      *
610      * @return   bool
611      * @access   public
612      * @see      Date::setDate(), Date::isNull(), Date::isValidTime()
613      * @since    Method available since Release 1.5.0
614      */
615     public function isValidDate()
616     {
617         return
618             !Date::isNull() &&
619             Date_Calc::isValidDate($this->year, $this->month, $this->day);
620     }
621
622
623     // }}}
624     // {{{ setDate()
625
626     /**
627      * Sets the date/time of the object based on the input date and format
628      *
629      * Accepts a string in three possible formats, and in this order of
630      * precedence:
631      *
632      *   - ISO 8601 date (see {@link http://en.wikipedia.org/wiki/ISO_8601})
633      *   - Time-Stamp (i.e. 'YYYYMMDDHHMMSS')
634      *   - Unix time-stamp (see {@link http://en.wikipedia.org/wiki/Unix_time})
635      *
636      * Note that if you want to pass a Unix time-stamp then you need to set
637      * the $format parameter to {@link DATE_FORMAT_UNIXTIME}, or else use the
638      * method {@link Date::setFromTime()}.
639      *
640      * The input string should be a date/time representation in one of the
641      * following general formats:
642      *
643      *   - <b><date>T<time><time-zone></b>
644      *   - <b><date> <time><time-zone></b> (non-ISO-standard)
645      *   - <b><date><time><time-zone></b> (non-ISO-standard)
646      *   - <b><date>T<time></b> i.e. without optional <time-zone> representation
647      *   - <b><date> <time></b>
648      *   - <b><date><time></b>
649      *   - <b><date></b> i.e. without optional <time> representation
650      *
651      * that is, the representation must be comprised of a <b><date></b> part,
652      * with an optional <b><time></b> part, which itself may include an optional
653      * <time-zone> part, each of which may consist of any one of the permitted
654      * formats detailed below.  The <b><date></b> and <b><time</b> representations
655      * should be divided with the time designator <b>T</b> according to the ISO 8601
656      * standard, although this method also permits representations divided by a
657      * space, or by no delimiter at all.
658      *
659      * The <b><date></b> representation should be in one of the following formats:
660      *
661      *   - <b>Calendar date</b>: <b>YYYY-MM-DD</b> (extended format) or
662      *                           <b>YYYYMMDD</b> (basic format), where [YYYY]
663      *                           indicates the four-digit year (0000-9999), [MM]
664      *                           indicates the month (01-12) and [DD] indicates the
665      *                           day of the month [01-31]
666      *   - <b>ISO week date</b>: <b>YYYY-Www-D</b> (extended format) or
667      *                           <b>YYYYWwwD</b> (basic format), where [YYYY]
668      *                           indicates the ISO year (slightly different from the
669      *                           calendar year (see below)), [Www] indicates the ISO
670      *                           week no prefixed by the letter 'W' (W01-W53) and
671      *                           [D] indicates the ISO week-day (1-7), beginning on
672      *                           Monday and ending on Sunday.  (Also see
673      *                           {@link http://en.wikipedia.org/wiki/ISO_week_date}.)
674      *   - <b>Ordinal date</b>: <b>YYYY-DDD</b> (extended format) or
675      *                          <b>YYYYDDD</b> (basic format), where [YYYY]
676      *                          indicates the four-digit year (0000-9999) and [DDD]
677      *                          indicates the day of the year (001-366)
678      *
679      * The <b><time></b> representation should be in one of the following formats:
680      *
681      *   - <b>hh:mm:ss</b> (extended format) or <b>hhmmss</b> (basic format)
682      *   - <b>hh:mm</b> (extended format) or <b>hhmm</b> (basic format)
683      *   - <b>hh</b> (extended format) or <b>hh</b> (basic format)
684      *
685      * where [hh] represents the hour (00-24), [mm] represents the minute (00-59)
686      * and [ss] represents the second (00-60)
687      *
688      * Format parameter should be one of the specified DATE_FORMAT_* constants:
689      *
690      *   - <b>{@link DATE_FORMAT_ISO}</b> - 'YYYY-MM-DD HH:MI:SS'
691      *   - <b>{@link DATE_FORMAT_ISO_BASIC}</b> - 'YYYYMMDDTHHMMSS(Z|(+/-)HHMM)?'
692      *   - <b>{@link DATE_FORMAT_ISO_EXTENDED}</b> - 'YYYY-MM-DDTHH:MM:SS(Z|(+/-)HH:MM)?'
693      *   - <b>{@link DATE_FORMAT_ISO_EXTENDED_MICROTIME}</b> - 'YYYY-MM-DDTHH:MM:SS(.S*)?(Z|(+/-)HH:MM)?'
694      *   - <b>{@link DATE_FORMAT_TIMESTAMP}</b> - 'YYYYMMDDHHMMSS'
695      *   - <b>{@link DATE_FORMAT_UNIXTIME}</b> - long integer of the no of seconds since
696      *                              the Unix Epoch
697      *                              (1st January 1970 00.00.00 GMT)
698      *
699      * @param string $date input date
700      * @param int $format optional format constant
701      *                                        (DATE_FORMAT_*) of the input date.
702      *                                        This parameter is not needed,
703      *                                        except to force the setting of the
704      *                                        date from a Unix time-stamp (for
705      *                                        which use
706      *                                        {@link DATE_FORMAT_UNIXTIME}).
707      *                                        (Defaults to
708      *                                        {@link DATE_FORMAT_ISO}.)
709      * @param bool $pb_repeatedhourdefault value to return if repeated
710      *                                        hour is specified (defaults
711      *                                        to false)
712      *
713      * @return   void
714      * @access   public
715      * @see      Date::isNull(), Date::isValidDate(), Date::isValidTime(),
716      *            Date::setFromTime()
717      */
718     public function setDate(
719         $date,
720         $format = DATE_FORMAT_ISO,
721         $pb_repeatedhourdefault = false
722     )
723     {
724         if ($format == DATE_FORMAT_UNIXTIME) {
725             if (is_numeric($date)) {
726                 // Assume Unix time-stamp:
727                 //
728                 $this->setFromTime((int)$date);
729             } else {
730                 return PEAR::raiseError("'$date' not valid Unix time-stamp");
731             }
732         } elseif (preg_match('/^([0-9]{4,4})-?(' .
733             '(0[1-9]|1[0-2])-?(0[1-9]|[12][0-9]|3[01])|' . // [mm]-[dd]
734             'W(0[1-9]|[1-4][0-9]|5[0-3])-?([1-7])|' .       // ISO week date
735             '(0(0[1-9]|[1-9][0-9])|[12][0-9]{2,2}|3([0-5][0-9]|6[1-6]))' . // [ddd]
736             ')([T\s]?' .
737             '([01][0-9]|2[0-3])(:?' .            // [hh]
738             '([0-5][0-9])(:?' .                  // [mm]
739             '([0-5][0-9]|60)([,.][0-9]+)?)?)?' . // [ss]
740             '(Z|[+\-][0-9]{2,2}(:?[0-5][0-9])?)?)?$/i',    // offset
741             $date, $regs)
742         ) {
743             if (substr($regs[2], 0, 1) == "W") {
744                 // ISO week date (YYYY-Www-D)
745                 //
746
747                 $hs_date = Date_Calc::isoWeekToDate(
748                     $regs[6],
749                     $regs[5],
750                     $regs[1],
751                     "%Y %m %d"
752                 );
753                 if (PEAR::isError($hs_date)) {
754                     return $hs_date;
755                 }
756
757                 list($hs_year, $hs_month, $hs_day) = explode(" ", $hs_date);
758             } elseif (strlen($regs[2]) == 3) {
759                 // ISO ordinal date (YYYY-DDD)
760                 //
761
762                 $hn_jd = Date_Calc::firstDayOfYear($regs[1]) + $regs[2] - 1;
763                 list($hs_year, $hs_month, $hs_day) =
764                     explode(" ", Date_Calc::daysToDate($hn_jd, "%Y %m %d"));
765             } else {
766                 // ISO calendar date (YYYY-MM-DD)
767                 //
768                 // DATE_FORMAT_ISO, ISO_BASIC, ISO_EXTENDED, and TIMESTAMP
769                 // These formats are extremely close to each other.  This regex
770                 // is very loose and accepts almost any butchered format you could
771                 // throw at it.  e.g. 2003-10-07 19:45:15 and 2003-10071945:15
772                 // are the same thing in the eyes of this regex, even though the
773                 // latter is not a valid ISO 8601 date.
774                 //
775
776                 $hs_year = $regs[1];
777                 $hs_month = $regs[3];
778                 $hs_day = $regs[4];
779
780                 if (!Date_Calc::isValidDate($hs_day, $hs_month, $hs_year)) {
781                     return PEAR::raiseError(
782                         "'" .
783                         Date_Calc::dateFormat(
784                             $hs_year,
785                             $hs_month,
786                             $hs_day,
787                             "%Y-%m-%d"
788                         ) .
789                         "' is invalid calendar date",
790                         DATE_ERROR_INVALIDDATE
791                     );
792                 }
793             }
794
795             if (isset($regs[17])) {
796                 if ($regs[17] == "Z") {
797                     $this->tz = new Date_TimeZone("UTC");
798                 } else {
799                     $this->tz = new Date_TimeZone("UTC" . $regs[17]);
800                 }
801             }
802
803             $this->setLocalTime(
804                 $hs_day,
805                 $hs_month,
806                 $hs_year,
807                 isset($regs[11]) && $regs[11] != "" ?
808                     $regs[11] : 0,
809                 isset($regs[13]) && $regs[13] != "" ?
810                     $regs[13] : 0,
811                 isset($regs[15]) && $regs[15] != "" ?
812                     $regs[15] : 0,
813                 isset($regs[16]) && $regs[16] != "" ?
814                     $regs[16] : 0.0,
815                 $pb_repeatedhourdefault
816             );
817         } else {
818             return PEAR::raiseError(
819                 "Date '$date' not in ISO 8601 format",
820                 DATE_ERROR_INVALIDDATEFORMAT
821             );
822         }
823     }
824
825
826     // }}}
827     // {{{ setNow()
828
829     /**
830      * Sets to local current time and time zone
831      *
832      * @param bool $pb_setmicrotime whether to set micro-time (defaults to the
833      *                               value of the constant
834      *                               {@link DATE_CAPTURE_MICROTIME_BY_DEFAULT})
835      *
836      * @return   void
837      * @access   public
838      * @since    Method available since Release 1.5.0
839      */
840     public function setNow($pb_setmicrotime = DATE_CAPTURE_MICROTIME_BY_DEFAULT)
841     {
842         $this->_setTZToDefault();
843
844         if ($pb_setmicrotime) {
845             $ha_unixtime = gettimeofday();
846         } else {
847             $ha_unixtime = array("sec" => time());
848         }
849
850         $this->setDate(date("Y-m-d H:i:s", $ha_unixtime["sec"]) .
851             (isset($ha_unixtime["usec"]) ?
852                 "." . sprintf("%06d", $ha_unixtime["usec"]) :
853                 ""));
854     }
855
856
857     // }}}
858     // {{{ round()
859
860     /**
861      * Rounds the date according to the specified precision (defaults
862      * to nearest day)
863      *
864      * The precision parameter must be one of the following constants:
865      *
866      *   - <b>{@link DATE_PRECISION_YEAR}</b>
867      *   - <b>{@link DATE_PRECISION_MONTH}</b>
868      *   - <b>{@link DATE_PRECISION_DAY}</b> (default)
869      *   - <b>{@link DATE_PRECISION_HOUR}</b>
870      *   - <b>{@link DATE_PRECISION_10MINUTES}</b>
871      *   - <b>{@link DATE_PRECISION_MINUTE}</b>
872      *   - <b>{@link DATE_PRECISION_10SECONDS}</b>
873      *   - <b>{@link DATE_PRECISION_SECOND}</b>
874      *
875      * The precision can also be specified as an integral offset from
876      * one of these constants, where the offset reflects a precision
877      * of 10 to the power of the offset greater than the constant.
878      * For example:
879      *
880      *   - <b>(DATE_PRECISION_YEAR - 1)</b> - rounds the date to the nearest 10 years
881      *   - <b>(DATE_PRECISION_YEAR - 3)</b> - rounds the date to the nearest 1000
882      *                                         years
883      *   - <b>(DATE_PRECISION_SECOND + 1)</b> - rounds the date to 1 decimal
884      *                                    point of a second
885      *   - <b>(DATE_PRECISION_SECOND + 3)</b> - rounds the date to 3 decimal
886      *                                    points of a second
887      *   - <b>(DATE_PRECISION_SECOND - 1)</b> - rounds the date to the nearest 10
888      *                                    seconds (thus it is equivalent to
889      *                                    <b>DATE_PRECISION_10SECONDS</b>)
890      *
891      * @param int $pn_precision a 'DATE_PRECISION_*' constant (defaults to
892      *                                     {@link DATE_PRECISION_DAY})
893      * @param bool $pb_correctinvalidtime whether to correct, by adding the
894      *                                     local Summer time offset, the rounded
895      *                                     time if it falls in the skipped hour
896      *                                     (defaults to
897      *                                     {@link DATE_CORRECTINVALIDTIME_DEFAULT})
898      *
899      * @return   void
900      * @access   public
901      * @since    Method available since Release 1.5.0
902      */
903     public function round(
904         $pn_precision = DATE_PRECISION_DAY,
905         $pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT
906     )
907     {
908         if ($pn_precision <= DATE_PRECISION_DAY) {
909             list($hn_year,
910                 $hn_month,
911                 $hn_day,
912                 $hn_hour,
913                 $hn_minute,
914                 $hn_secondraw) =
915                 Date_Calc::round(
916                     $pn_precision,
917                     $this->day,
918                     $this->month,
919                     $this->year,
920                     $this->hour,
921                     $this->minute,
922                     $this->partsecond == 0.0 ?
923                         $this->second :
924                         $this->second + $this->partsecond,
925                     $this->ob_countleapseconds
926                 );
927             if (is_float($hn_secondraw)) {
928                 $hn_second = intval($hn_secondraw);
929                 $hn_partsecond = $hn_secondraw - $hn_second;
930             } else {
931                 $hn_second = $hn_secondraw;
932                 $hn_partsecond = 0.0;
933             }
934
935             $this->setLocalTime(
936                 $hn_day,
937                 $hn_month,
938                 $hn_year,
939                 $hn_hour,
940                 $hn_minute,
941                 $hn_second,
942                 $hn_partsecond,
943                 true, // This is unlikely anyway, but the
944                 // day starts with the repeated hour
945                 // the first time around
946                 $pb_correctinvalidtime
947             );
948             return;
949         }
950
951         // ($pn_precision >= DATE_PRECISION_HOUR)
952         //
953         if ($this->tz->getDSTSavings() % 3600000 == 0 ||
954             ($this->tz->getDSTSavings() % 60000 == 0 &&
955                 $pn_precision >= DATE_PRECISION_MINUTE)
956         ) {
957             list($hn_year,
958                 $hn_month,
959                 $hn_day,
960                 $hn_hour,
961                 $hn_minute,
962                 $hn_secondraw) =
963                 Date_Calc::round(
964                     $pn_precision,
965                     $this->on_standardday,
966                     $this->on_standardmonth,
967                     $this->on_standardyear,
968                     $this->on_standardhour,
969                     $this->on_standardminute,
970                     $this->on_standardpartsecond == 0.0 ?
971                         $this->on_standardsecond :
972                         $this->on_standardsecond +
973                         $this->on_standardpartsecond,
974                     $this->ob_countleapseconds
975                 );
976             if (is_float($hn_secondraw)) {
977                 $hn_second = intval($hn_secondraw);
978                 $hn_partsecond = $hn_secondraw - $hn_second;
979             } else {
980                 $hn_second = $hn_secondraw;
981                 $hn_partsecond = 0.0;
982             }
983
984             $this->setStandardTime(
985                 $hn_day,
986                 $hn_month,
987                 $hn_year,
988                 $hn_hour,
989                 $hn_minute,
990                 $hn_second,
991                 $hn_partsecond
992             );
993             return;
994         }
995
996         // Very unlikely anyway (as I write, the only time zone like this
997         // is Lord Howe Island in Australia (offset of half an hour)):
998         //
999         // (This algorithm could be better)
1000         //
1001         list($hn_year,
1002             $hn_month,
1003             $hn_day,
1004             $hn_hour,
1005             $hn_minute,
1006             $hn_secondraw) =
1007             Date_Calc::round(
1008                 $pn_precision,
1009                 $this->day,
1010                 $this->month,
1011                 $this->year,
1012                 $this->hour,
1013                 $this->minute,
1014                 $this->partsecond == 0.0 ?
1015                     $this->second :
1016                     $this->second + $this->partsecond,
1017                 $this->ob_countleapseconds
1018             );
1019         if (is_float($hn_secondraw)) {
1020             $hn_second = intval($hn_secondraw);
1021             $hn_partsecond = $hn_secondraw - $hn_second;
1022         } else {
1023             $hn_second = $hn_secondraw;
1024             $hn_partsecond = 0.0;
1025         }
1026
1027         $this->setLocalTime(
1028             $hn_day,
1029             $hn_month,
1030             $hn_year,
1031             $hn_hour,
1032             $hn_minute,
1033             $hn_second,
1034             $hn_partsecond,
1035             false, // This will be right half the time
1036             $pb_correctinvalidtime
1037         );   // This will be right
1038         // some of the time
1039         // (depends on Summer
1040         // time offset)
1041     }
1042
1043
1044     // }}}
1045     // {{{ roundSeconds()
1046
1047     /**
1048      * Rounds seconds up or down to the nearest specified unit
1049      *
1050      * N.B. this function is equivalent to calling:
1051      *
1052      * <code>$date_object->round(DATE_PRECISION_SECOND + $pn_precision);</code>
1053      *
1054      * @param int $pn_precision number of digits after the decimal point
1055      * @param bool $pb_correctinvalidtime whether to correct, by adding the
1056      *                                     local Summer time offset, the rounded
1057      *                                     time if it falls in the skipped hour
1058      *                                     (defaults to
1059      *                                     {@link DATE_CORRECTINVALIDTIME_DEFAULT})
1060      *
1061      * @return   void
1062      * @access   public
1063      * @since    Method available since Release 1.5.0
1064      */
1065     public function roundSeconds(
1066         $pn_precision = 0,
1067         $pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT
1068     )
1069     {
1070         $this->round(
1071             DATE_PRECISION_SECOND + $pn_precision,
1072             $pb_correctinvalidtime
1073         );
1074     }
1075
1076
1077     // }}}
1078     // {{{ trunc()
1079
1080     /**
1081      * Truncates the date according to the specified precision (by
1082      * default, it truncates the time part of the date)
1083      *
1084      * The precision parameter must be one of the following constants:
1085      *
1086      *   - {@link DATE_PRECISION_YEAR}
1087      *   - {@link DATE_PRECISION_MONTH}
1088      *   - {@link DATE_PRECISION_DAY} (default)
1089      *   - {@link DATE_PRECISION_HOUR}
1090      *   - {@link DATE_PRECISION_10MINUTES}
1091      *   - {@link DATE_PRECISION_MINUTE}
1092      *   - {@link DATE_PRECISION_10SECONDS}
1093      *   - {@link DATE_PRECISION_SECOND}
1094      *
1095      * The precision can also be specified as an integral offset from
1096      * one of these constants, where the offset reflects a precision
1097      * of 10 to the power of the offset greater than the constant.
1098      * For example:
1099      *
1100      *   - <b>DATE_PRECISION_YEAR</b> - truncates the month, day and time
1101      *                            part of the year
1102      *   - <b>(DATE_PRECISION_YEAR - 1)</b> - truncates the unit part of the
1103      *                                  year, e.g. 1987 becomes 1980
1104      *   - <b>(DATE_PRECISION_YEAR - 3)</b> - truncates the hundreds part of the
1105      *                                  year, e.g. 1987 becomes 1000
1106      *   - <b>(DATE_PRECISION_SECOND + 1)</b> - truncates the part of the second
1107      *                                    less than 0.1 of a second, e.g.
1108      *                                    3.26301 becomes 3.2 seconds
1109      *   - <b>(DATE_PRECISION_SECOND + 3)</b> - truncates the part of the second
1110      *                                    less than 0.001 of a second, e.g.
1111      *                                    3.26301 becomes 3.263 seconds
1112      *   - <b>(DATE_PRECISION_SECOND - 1)</b> - truncates the unit part of the
1113      *                                    seconds (thus it is equivalent to
1114      *                                    <b>DATE_PRECISION_10SECONDS</b>)
1115      *
1116      * @param int $pn_precision a 'DATE_PRECISION_*' constant (defaults
1117      *                                     to {@link DATE_PRECISION_DAY})
1118      * @param bool $pb_correctinvalidtime whether to correct, by adding the
1119      *                                     local Summer time offset, the
1120      *                                     truncated time if it falls in the
1121      *                                     skipped hour (defaults to
1122      *                                     {@link DATE_CORRECTINVALIDTIME_DEFAULT})
1123      *
1124      * @return   void
1125      * @access   public
1126      * @since    Method available since Release 1.5.0
1127      */
1128     public function trunc(
1129         $pn_precision = DATE_PRECISION_DAY,
1130         $pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT
1131     )
1132     {
1133         if ($pn_precision <= DATE_PRECISION_DAY) {
1134             if ($pn_precision <= DATE_PRECISION_YEAR) {
1135                 $hn_month = 0;
1136                 $hn_day = 0;
1137                 $hn_hour = 0;
1138                 $hn_minute = 0;
1139                 $hn_second = 0;
1140                 $hn_partsecond = 0.0;
1141
1142                 $hn_invprecision = DATE_PRECISION_YEAR - $pn_precision;
1143                 if ($hn_invprecision > 0) {
1144                     $hn_year = intval($this->year / pow(10, $hn_invprecision)) *
1145                         pow(10, $hn_invprecision);
1146                     //
1147                     // (Conversion to int necessary for PHP <= 4.0.6)
1148                 } else {
1149                     $hn_year = $this->year;
1150                 }
1151             } elseif ($pn_precision == DATE_PRECISION_MONTH) {
1152                 $hn_year = $this->year;
1153                 $hn_month = $this->month;
1154                 $hn_day = 0;
1155                 $hn_hour = 0;
1156                 $hn_minute = 0;
1157                 $hn_second = 0;
1158                 $hn_partsecond = 0.0;
1159             } elseif ($pn_precision == DATE_PRECISION_DAY) {
1160                 $hn_year = $this->year;
1161                 $hn_month = $this->month;
1162                 $hn_day = $this->day;
1163                 $hn_hour = 0;
1164                 $hn_minute = 0;
1165                 $hn_second = 0;
1166                 $hn_partsecond = 0.0;
1167             }
1168
1169             $this->setLocalTime(
1170                 $hn_day,
1171                 $hn_month,
1172                 $hn_year,
1173                 $hn_hour,
1174                 $hn_minute,
1175                 $hn_second,
1176                 $hn_partsecond,
1177                 true, // This is unlikely anyway, but the
1178                 // day starts with the repeated
1179                 // hour the first time around
1180                 $pb_correctinvalidtime
1181             );
1182             return;
1183         }
1184
1185         // Precision is at least equal to DATE_PRECISION_HOUR
1186         //
1187         if ($pn_precision == DATE_PRECISION_HOUR) {
1188             $this->addSeconds($this->partsecond == 0.0 ?
1189                 -$this->second :
1190                 -$this->second - $this->partsecond);
1191             //
1192             // (leap seconds irrelevant)
1193
1194             $this->addMinutes(-$this->minute);
1195         } elseif ($pn_precision <= DATE_PRECISION_MINUTE) {
1196             if ($pn_precision == DATE_PRECISION_10MINUTES) {
1197                 $this->addMinutes(-$this->minute % 10);
1198             }
1199
1200             $this->addSeconds($this->partsecond == 0.0 ?
1201                 -$this->second :
1202                 -$this->second - $this->partsecond);
1203             //
1204             // (leap seconds irrelevant)
1205         } elseif ($pn_precision == DATE_PRECISION_10SECONDS) {
1206             $this->addSeconds($this->partsecond == 0.0 ?
1207                 -$this->second % 10 :
1208                 (-$this->second % 10) - $this->partsecond);
1209             //
1210             // (leap seconds irrelevant)
1211         } else {
1212             // Assume Summer time offset cannot be composed of part-seconds:
1213             //
1214             $hn_precision = $pn_precision - DATE_PRECISION_SECOND;
1215             $hn_partsecond = intval($this->on_standardpartsecond *
1216                     pow(10, $hn_precision)) /
1217                 pow(10, $hn_precision);
1218             $this->setStandardTime(
1219                 $this->on_standardday,
1220                 $this->on_standardmonth,
1221                 $this->on_standardyear,
1222                 $this->on_standardhour,
1223                 $this->on_standardminute,
1224                 $this->on_standardsecond,
1225                 $hn_partsecond
1226             );
1227         }
1228     }
1229
1230
1231     // }}}
1232     // {{{ truncSeconds()
1233
1234     /**
1235      * Truncates seconds according to the specified precision
1236      *
1237      * N.B. this function is equivalent to calling:
1238      *
1239      * <code>
1240      *   $date_object->trunc(DATE_PRECISION_SECOND + $pn_precision);
1241      * </code>
1242      *
1243      * @param int $pn_precision number of digits after the decimal point
1244      * @param bool $pb_correctinvalidtime whether to correct, by adding the
1245      *                                     local Summer time offset, the
1246      *                                     truncated time if it falls in the
1247      *                                     skipped hour (defaults to
1248      *                                     {@link DATE_CORRECTINVALIDTIME_DEFAULT})
1249      *
1250      * @return   void
1251      * @access   public
1252      * @since    Method available since Release 1.5.0
1253      */
1254     public function truncSeconds(
1255         $pn_precision = 0,
1256         $pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT
1257     )
1258     {
1259         $this->trunc(
1260             DATE_PRECISION_SECOND + $pn_precision,
1261             $pb_correctinvalidtime
1262         );
1263     }
1264
1265
1266     // }}}
1267     // {{{ getDate()
1268
1269     /**
1270      * Gets a string (or other) representation of this date
1271      *
1272      * Returns a date in the format specified by the DATE_FORMAT_* constants,
1273      * which should be one of the following:
1274      *
1275      *   - {@link DATE_FORMAT_ISO} (default)
1276      *   - {@link DATE_FORMAT_ISO_BASIC}
1277      *   - {@link DATE_FORMAT_ISO_EXTENDED}
1278      *   - {@link DATE_FORMAT_ISO_EXTENDED_MICROTIME}
1279      *   - {@link DATE_FORMAT_TIMESTAMP}
1280      *   - {@link DATE_FORMAT_UNIXTIME}
1281      *
1282      * @param int $format format constant (DATE_FORMAT_*) of the output date
1283      *
1284      * @return   string     the date in the requested format (defaults to
1285      *                       {@link DATE_FORMAT_ISO})
1286      * @access   public
1287      */
1288     public function getDate($format = DATE_FORMAT_ISO)
1289     {
1290         $ret;
1291         switch ($format) {
1292             case DATE_FORMAT_ISO:
1293                 $ret = $this->formatLikeStrftime("%Y-%m-%d %T");
1294                 break;
1295             case DATE_FORMAT_ISO_BASIC:
1296                 $format = "%Y%m%dT%H%M%S";
1297                 if ($this->getTZID() == 'UTC') {
1298                     $format .= "Z";
1299                 }
1300                 $ret = $this->formatLikeStrftime($format);
1301                 break;
1302             case DATE_FORMAT_ISO_EXTENDED:
1303                 $format = "%Y-%m-%dT%H:%M:%S";
1304                 if ($this->getTZID() == 'UTC') {
1305                     $format .= "Z";
1306                 }
1307                 $ret = $this->formatLikeStrftime($format);
1308                 break;
1309             case DATE_FORMAT_ISO_EXTENDED_MICROTIME:
1310                 $format = "%Y-%m-%dT%H:%M:%s";
1311                 if ($this->getTZID() == 'UTC') {
1312                     $format .= "Z";
1313                 }
1314                 $ret = $this->formatLikeStrftime($format);
1315                 break;
1316             case DATE_FORMAT_TIMESTAMP:
1317                 $ret = $this->formatLikeStrftime("%Y%m%d%H%M%S");
1318                 break;
1319             case DATE_FORMAT_UNIXTIME:
1320                 $ret = $this->getTime();
1321                 if (!PEAR::isError($ret)) {
1322                     $ret = (string)$ret;
1323                 }
1324                 break;
1325         }
1326
1327         return $ret;
1328     }
1329
1330
1331     // }}}
1332     // {{{ format()
1333
1334     /**
1335      * Formats the date according to the specified formatting code string
1336      *
1337      * This function is an alias for the method specified by the constant
1338      * {@link DATE_FORMAT_METHOD} (which defaults to 'formatLikeStrftime'
1339      * for backwards-compatibility).
1340      *
1341      * @return   string     date/time in given format
1342      * @access   public
1343      * @see      Date::formatLikeStrftime(), Date::formatLikeDate(),
1344      *            Date::formatLikeSQL()
1345      */
1346     public function format()
1347     {
1348         $ha_args = func_get_args();
1349         return call_user_func_array(
1350             array(&$this, DATE_FORMAT_METHOD),
1351             $ha_args
1352         );
1353     }
1354
1355
1356     // }}}
1357     // {{{ formatLikeStrftime()
1358
1359     /**
1360      * Formats the date according to the specified formatting code string,
1361      * based on {@link http://www.php.net/strftime strftime()}
1362      *
1363      * Formats the date in the given format, much like
1364      * strftime().  Most strftime() options are supported.
1365      *
1366      *
1367      * Formatting options:
1368      *
1369      *   - <b>%a</b> - abbreviated weekday name (Sun, Mon, Tue)
1370      *   - <b>%A</b> - full weekday name (Sunday, Monday, Tuesday)
1371      *   - <b>%b</b> - abbreviated month name (Jan, Feb, Mar)
1372      *   - <b>%B</b> - full month name (January, February, March)
1373      *   - <b>%C</b> - century number (the year divided by 100 and truncated
1374      *                     to an integer, range 00 to 99)
1375      *   - <b>%d</b> - day of month (range 00 to 31)
1376      *   - <b>%D</b> - equivalent to '<b>%m/%d/%y</b>'
1377      *   - <b>%e</b> - day of month without leading noughts (range 0 to 31)
1378      *   - <b>%E</b> - {@link http://en.wikipedia.org/wiki/Julian_day Julian day} -
1379      *                 no of days since Monday, 24th November, 4714 B.C. (in
1380      *                 the proleptic Gregorian calendar)
1381      *   - <b>%g</b> - like '<b>%G</b>', but without the century
1382      *   - <b>%G</b> - the 4-digit year corresponding to the ISO week
1383      *                     number (see '<b>%V</b>'). This has the same
1384      *                     format and value as '<b>%Y</b>', except that if
1385      *                     the ISO week number belongs to the previous or
1386      *                     next year, that year is used instead.
1387      *   - <b>%h</b> - hour as decimal number without leading noughts (0
1388      *                     to 23)
1389      *   - <b>%H</b> - hour as decimal number (00 to 23)
1390      *   - <b>%i</b> - hour as decimal number on 12-hour clock without
1391      *                     leading noughts (1 to 12)
1392      *   - <b>%I</b> - hour as decimal number on 12-hour clock (01 to 12)
1393      *   - <b>%j</b> - day of year (range 001 to 366)
1394      *   - <b>%m</b> - month as decimal number (range 01 to 12)
1395      *   - <b>%M</b> - minute as a decimal number (00 to 59)
1396      *   - <b>%n</b> - newline character ("\n")
1397      *   - <b>%o</b> - raw timezone offset expressed as '+/-HH:MM'
1398      *   - <b>%O</b> - dst-corrected timezone offset expressed as '+/-HH:MM'
1399      *   - <b>%p</b> - either 'am' or 'pm' depending on the time
1400      *   - <b>%P</b> - either 'AM' or 'PM' depending on the time
1401      *   - <b>%r</b> - time in am/pm notation; equivalent to
1402      *                  '<b>%I:%M:%S %p</b>'
1403      *   - <b>%R</b> - time in 24-hour notation; equivalent to
1404      *                  '<b>%H:%M</b>'
1405      *   - <b>%s</b> - seconds including the micro-time (the decimal
1406      *                     representation less than one second to six
1407      *                     decimal places
1408      *   - <b>%S</b> - seconds as a decimal number (00 to 59)
1409      *   - <b>%t</b> - tab character ("\t")
1410      *   - <b>%T</b> - current time; equivalent to '<b>%H:%M:%S</b>'
1411      *   - <b>%u</b> - day of week as decimal (1 to 7; where 1 = Monday)
1412      *   - <b>%U</b> - week number of the current year as a decimal
1413      *                     number, starting with the first Sunday as the first
1414      *                     day of the first week (i.e. the first full week of
1415      *                     the year, and the week that contains 7th January)
1416      *                     (00 to 53)
1417      *   - <b>%V</b> - the {@link http://en.wikipedia.org/wiki/ISO_week_date ISO 8601:1988}
1418      *                 week number of the current year
1419      *                 as a decimal number, range 01 to 53, where week 1
1420      *                 is the first week that has at least 4 days in the
1421      *                 current year, and with Monday as the first day of
1422      *                 the week.  (Use '<b>%G</b>' or '<b>%g</b>' for the
1423      *                 year component that corresponds to the week number
1424      *                 for the specified timestamp.)
1425      *   - <b>%w</b> - day of week as decimal (0 to 6; where 0 = Sunday)
1426      *   - <b>%W</b> - week number of the current year as a decimal
1427      *                     number, starting with the first Monday as the first
1428      *                     day of the first week (i.e. the first full week of
1429      *                     the year, and the week that contains 7th January)
1430      *                     (00 to 53)
1431      *   - <b>%y</b> - year as decimal (range 00 to 99)
1432      *   - <b>%Y</b> - year as decimal including century (range 0000 to
1433      *                     9999)
1434      *   - <b>%Z</b> - Abbreviated form of time zone name, e.g. 'GMT', or
1435      *                     the abbreviation for Summer time if the date falls
1436      *                     in Summer time, e.g. 'BST'.
1437      *   - <b>%%</b> - literal '%'
1438      *
1439      *
1440      * The following codes render a different output to that of
1441      *  {@link http://www.php.net/strftime strftime()}:
1442      *
1443      *   - <b>%e</b> - in 'strftime()' a single digit is preceded by a space
1444      *   - <b>%h</b> - in 'strftime()' is equivalent to '<b>%b</b>'
1445      *   - <b>%U</b> - '<b>%U</b>' and '<b>%W</b>' are different in
1446      *                  'strftime()' in that if week 1 does not start on 1st
1447      *                  January, '00' is returned, whereas this function
1448      *                  returns '53', that is, the week is counted as the
1449      *                  last of the previous year.
1450      *   - <b>%W</b>
1451      *
1452      * @param string $format the format string for returned date/time
1453      *
1454      * @return   string     date/time in given format
1455      * @access   public
1456      * @see      Date::format(), Date::formatLikeDate(), Date::formatLikeSQL()
1457      * @since    Method available since Release 1.5.1
1458      */
1459     public function formatLikeStrftime($format)
1460     {
1461         $output = "";
1462
1463         $hn_isoyear = null;
1464         $hn_isoweek = null;
1465         $hn_isoday = null;
1466
1467         for ($strpos = 0; $strpos < strlen($format); $strpos++) {
1468             $char = substr($format, $strpos, 1);
1469             if ($char == "%") {
1470                 $nextchar = substr($format, $strpos + 1, 1);
1471                 switch ($nextchar) {
1472                     case "a":
1473                         $output .= Date_Calc::getWeekdayAbbrname(
1474                             $this->day,
1475                             $this->month,
1476                             $this->year,
1477                             $this->getWeekdayAbbrnameLength
1478                         );
1479                         break;
1480                     case "A":
1481                         $output .= Date_Calc::getWeekdayFullname(
1482                             $this->day,
1483                             $this->month,
1484                             $this->year
1485                         );
1486                         break;
1487                     case "b":
1488                         $output .= Date_Calc::getMonthAbbrname($this->month);
1489                         break;
1490                     case "B":
1491                         $output .= Date_Calc::getMonthFullname($this->month);
1492                         break;
1493                     case "C":
1494                         $output .= sprintf("%02d", intval($this->year / 100));
1495                         break;
1496                     case "d":
1497                         $output .= sprintf("%02d", $this->day);
1498                         break;
1499                     case "D":
1500                         $output .= sprintf(
1501                             "%02d/%02d/%02d",
1502                             $this->month,
1503                             $this->day,
1504                             $this->year
1505                         );
1506                         break;
1507                     case "e":
1508                         $output .= $this->day;
1509                         break;
1510                     case "E":
1511                         $output .= Date_Calc::dateToDays(
1512                             $this->day,
1513                             $this->month,
1514                             $this->year
1515                         );
1516                         break;
1517                     case "g":
1518                         if (is_null($hn_isoyear)) {
1519                             list($hn_isoyear, $hn_isoweek, $hn_isoday) =
1520                                 Date_Calc::isoWeekDate(
1521                                     $this->day,
1522                                     $this->month,
1523                                     $this->year
1524                                 );
1525                         }
1526
1527                         $output .= sprintf("%02d", $hn_isoyear % 100);
1528                         break;
1529                     case "G":
1530                         if (is_null($hn_isoyear)) {
1531                             list($hn_isoyear, $hn_isoweek, $hn_isoday) =
1532                                 Date_Calc::isoWeekDate(
1533                                     $this->day,
1534                                     $this->month,
1535                                     $this->year
1536                                 );
1537                         }
1538
1539                         $output .= sprintf("%04d", $hn_isoyear);
1540                         break;
1541                     case 'h':
1542                         if ($this->ob_invalidtime) {
1543                             return $this->_getErrorInvalidTime();
1544                         }
1545                         $output .= sprintf("%d", $this->hour);
1546                         break;
1547                     case "H":
1548                         if ($this->ob_invalidtime) {
1549                             return $this->_getErrorInvalidTime();
1550                         }
1551                         $output .= sprintf("%02d", $this->hour);
1552                         break;
1553                     case "i":
1554                     case "I":
1555                         if ($this->ob_invalidtime) {
1556                             return $this->_getErrorInvalidTime();
1557                         }
1558                         $hour = $this->hour + 1 > 12 ?
1559                             $this->hour - 12 :
1560                             $this->hour;
1561                         $output .= $hour == 0 ?
1562                             12 :
1563                             ($nextchar == "i" ?
1564                                 $hour :
1565                                 sprintf('%02d', $hour));
1566                         break;
1567                     case "j":
1568                         $output .= sprintf(
1569                             "%03d",
1570                             Date_Calc::dayOfYear(
1571                                 $this->day,
1572                                 $this->month,
1573                                 $this->year
1574                             )
1575                         );
1576                         break;
1577                     case "m":
1578                         $output .= sprintf("%02d", $this->month);
1579                         break;
1580                     case "M":
1581                         $output .= sprintf("%02d", $this->minute);
1582                         break;
1583                     case "n":
1584                         $output .= "\n";
1585                         break;
1586                     case "O":
1587                         if ($this->ob_invalidtime) {
1588                             return $this->_getErrorInvalidTime();
1589                         }
1590                         $offms = $this->getTZOffset();
1591                         $direction = $offms >= 0 ? "+" : "-";
1592                         $offmins = abs($offms) / 1000 / 60;
1593                         $hours = $offmins / 60;
1594                         $minutes = $offmins % 60;
1595
1596                         $output .= sprintf("%s%02d:%02d", $direction, $hours, $minutes);
1597                         break;
1598                     case "o":
1599                         $offms = $this->tz->getRawOffset($this);
1600                         $direction = $offms >= 0 ? "+" : "-";
1601                         $offmins = abs($offms) / 1000 / 60;
1602                         $hours = $offmins / 60;
1603                         $minutes = $offmins % 60;
1604
1605                         $output .= sprintf("%s%02d:%02d", $direction, $hours, $minutes);
1606                         break;
1607                     case "p":
1608                         if ($this->ob_invalidtime) {
1609                             return $this->_getErrorInvalidTime();
1610                         }
1611                         $output .= $this->hour >= 12 ? "pm" : "am";
1612                         break;
1613                     case "P":
1614                         if ($this->ob_invalidtime) {
1615                             return $this->_getErrorInvalidTime();
1616                         }
1617                         $output .= $this->hour >= 12 ? "PM" : "AM";
1618                         break;
1619                     case "r":
1620                         if ($this->ob_invalidtime) {
1621                             return $this->_getErrorInvalidTime();
1622                         }
1623                         $hour = $this->hour + 1 > 12 ?
1624                             $this->hour - 12 :
1625                             $this->hour;
1626                         $output .= sprintf(
1627                             "%02d:%02d:%02d %s",
1628                             $hour == 0 ? 12 : $hour,
1629                             $this->minute,
1630                             $this->second,
1631                             $this->hour >= 12 ? "PM" : "AM"
1632                         );
1633                         break;
1634                     case "R":
1635                         if ($this->ob_invalidtime) {
1636                             return $this->_getErrorInvalidTime();
1637                         }
1638                         $output .= sprintf("%02d:%02d", $this->hour, $this->minute);
1639                         break;
1640                     case "s":
1641                         $output .= str_replace(
1642                             ',',
1643                             '.',
1644                             sprintf(
1645                                 "%09f",
1646                                 (float)((float)$this->second +
1647                                     $this->partsecond)
1648                             )
1649                         );
1650                         break;
1651                     case "S":
1652                         $output .= sprintf("%02d", $this->second);
1653                         break;
1654                     case "t":
1655                         $output .= "\t";
1656                         break;
1657                     case "T":
1658                         if ($this->ob_invalidtime) {
1659                             return $this->_getErrorInvalidTime();
1660                         }
1661                         $output .= sprintf(
1662                             "%02d:%02d:%02d",
1663                             $this->hour,
1664                             $this->minute,
1665                             $this->second
1666                         );
1667                         break;
1668                     case "u":
1669                         $hn_dayofweek = $this->getDayOfWeek();
1670                         $output .= $hn_dayofweek == 0 ? 7 : $hn_dayofweek;
1671                         break;
1672                     case "U":
1673                         $ha_week = Date_Calc::weekOfYear7th(
1674                             $this->day,
1675                             $this->month,
1676                             $this->year,
1677                             0
1678                         );
1679                         $output .= sprintf("%02d", $ha_week[1]);
1680                         break;
1681                     case "V":
1682                         if (is_null($hn_isoyear)) {
1683                             list($hn_isoyear, $hn_isoweek, $hn_isoday) =
1684                                 Date_Calc::isoWeekDate(
1685                                     $this->day,
1686                                     $this->month,
1687                                     $this->year
1688                                 );
1689                         }
1690
1691                         $output .= $hn_isoweek;
1692                         break;
1693                     case "w":
1694                         $output .= $this->getDayOfWeek();
1695                         break;
1696                     case "W":
1697                         $ha_week = Date_Calc::weekOfYear7th(
1698                             $this->day,
1699                             $this->month,
1700                             $this->year,
1701                             1
1702                         );
1703                         $output .= sprintf("%02d", $ha_week[1]);
1704                         break;
1705                     case 'y':
1706                         $output .= sprintf(
1707                             '%0' .
1708                             ($this->year < 0 ? '3' : '2') .
1709                             'd',
1710                             $this->year % 100
1711                         );
1712                         break;
1713                     case "Y":
1714                         $output .= sprintf(
1715                             '%0' .
1716                             ($this->year < 0 ? '5' : '4') .
1717                             'd',
1718                             $this->year
1719                         );
1720                         break;
1721                     case "Z":
1722                         if ($this->ob_invalidtime) {
1723                             return $this->_getErrorInvalidTime();
1724                         }
1725                         $output .= $this->getTZShortName();
1726                         break;
1727                     case "%":
1728                         $output .= "%";
1729                         break;
1730                     default:
1731                         $output .= $char . $nextchar;
1732                 }
1733                 $strpos++;
1734             } else {
1735                 $output .= $char;
1736             }
1737         }
1738         return $output;
1739     }
1740
1741
1742     // }}}
1743     // {{{ _getOrdinalSuffix()
1744
1745     /**
1746      * Returns appropriate ordinal suffix (i.e. 'th', 'st', 'nd' or 'rd')
1747      *
1748      * @param int $pn_num number with which to determine suffix
1749      * @param bool $pb_uppercase boolean specifying if the suffix should be
1750      *                            capitalized
1751      *
1752      * @return   string
1753      * @access   private
1754      * @since    Method available since Release 1.5.0
1755      */
1756     public function _getOrdinalSuffix($pn_num, $pb_uppercase = true)
1757     {
1758         switch (($pn_numabs = abs($pn_num)) % 100) {
1759             case 11:
1760             case 12:
1761             case 13:
1762                 $hs_suffix = "th";
1763                 break;
1764             default:
1765                 switch ($pn_numabs % 10) {
1766                     case 1:
1767                         $hs_suffix = "st";
1768                         break;
1769                     case 2:
1770                         $hs_suffix = "nd";
1771                         break;
1772                     case 3:
1773                         $hs_suffix = "rd";
1774                         break;
1775                     default:
1776                         $hs_suffix = "th";
1777                 }
1778         }
1779
1780         return $pb_uppercase ? strtoupper($hs_suffix) : $hs_suffix;
1781     }
1782
1783
1784     // }}}
1785     // {{{ _spellNumber()
1786
1787     /**
1788      * Converts a number to its word representation
1789      *
1790      * Private helper function, particularly for {@link Date::formatLikeSQL()}.
1791      * N.B. The second argument is the 'SP' code which can be specified in the
1792      * format string for 'formatLikeSQL()' and is interpreted as follows:
1793      *
1794      *  - <b>SP</b> - returns upper-case spelling, e.g. 'FOUR HUNDRED'
1795      *  - <b>Sp</b> - returns spelling with first character of each word
1796      *         capitalized, e.g. 'Four Hundred'
1797      *  - <b>sp</b> - returns lower-case spelling, e.g. 'four hundred'
1798      *
1799      * @param int $pn_num number to be converted to words
1800      * @param bool $pb_ordinal boolean specifying if the number should
1801      *                                   be ordinal
1802      * @param string $ps_capitalization string for specifying capitalization
1803      *                                   options
1804      * @param string $ps_locale language name abbreviation used for
1805      *                                   formatting numbers as spelled-out words
1806      *
1807      * @return   string
1808      * @access   private
1809      * @since    Method available since Release 1.5.0
1810      */
1811     public function _spellNumber(
1812         $pn_num,
1813         $pb_ordinal = false,
1814         $ps_capitalization = "SP",
1815         $ps_locale = "en_GB"
1816     )
1817     {
1818         include_once "Numbers/Words.php";
1819         $hs_words = Numbers_Words::toWords($pn_num, $ps_locale);
1820         if (Pear::isError($hs_words)) {
1821             return $hs_words;
1822         }
1823
1824         if ($pb_ordinal && substr($ps_locale, 0, 2) == "en") {
1825             if (($pn_rem = ($pn_numabs = abs($pn_num)) % 100) == 12) {
1826                 $hs_words = substr($hs_words, 0, -2) . "fth";
1827             } elseif ($pn_rem >= 11 && $pn_rem <= 15) {
1828                 $hs_words .= "th";
1829             } else {
1830                 switch ($pn_numabs % 10) {
1831                     case 1:
1832                         $hs_words = substr($hs_words, 0, -3) . "first";
1833                         break;
1834                     case 2:
1835                         $hs_words = substr($hs_words, 0, -3) . "second";
1836                         break;
1837                     case 3:
1838                         $hs_words = substr($hs_words, 0, -3) . "ird";
1839                         break;
1840                     case 5:
1841                         $hs_words = substr($hs_words, 0, -2) . "fth";
1842                         break;
1843                     default:
1844                         switch (substr($hs_words, -1)) {
1845                             case "e":
1846                                 $hs_words = substr($hs_words, 0, -1) . "th";
1847                                 break;
1848                             case "t":
1849                                 $hs_words .= "h";
1850                                 break;
1851                             case "y":
1852                                 $hs_words = substr($hs_words, 0, -1) . "ieth";
1853                                 break;
1854                             default:
1855                                 $hs_words .= "th";
1856                         }
1857                 }
1858             }
1859         }
1860
1861         if (($hs_char = substr($ps_capitalization, 0, 1)) ==
1862             strtolower($hs_char)) {
1863             $hb_upper = false;
1864             $hs_words = strtolower($hs_words);
1865         } elseif (($hs_char = substr($ps_capitalization, 1, 1)) ==
1866             strtolower($hs_char)) {
1867             $hb_upper = false;
1868             $hs_words = ucwords($hs_words);
1869         } else {
1870             $hb_upper = true;
1871             $hs_words = strtoupper($hs_words);
1872         }
1873
1874         return $hs_words;
1875     }
1876
1877
1878     // }}}
1879     // {{{ _formatNumber()
1880
1881     /**
1882      * Formats a number according to the specified format string
1883      *
1884      * Private helper function, for {@link Date::formatLikeSQL()}, which
1885      * interprets the codes '<b>SP</b>' and '<b>TH</b>' and the combination
1886      * of the two as follows:
1887      *
1888      *   - <b>TH</b> - Ordinal number
1889      *   - <b>SP</b> - Spelled cardinal number
1890      *   - <b>SPTH</b> - Spelled ordinal number (combination of '<b>SP</b>'
1891      *                   and '<b>TH</b>' in any order)
1892      *   - <b>THSP</b>
1893      *
1894      * Code '<b>SP</b>' can have the following three variations (which
1895      * can also be used in combination with '<b>TH</b>'):
1896      *
1897      *   - <b>SP</b> - returns upper-case spelling, e.g. 'FOUR HUNDRED'
1898      *   - <b>Sp</b> - returns spelling with first character of each word
1899      *                 capitalized, e.g. 'Four Hundred'
1900      *   - <b>sp</b> - returns lower-case spelling, e.g. 'four hundred'
1901      *
1902      * Code '<b>TH</b>' can have the following two variations (although in
1903      * combination with code '<b>SP</b>', the case specification of
1904      * '<b>SP</b>' takes precedence):
1905      *
1906      *   - <b>TH</b> - returns upper-case ordinal suffix, e.g. 400TH
1907      *   - <b>th</b> - returns lower-case ordinal suffix, e.g. 400th
1908      *
1909      * N.B. The format string is passed by reference, in order to pass back
1910      * the part of the format string that matches the valid codes '<b>SP</b>'
1911      * and '<b>TH</b>'.  If none of these are found, then it is set to an
1912      * empty string;  If both codes are found then a string is returned with
1913      * code '<b>SP</b>' preceding code '<b>TH</b>' (i.e. '<b>SPTH</b>',
1914      * '<b>Spth</b>' or '<b>spth</b>').
1915      *
1916      * @param int $pn_num integer to be converted to words
1917      * @param string &$ps_format string of formatting codes (max. length 4)
1918      * @param int $pn_numofdigits no of digits to display if displayed as
1919      *                                numeral (i.e. not spelled out), not
1920      *                                including the sign (if negative); to
1921      *                                allow all digits specify 0
1922      * @param bool $pb_nopad boolean specifying whether to suppress
1923      *                                padding with leading noughts (if displayed
1924      *                                as numeral)
1925      * @param bool $pb_nosign boolean specifying whether to suppress the
1926      *                                display of the sign (if negative)
1927      * @param string $ps_locale language name abbreviation used for
1928      *                                formatting
1929      * @param string $ps_thousandsep optional thousand-separator (e.g. a comma)
1930      *                                numbers as spelled-out words
1931      * @param int $pn_padtype optional integer to specify padding (if
1932      *                                displayed as numeral) - can be
1933      *                                STR_PAD_LEFT or STR_PAD_RIGHT
1934      *
1935      * @return   string
1936      * @access   private
1937      * @since    Method available since Release 1.5.0
1938      */
1939     public function _formatNumber(
1940         $pn_num,
1941         &$ps_format,
1942         $pn_numofdigits,
1943         $pb_nopad = false,
1944         $pb_nosign = false,
1945         $ps_locale = "en_GB",
1946         $ps_thousandsep = null,
1947         $pn_padtype = STR_PAD_LEFT
1948     )
1949     {
1950         $hs_code1 = substr($ps_format, 0, 2);
1951         $hs_code2 = substr($ps_format, 2, 2);
1952
1953         $hs_sp = null;
1954         $hs_th = null;
1955         if (strtoupper($hs_code1) == "SP") {
1956             $hs_sp = $hs_code1;
1957             if (strtoupper($hs_code2) == "TH") {
1958                 $hs_th = $hs_code2;
1959             }
1960         } elseif (strtoupper($hs_code1) == "TH") {
1961             $hs_th = $hs_code1;
1962             if (strtoupper($hs_code2) == "SP") {
1963                 $hs_sp = $hs_code2;
1964             }
1965         }
1966
1967         $hn_absnum = abs($pn_num);
1968         if ($pn_numofdigits > 0 && strlen($hn_absnum) > $pn_numofdigits) {
1969             $hn_absnum = intval(substr($hn_absnum, -$pn_numofdigits));
1970         }
1971         $hs_num = $hn_absnum;
1972
1973         if (!is_null($hs_sp)) {
1974             // Spell out number:
1975             //
1976             $ps_format = $hs_sp .
1977                 (is_null($hs_th) ? "" : ($hs_sp == "SP" ? "TH" : "th"));
1978             return $this->_spellNumber(
1979                 !$pb_nosign && $pn_num < 0 ?
1980                     $hn_absnum * -1 :
1981                     $hn_absnum,
1982                 !is_null($hs_th),
1983                 $hs_sp,
1984                 $ps_locale
1985             );
1986         } else {
1987             // Display number as Arabic numeral:
1988             //
1989             if (!$pb_nopad) {
1990                 $hs_num = str_pad($hs_num, $pn_numofdigits, "0", $pn_padtype);
1991             }
1992
1993             if (!is_null($ps_thousandsep)) {
1994                 for ($i = strlen($hs_num) - 3; $i > 0; $i -= 3) {
1995                     $hs_num = substr($hs_num, 0, $i) .
1996                         $ps_thousandsep .
1997                         substr($hs_num, $i);
1998                 }
1999             }
2000
2001             if (!$pb_nosign) {
2002                 if ($pn_num < 0) {
2003                     $hs_num = "-" . $hs_num;
2004                 } elseif (!$pb_nopad) {
2005                     $hs_num = " " . $hs_num;
2006                 }
2007             }
2008
2009             if (!is_null($hs_th)) {
2010                 $ps_format = $hs_th;
2011                 return $hs_num .
2012                     $this->_getOrdinalSuffix(
2013                         $pn_num,
2014                         substr($hs_th, 0, 1) == "T"
2015                     );
2016             } else {
2017                 $ps_format = "";
2018                 return $hs_num;
2019             }
2020         }
2021     }
2022
2023
2024     // }}}
2025     // {{{ formatLikeSQL()
2026
2027     /**
2028      * Formats the date according to the specified formatting code string,
2029      * based on SQL date-formatting codes
2030      *
2031      * Most codes reproduce the no of digits equal to the length of the
2032      * code, for example, '<b>YYY</b>' will return the last 3 digits of
2033      * the year, and so the year 2007 will produce '007', and the year 89
2034      * will produce '089', unless the no-padding code is used as in
2035      * '<b>NPYYY</b>', which will return '89'.
2036      *
2037      * For negative values, the sign will be discarded, unless the
2038      * '<b>S</b>' code is used in combination, but note that for positive
2039      * values the value will be padded with a leading space unless it
2040      * is suppressed with the no-padding modifier, for example for 2007:
2041      *
2042      *   - <b>YYYY</b> - returns '2007'
2043      *   - <b>SYYYY</b> - returns ' 2007'
2044      *   - <b>NPSYYYY</b> - returns '2007'
2045      *
2046      * The no-padding modifier '<b>NP</b>' can be used with numeric codes
2047      * to suppress leading (or trailing in the case of code '<b>F</b>')
2048      * noughts, and with character-returning codes such as '<b>DAY</b>'
2049      * to suppress trailing spaces, which will otherwise be padded to the
2050      * maximum possible length of the return-value of the code; for
2051      * example, for Monday:
2052      *
2053      *   - <b>Day</b> - returns 'Monday   ' because the maximum length of
2054      *                  this code is 'Wednesday';
2055      *   - <b>NPDay</b> - returns 'Monday'
2056      *
2057      * N.B. this code affects the code immediately following only, and
2058      * without this code the default is always to apply padding.
2059      *
2060      * Most character-returning codes, such as '<b>MONTH</b>', will
2061      * set the capitalization according to the code, so for example:
2062      *
2063      *   - <b>MONTH</b> - returns upper-case spelling, e.g. 'JANUARY'
2064      *   - <b>Month</b> - returns spelling with first character of each word
2065      *                    capitalized, e.g. 'January'
2066      *   - <b>month</b> - returns lower-case spelling, e.g. 'january'
2067      *
2068      * Where it makes sense, numeric codes can be combined with a following
2069      * '<b>SP</b>' code which spells out the number, or with a '<b>TH</b>'
2070      * code, which renders the code as an ordinal ('<b>TH</b>' only works
2071      * in English), for example, for 31st December:
2072      *
2073      *   - <b>DD</b> - returns '31'
2074      *   - <b>DDTH</b> - returns '31ST'
2075      *   - <b>DDth</b> - returns '31st'
2076      *   - <b>DDSP</b> - returns 'THIRTY-ONE'
2077      *   - <b>DDSp</b> - returns 'Thirty-one'
2078      *   - <b>DDsp</b> - returns 'thirty-one'
2079      *   - <b>DDSPTH</b> - returns 'THIRTY-FIRST'
2080      *   - <b>DDSpth</b> - returns 'Thirty-first'
2081      *   - <b>DDspth</b> - returns 'thirty-first'
2082      *
2083      *
2084      * All formatting options:
2085      *
2086      *   - <b>-</b> (All punctuation and white-space is reproduced unchanged)
2087      *   - <b>/</b>
2088      *   - <b>,</b>
2089      *   - <b>.</b>
2090      *   - <b>;</b>
2091      *   - <b>:</b>
2092      *   - <b>"text"</b> - Quoted text is reproduced unchanged (escape using
2093      *                     '\')
2094      *   - <b>AD</b> - AD indicator with or without full stops
2095      *   - <b>A.D.</b>
2096      *   - <b>AM</b> - Meridian indicator with or without full stops
2097      *   - <b>A.M.</b>
2098      *   - <b>BC</b> - BC indicator with or without full stops
2099      *   - <b>B.C.</b>
2100      *   - <b>BCE</b> - BCE indicator with or without full stops
2101      *   - <b>B.C.E.</b>
2102      *   - <b>CC</b> - Century, i.e. the year divided by 100, discarding the
2103      *                 remainder; '<b>S</b>' prefixes negative years with a
2104      *                 minus sign
2105      *   - <b>SCC</b>
2106      *   - <b>CE</b> - CE indicator with or without full stops
2107      *   - <b>C.E.</b>
2108      *   - <b>D</b> - Day of week (0-6), where 0 represents Sunday
2109      *   - <b>DAY</b> - Name of day, padded with blanks to display width of the
2110      *                  widest name of day in the locale of the machine
2111      *   - <b>DD</b> - Day of month (1-31)
2112      *   - <b>DDD</b> - Day of year (1-366)
2113      *   - <b>DY</b> - Abbreviated name of day
2114      *   - <b>FFF</b> - Fractional seconds; no radix character is printed.  The
2115      *                  no of '<b>F</b>'s determines the no of digits of the
2116      *                  part-second to return; e.g. 'HH:MI:SS.FF'
2117      *   - <b>F[integer]</b> - The integer after '<b>F</b>' specifies the
2118      *                         number of digits of the part-second to return.
2119      *                         This is an alternative to using several
2120      *                         '<b>F</b>'s in sequence, and '<b>F3</b>' is thus
2121      *                         equivalent to using '<b>FFF</b>'.
2122      *   - <b>HH</b> - Hour of day (0-23)
2123      *   - <b>HH12</b> - Hour of day (1-12)
2124      *   - <b>HH24</b> - Hour of day (0-23)
2125      *   - <b>ID</b> - Day of week (1-7) based on the ISO 8601 standard (see
2126      *                 '<b>IW</b>')
2127      *   - <b>IW</b> - Week of year (1-52 or 1-53) based on the
2128      *                 {@link http://en.wikipedia.org/wiki/ISO_week_date ISO 8601 standard}
2129      *   - <b>IYYY</b> - 4-digit year based on the ISO 8601 standard (see
2130      *                 '<b>IW</b>'); '<b>S</b>' prefixes negative years with a
2131      *                 minus sign
2132      *   - <b>SIYYY</b>
2133      *   - <b>IYY</b> - Last 3, 2, or 1 digit(s) of ISO year
2134      *   - <b>IY</b>
2135      *   - <b>I</b>
2136      *   - <b>J</b> - {@link http://en.wikipedia.org/wiki/Julian_day Julian day} -
2137      *                the number of days since Monday, 24th November, 4714 B.C.
2138      *                (proleptic Gregorian calendar)
2139      *   - <b>MI</b> - Minute (0-59)
2140      *   - <b>MM</b> - Month (01-12; January = 01)
2141      *   - <b>MON</b> - Abbreviated name of month
2142      *   - <b>MONTH</b> - Name of month, padded with blanks to display width of
2143      *                    the widest name of month in the date language used for
2144      *   - <b>PM</b> - Meridian indicator with or without full stops
2145      *   - <b>P.M.</b>
2146      *   - <b>Q</b> - Quarter of year (1, 2, 3, 4; January - March = 1)
2147      *   - <b>RM</b> - Roman numeral month (I-XII; January = I); N.B. padded
2148      *                 with leading spaces.
2149      *   - <b>SS</b> - Second (0-59)
2150      *   - <b>SSSSS</b> - Seconds past midnight (0-86399)
2151      *   - <b>TZC</b> - Abbreviated form of time zone name, e.g. 'GMT', or the
2152      *                  abbreviation for Summer time if the date falls in Summer
2153      *                  time, e.g. 'BST'.
2154      *                  N.B. this is not a unique identifier - for this purpose
2155      *                  use the time zone region (code '<b>TZR</b>').
2156      *   - <b>TZH</b> - Time zone hour; '<b>S</b>' prefixes the hour with the
2157      *                  correct sign, (+/-), which otherwise is not displayed.
2158      *                  Note that the leading nought can be suppressed with the
2159      *                  no-padding code '<b>NP</b>').  Also note that if you
2160      *                  combine with the '<b>SP</b>' code, the sign will not be
2161      *                  spelled out. (I.e. '<b>STZHSp</b>' will produce '+One',
2162      *                  for example, and not 'Plus One'.
2163      *                  '<b>TZH:TZM</b>' will produce, for example, '+05:30'.
2164      *                  (Also see '<b>TZM</b>' format code)
2165      *   - <b>STZH</b>
2166      *   - <b>TZI</b> - Whether or not the date is in Summer time (daylight
2167      *                  saving time).  Returns '1' if Summer time, else '0'.
2168      *   - <b>TZM</b> - Time zone minute, without any +/- sign.  (Also see
2169      *                  '<b>TZH</b>' format element)
2170      *   - <b>TZN</b> - Long form of time zone name, e.g.
2171      *                  'Greenwich Mean Time', or the name of the Summer time if
2172      *                  the date falls in Summer time, e.g.
2173      *                  'British Summer Time'.  N.B. this is not a unique
2174      *                  identifier - for this purpose use the time zone region
2175      *                  (code '<b>TZR</b>').
2176      *   - <b>TZO</b> - Time zone offset in ISO 8601 form - that is, 'Z' if
2177      *                  UTC, else [+/-][hh]:[mm] (which would be equivalent
2178      *                  to '<b>STZH:TZM</b>').  Note that this result is right
2179      *                  padded.
2180      *                  with spaces by default, (i.e. if 'Z').
2181      *   - <b>TZS</b> - Time zone offset in seconds; '<b>S</b>' prefixes
2182      *                  negative sign with minus sign '-' if negative, and no
2183      *                  sign if positive (i.e. -43200 to 50400).
2184      *   - <b>STZS</b>
2185      *   - <b>TZR</b> - Time zone region, that is, the name or ID of the time
2186      *                  zone e.g. 'Europe/London'.  This value is unique for
2187      *                  each time zone.
2188      *   - <b>U</b> - Seconds since the Unix Epoch -
2189      *                January 1 1970 00:00:00 GMT
2190      *   - <b>W</b> - 'Absolute' week of month (1-5), counting week 1 as
2191      *                1st-7th of the year, regardless of the day
2192      *   - <b>W1</b> - Week of year (1-54), counting week 1 as the week that
2193      *                 contains 1st January
2194      *   - <b>W4</b> - Week of year (1-53), counting week 1 as the week that
2195      *                 contains 4th January (i.e. first week with at least 4
2196      *                 days)
2197      *   - <b>W7</b> - Week of year (1-53), counting week 1 as the week that
2198      *                 contains 7th January (i.e. first full week)
2199      *   - <b>WW</b> - 'Absolute' week of year (1-53), counting week 1 as
2200      *                 1st-7th of the year, regardless of the day
2201      *   - <b>YEAR</b> - Year, spelled out; '<b>S</b>' prefixes negative
2202      *                   years with 'MINUS'; N.B. '<b>YEAR</b>' differs from
2203      *                   '<b>YYYYSP</b>' in that the first will render 1923,
2204      *                   for example, as 'NINETEEN TWENTY-THREE, and the
2205      *                   second as 'ONE THOUSAND NINE HUNDRED TWENTY-THREE'
2206      *   - <b>SYEAR</b>
2207      *   - <b>YYYY</b> - 4-digit year; '<b>S</b>' prefixes negative years
2208      *                   with a minus sign
2209      *   - <b>SYYYY</b>
2210      *   - <b>YYY</b> - Last 3, 2, or 1 digit(s) of year
2211      *   - <b>YY</b>
2212      *   - <b>Y</b>
2213      *   - <b>Y,YYY</b> - Year with thousands-separator in this position; five
2214      *                    possible separators
2215      *   - <b>Y.YYY</b>
2216      *   - <b>Y�YYY</b> - N.B. space-dot (mid-dot, interpunct) is valid only in
2217      *                    ISO 8859-1 (so take care when using UTF-8 in
2218      *                    particular)
2219      *   - <b>Y'YYY</b>
2220      *   - <b>Y YYY</b>
2221      *
2222      * In addition the following codes can be used in combination with other
2223      * codes;
2224      *  Codes that modify the next code in the format string:
2225      *
2226      *   - <b>NP</b> - 'No Padding' - Returns a value with no trailing blanks
2227      *                 and no leading or trailing noughts; N.B. that the
2228      *                 default is to include this padding in the return string.
2229      *                 N.B. affects the code immediately following only.
2230      *
2231      *  Codes that modify the previous code in the format string (can only
2232      *  be used with integral codes such as '<b>MM</b>'):
2233      *
2234      *   - <b>TH</b> - Ordinal number
2235      *   - <b>SP</b> - Spelled cardinal number
2236      *   - <b>SPTH</b> - Spelled ordinal number (combination of '<b>SP</b>'
2237      *                   and '<b>TH</b>' in any order)
2238      *   - <b>THSP</b>
2239      *
2240      * Code '<b>SP</b>' can have the following three variations (which can
2241      * also be used in combination with '<b>TH</b>'):
2242      *
2243      *   - <b>SP</b> - returns upper-case spelling, e.g. 'FOUR HUNDRED'
2244      *   - <b>Sp</b> - returns spelling with first character of each word
2245      *                 capitalized, e.g. 'Four Hundred'
2246      *   - <b>sp</b> - returns lower-case spelling, e.g. 'four hundred'
2247      *
2248      * Code '<b>TH</b>' can have the following two variations (although in
2249      * combination with code '<b>SP</b>', the case specification of
2250      * '<b>SP</b>' takes precedence):
2251      *
2252      *   - <b>TH</b> - returns upper-case ordinal suffix, e.g. 400TH
2253      *   - <b>th</b> - returns lower-case ordinal suffix, e.g. 400th
2254      *
2255      * @param string $ps_format format string for returned date/time
2256      * @param string $ps_locale language name abbreviation used for formatting
2257      *                           numbers as spelled-out words
2258      *
2259      * @return   string     date/time in given format
2260      * @access   public
2261      * @see      Date::format(), Date::formatLikeStrftime(), Date::formatLikeDate()
2262      * @since    Method available since Release 1.5.0
2263      */
2264     public function formatLikeSQL($ps_format, $ps_locale = "en_GB")
2265     {
2266         if (!preg_match(
2267             '/^("([^"\\\\]|\\\\\\\\|\\\\")*"|(D{1,3}|S?C+|' .
2268             'HH(12|24)?|I[DW]|S?IY*|J|M[IM]|Q|SS(SSS)?|S?TZ[HS]|' .
2269             'TZM|U|W[W147]?|S?Y{1,3}([,.�\' ]?YYY)*)(SP(TH)?|' .
2270             'TH(SP)?)?|AD|A\.D\.|AM|A\.M\.|BCE?|B\.C\.(E\.)?|CE|' .
2271             'C\.E\.|DAY|DY|F(F*|[1-9][0-9]*)|MON(TH)?|NP|PM|' .
2272             'P\.M\.|RM|TZ[CINOR]|S?YEAR|[^A-Z0-9"])*$/i',
2273             $ps_format
2274         )) {
2275             return PEAR::raiseError(
2276                 "Invalid date format '$ps_format'",
2277                 DATE_ERROR_INVALIDFORMATSTRING
2278             );
2279         }
2280
2281         $ret = "";
2282         $i = 0;
2283
2284         $hb_nopadflag = false;
2285         $hb_showsignflag = false;
2286
2287         $hn_weekdaypad = null;
2288         $hn_monthpad = null;
2289         $hn_isoyear = null;
2290         $hn_isoweek = null;
2291         $hn_isoday = null;
2292         $hn_tzoffset = null;
2293
2294         while ($i < strlen($ps_format)) {
2295             $hb_lower = false;
2296
2297             if ($hb_nopadflag) {
2298                 $hb_nopad = true;
2299             } else {
2300                 $hb_nopad = false;
2301             }
2302             if ($hb_showsignflag) {
2303                 $hb_nosign = false;
2304             } else {
2305                 $hb_nosign = true;
2306             }
2307             $hb_nopadflag = false;
2308             $hb_showsignflag = false;
2309
2310             switch ($hs_char = substr($ps_format, $i, 1)) {
2311                 case "-":
2312                 case "/":
2313                 case ",":
2314                 case ".":
2315                 case ";":
2316                 case ":":
2317                 case " ":
2318                     $ret .= $hs_char;
2319                     $i += 1;
2320                     break;
2321                 case "\"":
2322                     preg_match(
2323                         '/(([^"\\\\]|\\\\\\\\|\\\\")*)"/',
2324                         $ps_format,
2325                         $ha_matches,
2326                         PREG_OFFSET_CAPTURE,
2327                         $i + 1
2328                     );
2329                     $ret .= str_replace(
2330                         array('\\\\', '\\"'),
2331                         array('\\', '"'),
2332                         $ha_matches[1][0]
2333                     );
2334                     $i += strlen($ha_matches[0][0]) + 1;
2335                     break;
2336                 case "a":
2337                     $hb_lower = true;
2338                 // no break
2339                 case "A":
2340                     if (strtoupper(substr($ps_format, $i, 4)) == "A.D.") {
2341                         $ret .= $this->year >= 0 ?
2342                             ($hb_lower ? "a.d." : "A.D.") :
2343                             ($hb_lower ? "b.c." : "B.C.");
2344                         $i += 4;
2345                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "AD") {
2346                         $ret .= $this->year >= 0 ?
2347                             ($hb_lower ? "ad" : "AD") :
2348                             ($hb_lower ? "bc" : "BC");
2349                         $i += 2;
2350                     } else {
2351                         if ($this->ob_invalidtime) {
2352                             return $this->_getErrorInvalidTime();
2353                         }
2354                         if (strtoupper(substr($ps_format, $i, 4)) == "A.M.") {
2355                             $ret .= $this->hour < 12 ?
2356                                 ($hb_lower ? "a.m." : "A.M.") :
2357                                 ($hb_lower ? "p.m." : "P.M.");
2358                             $i += 4;
2359                         } elseif (strtoupper(substr($ps_format, $i, 2)) == "AM") {
2360                             $ret .= $this->hour < 12 ?
2361                                 ($hb_lower ? "am" : "AM") :
2362                                 ($hb_lower ? "pm" : "PM");
2363                             $i += 2;
2364                         }
2365                     }
2366
2367                     break;
2368                 case "b":
2369                     $hb_lower = true;
2370                 // no break
2371                 case "B":
2372                     // Check for 'B.C.E.' first:
2373                     //
2374                     if (strtoupper(substr($ps_format, $i, 6)) == "B.C.E.") {
2375                         if ($this->year >= 0) {
2376                             $hs_era = $hb_lower ? "c.e." : "C.E.";
2377                             $ret .= $hb_nopad ?
2378                                 $hs_era :
2379                                 str_pad($hs_era, 6, " ", STR_PAD_RIGHT);
2380                         } else {
2381                             $ret .= $hb_lower ? "b.c.e." : "B.C.E.";
2382                         }
2383                         $i += 6;
2384                     } elseif (strtoupper(substr($ps_format, $i, 3)) == "BCE") {
2385                         if ($this->year >= 0) {
2386                             $hs_era = $hb_lower ? "ce" : "CE";
2387                             $ret .= $hb_nopad ?
2388                                 $hs_era :
2389                                 str_pad($hs_era, 3, " ", STR_PAD_RIGHT);
2390                         } else {
2391                             $ret .= $hb_lower ? "bce" : "BCE";
2392                         }
2393                         $i += 3;
2394                     } elseif (strtoupper(substr($ps_format, $i, 4)) == "B.C.") {
2395                         $ret .= $this->year >= 0 ?
2396                             ($hb_lower ? "a.d." : "A.D.") :
2397                             ($hb_lower ? "b.c." : "B.C.");
2398                         $i += 4;
2399                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "BC") {
2400                         $ret .= $this->year >= 0 ?
2401                             ($hb_lower ? "ad" : "AD") :
2402                             ($hb_lower ? "bc" : "BC");
2403                         $i += 2;
2404                     }
2405
2406                     break;
2407                 case "c":
2408                     $hb_lower = true;
2409                 // no break
2410                 case "C":
2411                     if (strtoupper(substr($ps_format, $i, 4)) == "C.E.") {
2412                         if ($this->year >= 0) {
2413                             $hs_era = $hb_lower ? "c.e." : "C.E.";
2414                             $ret .= $hb_nopad ?
2415                                 $hs_era :
2416                                 str_pad($hs_era, 6, " ", STR_PAD_RIGHT);
2417                         } else {
2418                             $ret .= $hb_lower ? "b.c.e." : "B.C.E.";
2419                         }
2420                         $i += 4;
2421                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "CE") {
2422                         if ($this->year >= 0) {
2423                             $hs_era = $hb_lower ? "ce" : "CE";
2424                             $ret .= $hb_nopad ?
2425                                 $hs_era :
2426                                 str_pad($hs_era, 3, " ", STR_PAD_RIGHT);
2427                         } else {
2428                             $ret .= $hb_lower ? "bce" : "BCE";
2429                         }
2430                         $i += 2;
2431                     } else {
2432                         // Code C(CCC...):
2433                         //
2434                         $hn_codelen = 1;
2435                         while (strtoupper(substr(
2436                                 $ps_format,
2437                                 $i + $hn_codelen,
2438                                 1
2439                             )) == "C") {
2440                             ++$hn_codelen;
2441                         }
2442
2443                         // Check next code is not 'CE' or 'C.E.'
2444                         //
2445                         if ($hn_codelen > 1 &&
2446                             (
2447                                 strtoupper(substr(
2448                                     $ps_format,
2449                                     $i + $hn_codelen - 1,
2450                                     4
2451                                 )) == "C.E." ||
2452                                 strtoupper(substr(
2453                                     $ps_format,
2454                                     $i + $hn_codelen - 1,
2455                                     2
2456                                 )) == "CE"
2457                             )) {
2458                             --$hn_codelen;
2459                         }
2460
2461                         $hn_century = intval($this->year / 100);
2462                         $hs_numberformat = substr($ps_format, $i + $hn_codelen, 4);
2463                         $hs_century = $this->_formatNumber(
2464                             $hn_century,
2465                             $hs_numberformat,
2466                             $hn_codelen,
2467                             $hb_nopad,
2468                             $hb_nosign,
2469                             $ps_locale
2470                         );
2471                         if (Pear::isError($hs_century)) {
2472                             return $hs_century;
2473                         }
2474
2475                         $ret .= $hs_century;
2476                         $i += $hn_codelen + strlen($hs_numberformat);
2477                     }
2478
2479                     break;
2480                 case "d":
2481                     $hb_lower = true;
2482                 // no break
2483                 case "D":
2484                     if (strtoupper(substr($ps_format, $i, 3)) == "DAY") {
2485                         $hs_day = Date_Calc::getWeekdayFullname(
2486                             $this->day,
2487                             $this->month,
2488                             $this->year
2489                         );
2490
2491                         if (!$hb_nopad) {
2492                             if (is_null($hn_weekdaypad)) {
2493                                 // Set week-day padding variable:
2494                                 //
2495                                 $hn_weekdaypad = 0;
2496                                 foreach (Date_Calc::getWeekDays() as $hs_weekday) {
2497                                     $hn_weekdaypad = max(
2498                                         $hn_weekdaypad,
2499                                         strlen($hs_weekday)
2500                                     );
2501                                 }
2502                             }
2503                             $hs_day = str_pad(
2504                                 $hs_day,
2505                                 $hn_weekdaypad,
2506                                 " ",
2507                                 STR_PAD_RIGHT
2508                             );
2509                         }
2510
2511                         $ret .= $hb_lower ?
2512                             strtolower($hs_day) :
2513                             (substr($ps_format, $i + 1, 1) == "A" ?
2514                                 strtoupper($hs_day) :
2515                                 $hs_day);
2516                         $i += 3;
2517                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "DY") {
2518                         $hs_day = Date_Calc::getWeekdayAbbrname(
2519                             $this->day,
2520                             $this->month,
2521                             $this->year
2522                         );
2523                         $ret .= $hb_lower ?
2524                             strtolower($hs_day) :
2525                             (substr($ps_format, $i + 1, 1) == "Y" ?
2526                                 strtoupper($hs_day) :
2527                                 $hs_day);
2528                         $i += 2;
2529                     } elseif (strtoupper(substr($ps_format, $i, 3)) == "DDD" &&
2530                         strtoupper(substr($ps_format, $i + 2, 3)) != "DAY" &&
2531                         strtoupper(substr($ps_format, $i + 2, 2)) != "DY"
2532                     ) {
2533                         $hn_day = Date_Calc::dayOfYear(
2534                             $this->day,
2535                             $this->month,
2536                             $this->year
2537                         );
2538                         $hs_numberformat = substr($ps_format, $i + 3, 4);
2539                         $hs_day = $this->_formatNumber(
2540                             $hn_day,
2541                             $hs_numberformat,
2542                             3,
2543                             $hb_nopad,
2544                             true,
2545                             $ps_locale
2546                         );
2547                         if (Pear::isError($hs_day)) {
2548                             return $hs_day;
2549                         }
2550
2551                         $ret .= $hs_day;
2552                         $i += 3 + strlen($hs_numberformat);
2553                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "DD" &&
2554                         strtoupper(substr($ps_format, $i + 1, 3)) != "DAY" &&
2555                         strtoupper(substr($ps_format, $i + 1, 2)) != "DY"
2556                     ) {
2557                         $hs_numberformat = substr($ps_format, $i + 2, 4);
2558                         $hs_day = $this->_formatNumber(
2559                             $this->day,
2560                             $hs_numberformat,
2561                             2,
2562                             $hb_nopad,
2563                             true,
2564                             $ps_locale
2565                         );
2566                         if (Pear::isError($hs_day)) {
2567                             return $hs_day;
2568                         }
2569
2570                         $ret .= $hs_day;
2571                         $i += 2 + strlen($hs_numberformat);
2572                     } else {
2573                         // Code 'D':
2574                         //
2575                         $hn_day = Date_Calc::dayOfWeek(
2576                             $this->day,
2577                             $this->month,
2578                             $this->year
2579                         );
2580                         $hs_numberformat = substr($ps_format, $i + 1, 4);
2581                         $hs_day = $this->_formatNumber(
2582                             $hn_day,
2583                             $hs_numberformat,
2584                             1,
2585                             $hb_nopad,
2586                             true,
2587                             $ps_locale
2588                         );
2589                         if (Pear::isError($hs_day)) {
2590                             return $hs_day;
2591                         }
2592
2593                         $ret .= $hs_day;
2594                         $i += 1 + strlen($hs_numberformat);
2595                     }
2596
2597                     break;
2598                 case "f":
2599                 case "F":
2600                     if ($this->ob_invalidtime) {
2601                         return $this->_getErrorInvalidTime();
2602                     }
2603                     $hn_codelen = 1;
2604                     if (is_numeric(substr($ps_format, $i + $hn_codelen, 1))) {
2605                         ++$hn_codelen;
2606                         while (is_numeric(substr($ps_format, $i + $hn_codelen, 1))) {
2607                             ++$hn_codelen;
2608                         }
2609
2610                         $hn_partsecdigits = substr($ps_format, $i + 1, $hn_codelen - 1);
2611                     } else {
2612                         while (strtoupper(substr(
2613                                 $ps_format,
2614                                 $i + $hn_codelen,
2615                                 1
2616                             )) == "F") {
2617                             ++$hn_codelen;
2618                         }
2619
2620                         // Check next code is not F[numeric]:
2621                         //
2622                         if ($hn_codelen > 1 &&
2623                             is_numeric(substr($ps_format, $i + $hn_codelen, 1))) {
2624                             --$hn_codelen;
2625                         }
2626
2627                         $hn_partsecdigits = $hn_codelen;
2628                     }
2629
2630                     $hs_partsec = (string)$this->partsecond;
2631                     if (preg_match(
2632                         '/^([0-9]+)(\.([0-9]+))?E-([0-9]+)$/i',
2633                         $hs_partsec,
2634                         $ha_matches
2635                     )) {
2636                         $hs_partsec =
2637                             str_repeat("0", $ha_matches[4] - strlen($ha_matches[1])) .
2638                             $ha_matches[1] .
2639                             $ha_matches[3];
2640                     } else {
2641                         $hs_partsec = substr($hs_partsec, 2);
2642                     }
2643                     $hs_partsec = substr($hs_partsec, 0, $hn_partsecdigits);
2644
2645                     // '_formatNumber() will not work for this because the
2646                     // part-second is an int, and we want it to behave like a float:
2647                     //
2648                     if ($hb_nopad) {
2649                         $hs_partsec = rtrim($hs_partsec, "0");
2650                         if ($hs_partsec == "") {
2651                             $hs_partsec = "0";
2652                         }
2653                     } else {
2654                         $hs_partsec = str_pad(
2655                             $hs_partsec,
2656                             $hn_partsecdigits,
2657                             "0",
2658                             STR_PAD_RIGHT
2659                         );
2660                     }
2661
2662                     $ret .= $hs_partsec;
2663                     $i += $hn_codelen;
2664                     break;
2665                 case "h":
2666                 case "H":
2667                     if ($this->ob_invalidtime) {
2668                         return $this->_getErrorInvalidTime();
2669                     }
2670                     if (strtoupper(substr($ps_format, $i, 4)) == "HH12") {
2671                         $hn_hour = $this->hour % 12;
2672                         if ($hn_hour == 0) {
2673                             $hn_hour = 12;
2674                         }
2675
2676                         $hn_codelen = 4;
2677                     } else {
2678                         // Code 'HH' or 'HH24':
2679                         //
2680                         $hn_hour = $this->hour;
2681                         $hn_codelen = strtoupper(substr(
2682                             $ps_format,
2683                             $i,
2684                             4
2685                         )) == "HH24" ? 4 : 2;
2686                     }
2687
2688                     $hs_numberformat = substr($ps_format, $i + $hn_codelen, 4);
2689                     $hs_hour = $this->_formatNumber(
2690                         $hn_hour,
2691                         $hs_numberformat,
2692                         2,
2693                         $hb_nopad,
2694                         true,
2695                         $ps_locale
2696                     );
2697                     if (Pear::isError($hs_hour)) {
2698                         return $hs_hour;
2699                     }
2700
2701                     $ret .= $hs_hour;
2702                     $i += $hn_codelen + strlen($hs_numberformat);
2703                     break;
2704                 case "i":
2705                 case "I":
2706                     if (is_null($hn_isoyear)) {
2707                         list($hn_isoyear, $hn_isoweek, $hn_isoday) =
2708                             Date_Calc::isoWeekDate(
2709                                 $this->day,
2710                                 $this->month,
2711                                 $this->year
2712                             );
2713                     }
2714
2715                     if (strtoupper(substr($ps_format, $i, 2)) == "ID" &&
2716                         strtoupper(substr($ps_format, $i + 1, 3)) != "DAY"
2717                     ) {
2718                         $hs_numberformat = substr($ps_format, $i + 2, 4);
2719                         $hs_isoday = $this->_formatNumber(
2720                             $hn_isoday,
2721                             $hs_numberformat,
2722                             1,
2723                             $hb_nopad,
2724                             true,
2725                             $ps_locale
2726                         );
2727                         if (Pear::isError($hs_isoday)) {
2728                             return $hs_isoday;
2729                         }
2730
2731                         $ret .= $hs_isoday;
2732                         $i += 2 + strlen($hs_numberformat);
2733                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "IW") {
2734                         $hs_numberformat = substr($ps_format, $i + 2, 4);
2735                         $hs_isoweek = $this->_formatNumber(
2736                             $hn_isoweek,
2737                             $hs_numberformat,
2738                             2,
2739                             $hb_nopad,
2740                             true,
2741                             $ps_locale
2742                         );
2743                         if (Pear::isError($hs_isoweek)) {
2744                             return $hs_isoweek;
2745                         }
2746
2747                         $ret .= $hs_isoweek;
2748                         $i += 2 + strlen($hs_numberformat);
2749                     } else {
2750                         // Code I(YYY...):
2751                         //
2752                         $hn_codelen = 1;
2753                         while (strtoupper(substr(
2754                                 $ps_format,
2755                                 $i + $hn_codelen,
2756                                 1
2757                             )) == "Y") {
2758                             ++$hn_codelen;
2759                         }
2760
2761                         $hs_numberformat = substr($ps_format, $i + $hn_codelen, 4);
2762                         $hs_isoyear = $this->_formatNumber(
2763                             $hn_isoyear,
2764                             $hs_numberformat,
2765                             $hn_codelen,
2766                             $hb_nopad,
2767                             $hb_nosign,
2768                             $ps_locale
2769                         );
2770                         if (Pear::isError($hs_isoyear)) {
2771                             return $hs_isoyear;
2772                         }
2773
2774                         $ret .= $hs_isoyear;
2775                         $i += $hn_codelen + strlen($hs_numberformat);
2776                     }
2777
2778                     break;
2779                 case "j":
2780                 case "J":
2781                     $hn_jd = Date_Calc::dateToDays(
2782                         $this->day,
2783                         $this->month,
2784                         $this->year
2785                     );
2786                     $hs_numberformat = substr($ps_format, $i + 1, 4);
2787
2788                     // Allow sign if negative; allow all digits (specify nought);
2789                     // suppress padding:
2790                     //
2791                     $hs_jd = $this->_formatNumber(
2792                         $hn_jd,
2793                         $hs_numberformat,
2794                         0,
2795                         true,
2796                         false,
2797                         $ps_locale
2798                     );
2799                     if (Pear::isError($hs_jd)) {
2800                         return $hs_jd;
2801                     }
2802
2803                     $ret .= $hs_jd;
2804                     $i += 1 + strlen($hs_numberformat);
2805                     break;
2806                 case "m":
2807                     $hb_lower = true;
2808                 // no break
2809                 case "M":
2810                     if (strtoupper(substr($ps_format, $i, 2)) == "MI") {
2811                         if ($this->ob_invalidtime) {
2812                             return $this->_getErrorInvalidTime();
2813                         }
2814                         $hs_numberformat = substr($ps_format, $i + 2, 4);
2815                         $hs_minute = $this->_formatNumber(
2816                             $this->minute,
2817                             $hs_numberformat,
2818                             2,
2819                             $hb_nopad,
2820                             true,
2821                             $ps_locale
2822                         );
2823                         if (Pear::isError($hs_minute)) {
2824                             return $hs_minute;
2825                         }
2826
2827                         $ret .= $hs_minute;
2828                         $i += 2 + strlen($hs_numberformat);
2829                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "MM") {
2830                         $hs_numberformat = substr($ps_format, $i + 2, 4);
2831                         $hs_month = $this->_formatNumber(
2832                             $this->month,
2833                             $hs_numberformat,
2834                             2,
2835                             $hb_nopad,
2836                             true,
2837                             $ps_locale
2838                         );
2839                         if (Pear::isError($hs_month)) {
2840                             return $hs_month;
2841                         }
2842
2843                         $ret .= $hs_month;
2844                         $i += 2 + strlen($hs_numberformat);
2845                     } elseif (strtoupper(substr($ps_format, $i, 5)) == "MONTH") {
2846                         $hs_month = Date_Calc::getMonthFullname($this->month);
2847
2848                         if (!$hb_nopad) {
2849                             if (is_null($hn_monthpad)) {
2850                                 // Set month padding variable:
2851                                 //
2852                                 $hn_monthpad = 0;
2853                                 foreach (Date_Calc::getMonthNames() as $hs_monthofyear) {
2854                                     $hn_monthpad = max(
2855                                         $hn_monthpad,
2856                                         strlen($hs_monthofyear)
2857                                     );
2858                                 }
2859                             }
2860                             $hs_month = str_pad(
2861                                 $hs_month,
2862                                 $hn_monthpad,
2863                                 " ",
2864                                 STR_PAD_RIGHT
2865                             );
2866                         }
2867
2868                         $ret .= $hb_lower ?
2869                             strtolower($hs_month) :
2870                             (substr($ps_format, $i + 1, 1) == "O" ?
2871                                 strtoupper($hs_month) :
2872                                 $hs_month);
2873                         $i += 5;
2874                     } elseif (strtoupper(substr($ps_format, $i, 3)) == "MON") {
2875                         $hs_month = Date_Calc::getMonthAbbrname($this->month);
2876                         $ret .= $hb_lower ?
2877                             strtolower($hs_month) :
2878                             (substr($ps_format, $i + 1, 1) == "O" ?
2879                                 strtoupper($hs_month) :
2880                                 $hs_month);
2881                         $i += 3;
2882                     }
2883
2884                     break;
2885                 case "n":
2886                 case "N":
2887                     // No-Padding rule 'NP' applies to the next code (either trailing
2888                     // spaces or leading/trailing noughts):
2889                     //
2890                     $hb_nopadflag = true;
2891                     $i += 2;
2892                     break;
2893                 case "p":
2894                     $hb_lower = true;
2895                 // no break
2896                 case "P":
2897                     if ($this->ob_invalidtime) {
2898                         return $this->_getErrorInvalidTime();
2899                     }
2900                     if (strtoupper(substr($ps_format, $i, 4)) == "P.M.") {
2901                         $ret .= $this->hour < 12 ?
2902                             ($hb_lower ? "a.m." : "A.M.") :
2903                             ($hb_lower ? "p.m." : "P.M.");
2904                         $i += 4;
2905                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "PM") {
2906                         $ret .= $this->hour < 12 ?
2907                             ($hb_lower ? "am" : "AM") :
2908                             ($hb_lower ? "pm" : "PM");
2909                         $i += 2;
2910                     }
2911
2912                     break;
2913                 case "q":
2914                 case "Q":
2915                     // N.B. Current implementation ignores the day and year, but
2916                     // it is possible that a different implementation might be
2917                     // desired, so pass these parameters anyway:
2918                     //
2919                     $hn_quarter = Date_Calc::quarterOfYear(
2920                         $this->day,
2921                         $this->month,
2922                         $this->year
2923                     );
2924                     $hs_numberformat = substr($ps_format, $i + 1, 4);
2925                     $hs_quarter = $this->_formatNumber(
2926                         $hn_quarter,
2927                         $hs_numberformat,
2928                         1,
2929                         $hb_nopad,
2930                         true,
2931                         $ps_locale
2932                     );
2933                     if (Pear::isError($hs_quarter)) {
2934                         return $hs_quarter;
2935                     }
2936
2937                     $ret .= $hs_quarter;
2938                     $i += 1 + strlen($hs_numberformat);
2939                     break;
2940                 case "r":
2941                     $hb_lower = true;
2942                 // no break
2943                 case "R":
2944                     // Code 'RM':
2945                     //
2946                     switch ($this->month) {
2947                         case 1:
2948                             $hs_monthroman = "i";
2949                             break;
2950                         case 2:
2951                             $hs_monthroman = "ii";
2952                             break;
2953                         case 3:
2954                             $hs_monthroman = "iii";
2955                             break;
2956                         case 4:
2957                             $hs_monthroman = "iv";
2958                             break;
2959                         case 5:
2960                             $hs_monthroman = "v";
2961                             break;
2962                         case 6:
2963                             $hs_monthroman = "vi";
2964                             break;
2965                         case 7:
2966                             $hs_monthroman = "vii";
2967                             break;
2968                         case 8:
2969                             $hs_monthroman = "viii";
2970                             break;
2971                         case 9:
2972                             $hs_monthroman = "ix";
2973                             break;
2974                         case 10:
2975                             $hs_monthroman = "x";
2976                             break;
2977                         case 11:
2978                             $hs_monthroman = "xi";
2979                             break;
2980                         case 12:
2981                             $hs_monthroman = "xii";
2982                             break;
2983                     }
2984
2985                     $hs_monthroman = $hb_lower ?
2986                         $hs_monthroman :
2987                         strtoupper($hs_monthroman);
2988                     $ret .= $hb_nopad ?
2989                         $hs_monthroman :
2990                         str_pad($hs_monthroman, 4, " ", STR_PAD_LEFT);
2991                     $i += 2;
2992                     break;
2993                 case "s":
2994                 case "S":
2995                     // Check for 'SSSSS' before 'SS':
2996                     //
2997                     if (strtoupper(substr($ps_format, $i, 5)) == "SSSSS") {
2998                         if ($this->ob_invalidtime) {
2999                             return $this->_getErrorInvalidTime();
3000                         }
3001                         $hs_numberformat = substr($ps_format, $i + 5, 4);
3002                         $hn_second = Date_Calc::secondsPastMidnight(
3003                             $this->hour,
3004                             $this->minute,
3005                             $this->second
3006                         );
3007                         $hs_second = $this->_formatNumber(
3008                             $hn_second,
3009                             $hs_numberformat,
3010                             5,
3011                             $hb_nopad,
3012                             true,
3013                             $ps_locale
3014                         );
3015                         if (Pear::isError($hs_second)) {
3016                             return $hs_second;
3017                         }
3018
3019                         $ret .= $hs_second;
3020                         $i += 5 + strlen($hs_numberformat);
3021                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "SS") {
3022                         if ($this->ob_invalidtime) {
3023                             return $this->_getErrorInvalidTime();
3024                         }
3025                         $hs_numberformat = substr($ps_format, $i + 2, 4);
3026                         $hs_second = $this->_formatNumber(
3027                             $this->second,
3028                             $hs_numberformat,
3029                             2,
3030                             $hb_nopad,
3031                             true,
3032                             $ps_locale
3033                         );
3034                         if (Pear::isError($hs_second)) {
3035                             return $hs_second;
3036                         }
3037
3038                         $ret .= $hs_second;
3039                         $i += 2 + strlen($hs_numberformat);
3040                     } else {
3041                         // One of the following codes:
3042                         //  'SC(CCC...)'
3043                         //  'SY(YYY...)'
3044                         //  'SIY(YYY...)'
3045                         //  'STZH'
3046                         //  'STZS'
3047                         //  'SYEAR'
3048                         //
3049                         $hb_showsignflag = true;
3050                         if ($hb_nopad) {
3051                             $hb_nopadflag = true;
3052                         }
3053                         ++$i;
3054                     }
3055
3056                     break;
3057                 case "t":
3058                 case "T":
3059                     // Code TZ[...]:
3060                     //
3061
3062                     if (strtoupper(substr($ps_format, $i, 3)) == "TZR") {
3063                         // This time-zone-related code can be called when the time is
3064                         // invalid, but the others should return an error:
3065                         //
3066                         $ret .= $this->getTZID();
3067                         $i += 3;
3068                     } else {
3069                         if ($this->ob_invalidtime) {
3070                             return $this->_getErrorInvalidTime();
3071                         }
3072
3073                         if (strtoupper(substr($ps_format, $i, 3)) == "TZC") {
3074                             $ret .= $this->getTZShortName();
3075                             $i += 3;
3076                         } elseif (strtoupper(substr($ps_format, $i, 3)) == "TZH") {
3077                             if (is_null($hn_tzoffset)) {
3078                                 $hn_tzoffset = $this->getTZOffset();
3079                             }
3080
3081                             $hs_numberformat = substr($ps_format, $i + 3, 4);
3082                             $hn_tzh = intval($hn_tzoffset / 3600000);
3083
3084                             // Suppress sign here (it is added later):
3085                             //
3086                             $hs_tzh = $this->_formatNumber(
3087                                 $hn_tzh,
3088                                 $hs_numberformat,
3089                                 2,
3090                                 $hb_nopad,
3091                                 true,
3092                                 $ps_locale
3093                             );
3094                             if (Pear::isError($hs_tzh)) {
3095                                 return $hs_tzh;
3096                             }
3097
3098                             // Display sign, even if positive:
3099                             //
3100                             $ret .= ($hb_nosign ? "" : ($hn_tzh >= 0 ? '+' : '-')) .
3101                                 $hs_tzh;
3102                             $i += 3 + strlen($hs_numberformat);
3103                         } elseif (strtoupper(substr($ps_format, $i, 3)) == "TZI") {
3104                             $ret .= ($this->inDaylightTime() ? '1' : '0');
3105                             $i += 3;
3106                         } elseif (strtoupper(substr($ps_format, $i, 3)) == "TZM") {
3107                             if (is_null($hn_tzoffset)) {
3108                                 $hn_tzoffset = $this->getTZOffset();
3109                             }
3110
3111                             $hs_numberformat = substr($ps_format, $i + 3, 4);
3112                             $hn_tzm = intval(($hn_tzoffset % 3600000) / 60000);
3113
3114                             // Suppress sign:
3115                             //
3116                             $hs_tzm = $this->_formatNumber(
3117                                 $hn_tzm,
3118                                 $hs_numberformat,
3119                                 2,
3120                                 $hb_nopad,
3121                                 true,
3122                                 $ps_locale
3123                             );
3124                             if (Pear::isError($hs_tzm)) {
3125                                 return $hs_tzm;
3126                             }
3127
3128                             $ret .= $hs_tzm;
3129                             $i += 3 + strlen($hs_numberformat);
3130                         } elseif (strtoupper(substr($ps_format, $i, 3)) == "TZN") {
3131                             $ret .= $this->getTZLongName();
3132                             $i += 3;
3133                         } elseif (strtoupper(substr($ps_format, $i, 3)) == "TZO") {
3134                             if (is_null($hn_tzoffset)) {
3135                                 $hn_tzoffset = $this->getTZOffset();
3136                             }
3137
3138                             $hn_tzh = intval(abs($hn_tzoffset) / 3600000);
3139                             $hn_tzm = intval((abs($hn_tzoffset) % 3600000) / 60000);
3140
3141                             if ($hn_tzoffset == 0) {
3142                                 $ret .= $hb_nopad ? "Z" : "Z     ";
3143                             } else {
3144                                 // Display sign, even if positive:
3145                                 //
3146                                 $ret .= ($hn_tzoffset >= 0 ? '+' : '-') .
3147                                     sprintf("%02d", $hn_tzh) .
3148                                     ":" .
3149                                     sprintf("%02d", $hn_tzm);
3150                             }
3151                             $i += 3;
3152                         } elseif (strtoupper(substr($ps_format, $i, 3)) == "TZS") {
3153                             if (is_null($hn_tzoffset)) {
3154                                 $hn_tzoffset = $this->getTZOffset();
3155                             }
3156
3157                             $hs_numberformat = substr($ps_format, $i + 3, 4);
3158                             $hn_tzs = intval($hn_tzoffset / 1000);
3159                             $hs_tzs = $this->_formatNumber(
3160                                 $hn_tzs,
3161                                 $hs_numberformat,
3162                                 5,
3163                                 $hb_nopad,
3164                                 $hb_nosign,
3165                                 $ps_locale
3166                             );
3167                             if (Pear::isError($hs_tzs)) {
3168                                 return $hs_tzs;
3169                             }
3170
3171                             $ret .= $hs_tzs;
3172                             $i += 3 + strlen($hs_numberformat);
3173                         }
3174                     }
3175
3176                     break;
3177                 case "u":
3178                 case "U":
3179                     if ($this->ob_invalidtime) {
3180                         return $this->_getErrorInvalidTime();
3181                     }
3182                     $hn_unixtime = $this->getTime();
3183                     $hs_numberformat = substr($ps_format, $i + 1, 4);
3184
3185                     // Allow sign if negative; allow all digits (specify nought);
3186                     // suppress padding:
3187                     //
3188                     $hs_unixtime = $this->_formatNumber(
3189                         $hn_unixtime,
3190                         $hs_numberformat,
3191                         0,
3192                         true,
3193                         false,
3194                         $ps_locale
3195                     );
3196                     if (Pear::isError($hs_unixtime)) {
3197                         return $hs_unixtime;
3198                     }
3199
3200                     $ret .= $hs_unixtime;
3201                     $i += 1 + strlen($hs_numberformat);
3202                     break;
3203                 case "w":
3204                 case "W":
3205                     // Check for 'WW' before 'W':
3206                     //
3207                     if (strtoupper(substr($ps_format, $i, 2)) == "WW") {
3208                         $hn_week = Date_Calc::weekOfYearAbsolute(
3209                             $this->day,
3210                             $this->month,
3211                             $this->year
3212                         );
3213                         $hs_numberformat = substr($ps_format, $i + 2, 4);
3214                         $hs_week = $this->_formatNumber(
3215                             $hn_week,
3216                             $hs_numberformat,
3217                             2,
3218                             $hb_nopad,
3219                             true,
3220                             $ps_locale
3221                         );
3222                         if (Pear::isError($hs_week)) {
3223                             return $hs_week;
3224                         }
3225
3226                         $ret .= $hs_week;
3227                         $i += 2 + strlen($hs_numberformat);
3228                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "W1") {
3229                         $hn_week = Date_Calc::weekOfYear1st(
3230                             $this->day,
3231                             $this->month,
3232                             $this->year
3233                         );
3234                         $hs_numberformat = substr($ps_format, $i + 2, 4);
3235                         $hs_week = $this->_formatNumber(
3236                             $hn_week,
3237                             $hs_numberformat,
3238                             2,
3239                             $hb_nopad,
3240                             true,
3241                             $ps_locale
3242                         );
3243                         if (Pear::isError($hs_week)) {
3244                             return $hs_week;
3245                         }
3246
3247                         $ret .= $hs_week;
3248                         $i += 2 + strlen($hs_numberformat);
3249                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "W4") {
3250                         $ha_week = Date_Calc::weekOfYear4th(
3251                             $this->day,
3252                             $this->month,
3253                             $this->year
3254                         );
3255                         $hn_week = $ha_week[1];
3256                         $hs_numberformat = substr($ps_format, $i + 2, 4);
3257                         $hs_week = $this->_formatNumber(
3258                             $hn_week,
3259                             $hs_numberformat,
3260                             2,
3261                             $hb_nopad,
3262                             true,
3263                             $ps_locale
3264                         );
3265                         if (Pear::isError($hs_week)) {
3266                             return $hs_week;
3267                         }
3268
3269                         $ret .= $hs_week;
3270                         $i += 2 + strlen($hs_numberformat);
3271                     } elseif (strtoupper(substr($ps_format, $i, 2)) == "W7") {
3272                         $ha_week = Date_Calc::weekOfYear7th(
3273                             $this->day,
3274                             $this->month,
3275                             $this->year
3276                         );
3277                         $hn_week = $ha_week[1];
3278                         $hs_numberformat = substr($ps_format, $i + 2, 4);
3279                         $hs_week = $this->_formatNumber(
3280                             $hn_week,
3281                             $hs_numberformat,
3282                             2,
3283                             $hb_nopad,
3284                             true,
3285                             $ps_locale
3286                         );
3287                         if (Pear::isError($hs_week)) {
3288                             return $hs_week;
3289                         }
3290
3291                         $ret .= $hs_week;
3292                         $i += 2 + strlen($hs_numberformat);
3293                     } else {
3294                         // Code 'W':
3295                         //
3296                         $hn_week = Date_Calc::weekOfMonthAbsolute(
3297                             $this->day,
3298                             $this->month,
3299                             $this->year
3300                         );
3301                         $hs_numberformat = substr($ps_format, $i + 1, 4);
3302                         $hs_week = $this->_formatNumber(
3303                             $hn_week,
3304                             $hs_numberformat,
3305                             1,
3306                             $hb_nopad,
3307                             true,
3308                             $ps_locale
3309                         );
3310                         if (Pear::isError($hs_week)) {
3311                             return $hs_week;
3312                         }
3313
3314                         $ret .= $hs_week;
3315                         $i += 1 + strlen($hs_numberformat);
3316                     }
3317
3318                     break;
3319                 case "y":
3320                 case "Y":
3321                     // Check for 'YEAR' first:
3322                     //
3323                     if (strtoupper(substr($ps_format, $i, 4)) == "YEAR") {
3324                         switch (substr($ps_format, $i, 2)) {
3325                             case "YE":
3326                                 $hs_spformat = "SP";
3327                                 break;
3328                             case "Ye":
3329                                 $hs_spformat = "Sp";
3330                                 break;
3331                             default:
3332                                 $hs_spformat = "sp";
3333                         }
3334
3335                         if (($hn_yearabs = abs($this->year)) < 100 ||
3336                             $hn_yearabs % 100 < 10) {
3337                             $hs_numberformat = $hs_spformat;
3338
3339                             // Allow all digits (specify nought); padding irrelevant:
3340                             //
3341                             $hs_year = $this->_formatNumber(
3342                                 $this->year,
3343                                 $hs_numberformat,
3344                                 0,
3345                                 true,
3346                                 $hb_nosign,
3347                                 $ps_locale
3348                             );
3349                             if (Pear::isError($hs_year)) {
3350                                 return $hs_year;
3351                             }
3352
3353                             $ret .= $hs_year;
3354                         } else {
3355                             // Year is spelled 'Nineteen Twelve' rather than
3356                             // 'One thousand Nine Hundred Twelve':
3357                             //
3358                             $hn_century = intval($this->year / 100);
3359                             $hs_numberformat = $hs_spformat;
3360
3361                             // Allow all digits (specify nought); padding irrelevant:
3362                             //
3363                             $hs_century = $this->_formatNumber(
3364                                 $hn_century,
3365                                 $hs_numberformat,
3366                                 0,
3367                                 true,
3368                                 $hb_nosign,
3369                                 $ps_locale
3370                             );
3371                             if (Pear::isError($hs_century)) {
3372                                 return $hs_century;
3373                             }
3374
3375                             $ret .= $hs_century . " ";
3376
3377                             $hs_numberformat = $hs_spformat;
3378
3379                             // Discard sign; padding irrelevant:
3380                             //
3381                             $hs_year = $this->_formatNumber(
3382                                 $this->year,
3383                                 $hs_numberformat,
3384                                 2,
3385                                 false,
3386                                 true,
3387                                 $ps_locale
3388                             );
3389                             if (Pear::isError($hs_year)) {
3390                                 return $hs_year;
3391                             }
3392
3393                             $ret .= $hs_year;
3394                         }
3395
3396                         $i += 4;
3397                     } else {
3398                         // Code Y(YYY...):
3399                         //
3400                         $hn_codelen = 1;
3401                         while (strtoupper(substr(
3402                                 $ps_format,
3403                                 $i + $hn_codelen,
3404                                 1
3405                             )) == "Y") {
3406                             ++$hn_codelen;
3407                         }
3408
3409                         $hs_thousandsep = null;
3410                         $hn_thousandseps = 0;
3411                         if ($hn_codelen <= 3) {
3412                             while (preg_match(
3413                                 '/([,.�\' ])YYY/i',
3414                                 substr(
3415                                     $ps_format,
3416                                     $i + $hn_codelen,
3417                                     4
3418                                 ),
3419                                 $ha_matches
3420                             )) {
3421                                 $hn_codelen += 4;
3422                                 $hs_thousandsep = $ha_matches[1];
3423                                 ++$hn_thousandseps;
3424                             }
3425                         }
3426
3427                         // Check next code is not 'YEAR'
3428                         //
3429                         if ($hn_codelen > 1 &&
3430                             strtoupper(substr(
3431                                 $ps_format,
3432                                 $i + $hn_codelen - 1,
3433                                 4
3434                             )) == "YEAR") {
3435                             --$hn_codelen;
3436                         }
3437
3438                         $hs_numberformat = substr($ps_format, $i + $hn_codelen, 4);
3439                         $hs_year = $this->_formatNumber(
3440                             $this->year,
3441                             $hs_numberformat,
3442                             $hn_codelen -
3443                             $hn_thousandseps,
3444                             $hb_nopad,
3445                             $hb_nosign,
3446                             $ps_locale,
3447                             $hs_thousandsep
3448                         );
3449                         if (Pear::isError($hs_year)) {
3450                             return $hs_year;
3451                         }
3452
3453                         $ret .= $hs_year;
3454                         $i += $hn_codelen + strlen($hs_numberformat);
3455                     }
3456
3457                     break;
3458                 default:
3459                     $ret .= $hs_char;
3460                     ++$i;
3461                     break;
3462             }
3463         }
3464         return $ret;
3465     }
3466
3467
3468     // }}}
3469     // {{{ formatLikeDate()
3470
3471     /**
3472      * Formats the date according to the specified formatting code string,
3473      * based on {@link http://www.php.net/date date()}
3474      *
3475      * All date() formatting options are supported except '<b>B</b>'.  This
3476      * function also responds to the DATE_* constants, such as DATE_COOKIE,
3477      * which are specified at:
3478      *
3479      *  {@link http://www.php.net/manual/en/datetime.constants.php}
3480      *
3481      *
3482      * Formatting options:
3483      *
3484      * (Day)
3485      *
3486      *   - <b>d</b> - Day of the month, 2 digits with leading zeros (01 to 31)
3487      *   - <b>D</b> - A textual representation of a day, three letters ('Mon'
3488      *                to 'Sun')
3489      *   - <b>j</b> - Day of the month without leading zeros (1 to 31)
3490      *   - <b>l</b> - [lowercase 'L'] A full textual representation of the day
3491      *                of the week ('Sunday' to 'Saturday')
3492      *   - <b>N</b> - ISO-8601 numeric representation of the day of the week
3493      *                (1 (for Monday) to 7 (for Sunday)) (see '<b>W</b>')
3494      *   - <b>S</b> - English ordinal suffix for the day of the month, 2
3495      *                characters ('st', 'nd', 'rd' or 'th')
3496      *   - <b>w</b> - Numeric representation of the day of the week (0 (for
3497      *                Sunday) to 6 (for Saturday))
3498      *   - <b>z</b> - The day of the year, starting from 0 (0 to 365)
3499      *
3500      * (Week)
3501      *
3502      *   - <b>W</b> - {@link http://en.wikipedia.org/wiki/ISO_week_date ISO-8601}
3503      *                week number of year, weeks starting on Monday (00 to 53)
3504      *
3505      * (Month)
3506      *
3507      *   - <b>F</b> - A full textual representation of a month ('January' to
3508      *                'December')
3509      *   - <b>m</b> - Numeric representation of a month, with leading zeros
3510      *                (01 to 12)
3511      *   - <b>M</b> - A short textual representation of a month, three letters
3512      *                ('Jan' to 'Dec')
3513      *   - <b>n</b> - Numeric representation of a month, without leading zeros
3514      *                (1 to 12)
3515      *   - <b>t</b> - Number of days in the given month (28 to 31)
3516      *
3517      * (Year)
3518      *
3519      *   - <b>L</b> - Whether it is a leap year (1 if it is a leap year, 0
3520      *                otherwise)
3521      *   - <b>o</b> - ISO-8601 year number (see '<b>W</b>'). This has the same
3522      *                value as '<b>Y</b>', except that if the ISO week number
3523      *                ('<b>W</b>') belongs to the previous or next year, that
3524      *                year is used instead.
3525      *   - <b>Y</b> - A full numeric representation of a year, 4 digits (0000
3526      *                to 9999)
3527      *   - <b>y</b> - A two digit representation of a year (00 to 99)
3528      *
3529      * (Time)
3530      *
3531      *   - <b>a</b> - Lowercase Ante meridiem and Post meridiem ('am' or
3532      *                'pm')
3533      *   - <b>A</b> - Uppercase Ante meridiem and Post meridiem ('AM' or
3534      *                'PM')
3535      *   - <b>g</b> - 12-hour format of an hour without leading zeros (1 to 12)
3536      *   - <b>G</b> - 24-hour format of an hour without leading zeros (0 to 23)
3537      *   - <b>h</b> - 12-hour format of an hour with leading zeros (01 to 12)
3538      *   - <b>H</b> - 24-hour format of an hour with leading zeros (00 to 23)
3539      *   - <b>i</b> - Minutes with leading zeros (00 to 59)
3540      *   - <b>s</b> - Seconds, with leading zeros (00 to 59)
3541      *   - <b>u</b> - Milliseconds, e.g. '54321'
3542      *
3543      * (Time Zone)
3544      *
3545      *   - <b>e</b> - Timezone identifier, e.g. Europe/London
3546      *   - <b>I</b> - Whether or not the date is in Summer time (1 if Summer
3547      *                time, 0 otherwise)
3548      *   - <b>O</b> - Difference to Greenwich time (GMT) in hours, e.g. '+0200'
3549      *   - <b>P</b> - Difference to Greenwich time (GMT) with colon between
3550      *                hours and minutes, e.g. '+02:00'
3551      *   - <b>T</b> - Timezone abbreviation, e.g. 'GMT', 'EST'
3552      *   - <b>Z</b> - Timezone offset in seconds. The offset for timezones west
3553      *                of UTC is always negative, and for those east of UTC is
3554      *                always positive. (-43200 to 50400)
3555      *
3556      * (Full Date/Time)
3557      *
3558      *   - <b>c</b> - ISO 8601 date, e.g. '2004-02-12T15:19:21+00:00'
3559      *   - <b>r</b> - RFC 2822 formatted date, e.g.
3560      *                'Thu, 21 Dec 2000 16:01:07 +0200'
3561      *   - <b>U</b> - Seconds since the Unix Epoch
3562      *                (January 1 1970 00:00:00 GMT)
3563      *
3564      * @param string $ps_format the format string for returned date/time
3565      *
3566      * @return   string     date/time in given format
3567      * @access   public
3568      * @see      Date::format(), Date::formatLikeStrftime(), Date::formatLikeSQL()
3569      * @since    Method available since Release 1.5.0
3570      */
3571     public function formatLikeDate($ps_format)
3572     {
3573         $hs_formatlikesqlstr = "";
3574
3575         for ($i = 0; $i < strlen($ps_format); ++$i) {
3576             switch ($hs_char = substr($ps_format, $i, 1)) {
3577                 case 'd':
3578                     $hs_formatlikesqlstr .= 'DD';
3579                     break;
3580                 case 'D':
3581                     $hs_formatlikesqlstr .= 'NPDy';
3582                     break;
3583                 case 'j':
3584                     $hs_formatlikesqlstr .= 'NPDD';
3585                     break;
3586                 case 'l':
3587                     $hs_formatlikesqlstr .= 'NPDay';
3588                     break;
3589                 case 'N':
3590                     $hs_formatlikesqlstr .= 'ID';
3591                     break;
3592                 case 'S':
3593                     $hs_formatlikesqlstr .= 'th';
3594                     break;
3595                 case 'w':
3596                     $hs_formatlikesqlstr .= 'D';
3597                     break;
3598                 case 'z':
3599                     $hs_formatlikesqlstr .= '"' . ($this->getDayOfYear() - 1) . '"';
3600                     break;
3601                 case 'W':
3602                     $hs_formatlikesqlstr .= 'IW';
3603                     break;
3604                 case 'F':
3605                     $hs_formatlikesqlstr .= 'NPMonth';
3606                     break;
3607                 case 'm':
3608                     $hs_formatlikesqlstr .= 'MM';
3609                     break;
3610                 case 'M':
3611                     $hs_formatlikesqlstr .= 'NPMon';
3612                     break;
3613                 case 'n':
3614                     $hs_formatlikesqlstr .= 'NPMM';
3615                     break;
3616                 case 't':
3617                     $hs_formatlikesqlstr .= '"' . $this->getDaysInMonth() . '"';
3618                     break;
3619                 case 'L':
3620                     $hs_formatlikesqlstr .= '"' . ($this->isLeapYear() ? 1 : 0) . '"';
3621                     break;
3622                 case 'o':
3623                     $hs_formatlikesqlstr .= 'IYYY';
3624                     break;
3625                 case 'Y':
3626                     $hs_formatlikesqlstr .= 'YYYY';
3627                     break;
3628                 case 'y':
3629                     $hs_formatlikesqlstr .= 'YY';
3630                     break;
3631                 case 'a':
3632                     $hs_formatlikesqlstr .= 'am';
3633                     break;
3634                 case 'A':
3635                     $hs_formatlikesqlstr .= 'AM';
3636                     break;
3637                 case 'g':
3638                     $hs_formatlikesqlstr .= 'NPHH12';
3639                     break;
3640                 case 'G':
3641                     $hs_formatlikesqlstr .= 'NPHH24';
3642                     break;
3643                 case 'h':
3644                     $hs_formatlikesqlstr .= 'HH12';
3645                     break;
3646                 case 'H':
3647                     $hs_formatlikesqlstr .= 'HH24';
3648                     break;
3649                 case 'i':
3650                     $hs_formatlikesqlstr .= 'MI';
3651                     break;
3652                 case 's':
3653                     $hs_formatlikesqlstr .= 'SS';
3654                     break;
3655                 case 'u':
3656                     $hs_formatlikesqlstr .= 'SSFFF';
3657                     break;
3658                 case 'e':
3659                     $hs_formatlikesqlstr .= 'TZR';
3660                     break;
3661                 case 'I':
3662                     $hs_formatlikesqlstr .= 'TZI';
3663                     break;
3664                 case 'O':
3665                     $hs_formatlikesqlstr .= 'STZHTZM';
3666                     break;
3667                 case 'P':
3668                     $hs_formatlikesqlstr .= 'STZH:TZM';
3669                     break;
3670                 case 'T':
3671                     $hs_formatlikesqlstr .= 'TZC';
3672                     break;
3673                 case 'Z':
3674                     $hs_formatlikesqlstr .= 'TZS';
3675                     break;
3676                 case 'c':
3677                     $hs_formatlikesqlstr .= 'YYYY-MM-DD"T"HH24:MI:SSSTZH:TZM';
3678                     break;
3679                 case 'r':
3680                     $hs_formatlikesqlstr .= 'Dy, DD Mon YYYY HH24:MI:SS STZHTZM';
3681                     break;
3682                 case 'U':
3683                     $hs_formatlikesqlstr .= 'U';
3684                     break;
3685                 case '\\':
3686                     $hs_char = substr($ps_format, ++$i, 1);
3687                     $hs_formatlikesqlstr .= '"' .
3688                         ($hs_char == '\\' ? '\\\\' : $hs_char) .
3689                         '"';
3690                     break;
3691                 case '"':
3692                     $hs_formatlikesqlstr .= '"\\""';
3693                     break;
3694                 default:
3695                     $hs_formatlikesqlstr .= '"' . $hs_char . '"';
3696             }
3697         }
3698
3699         $ret = $this->formatLikeSQL($hs_formatlikesqlstr);
3700         if (PEAR::isError($ret) &&
3701             $ret->getCode() == DATE_ERROR_INVALIDFORMATSTRING) {
3702             return PEAR::raiseError(
3703                 "Invalid date format '$ps_format'",
3704                 DATE_ERROR_INVALIDFORMATSTRING
3705             );
3706         }
3707
3708         return $ret;
3709     }
3710
3711
3712     // }}}
3713     // {{{ setFromTime()
3714
3715     /**
3716      * Sets the date/time using a Unix time-stamp
3717      *
3718      * This may only be valid for dates from 1970 to ~2038.  N.B. this
3719      * function makes a call to {@link http://www.php.net/gmdate gmdate()}
3720      *
3721      * @param int $pn_timestamp Unix time-stamp
3722      *
3723      * @return   void
3724      * @access   public
3725      * @see      Date::getTime(), Date::setDate()
3726      */
3727     public function setFromTime($pn_timestamp)
3728     {
3729         // Unix Time; N.B. Unix Time is defined relative to GMT,
3730         // so it needs to be adjusted for the current time zone;
3731         // however we do not know if it is in Summer time until
3732         // we have converted it from Unix time:
3733         //
3734
3735         // Get current time zone details:
3736         //
3737         $hs_id = $this->getTZID();
3738
3739         // Input Unix time as UTC:
3740         //
3741         $this->tz = new Date_TimeZone("UTC");
3742         $this->setDate(gmdate("Y-m-d H:i:s", $pn_timestamp));
3743
3744         // Convert back to correct time zone:
3745         //
3746         $this->convertTZByID($hs_id);
3747     }
3748
3749
3750     // }}}
3751     // {{{ getTime()
3752
3753     /**
3754      * Returns the date/time as Unix time-stamp (as returned for example by
3755      * {@link http://www.php.net/time time()})
3756      *
3757      * This may only be valid for dates from 1970 to ~2038.  N.B. this
3758      * function makes a call to {@link http://www.php.net/gmmktime gmmktime()}
3759      *
3760      * @return   int        number of seconds since the Unix epoch
3761      * @access   public
3762      */
3763     public function getTime()
3764     {
3765         if ($this->ob_invalidtime) {
3766             $ret = $this->_getErrorInvalidTime();
3767         } else {
3768             // Use 'gmmktime()' and offset result (to get UTC):
3769             //
3770             return gmmktime(
3771                     $this->on_standardhour,
3772                     $this->on_standardminute,
3773                     $this->on_standardsecond,
3774                     $this->on_standardmonth,
3775                     $this->on_standardday,
3776                     $this->on_standardyear
3777                 ) -
3778                 $this->tz->getRawOffset() / 1000; // N.B. Unix-time excludes
3779             // leap seconds by
3780             // definition
3781         }
3782     }
3783
3784
3785     // }}}
3786     // {{{ getTZID()
3787
3788     /**
3789      * Returns the unique ID of the time zone, e.g. 'America/Chicago'
3790      *
3791      * @return   string     the time zone ID
3792      * @access   public
3793      * @see      Date::setTZByID(), Date::getTZLongName(),
3794      *            Date::getTZShortName(), Date_TimeZone
3795      * @since    Method available since Release 1.5.0
3796      */
3797     public function getTZID()
3798     {
3799         return $this->tz->getID();
3800     }
3801
3802
3803     // }}}
3804     // {{{ _setTZToDefault()
3805
3806     /**
3807      * sets time zone to the default time zone
3808      *
3809      * If PHP version >= 5.1.0, uses date_default_timezone_get(),
3810      * else the value returned by
3811      * '{@link http://www.php.net/date date("e")}'
3812      * if valid, else the default specified if the global
3813      * constant '$GLOBALS["_DATE_TIMEZONE_DEFAULT"]', which if itself
3814      * left unset, defaults to "UTC".
3815      *
3816      * N.B. this is a private method; to set the time zone to the
3817      * default publicly you should call '{@link Date::setTZByID()}',
3818      * that is, with no parameter (or a parameter of null).
3819      *
3820      * @return   void
3821      * @access   private
3822      * @since    Method available since Release 1.5.0
3823      */
3824     public function _setTZToDefault()
3825     {
3826         if (function_exists('version_compare') &&
3827             version_compare(phpversion(), "5.1.0", ">=") &&
3828             (
3829                 Date_TimeZone::isValidID($hs_id = date_default_timezone_get()) ||
3830                 Date_TimeZone::isValidID($hs_id = date("e"))
3831             )
3832         ) {
3833             $this->tz = new Date_TimeZone($hs_id);
3834         } else {
3835             $this->tz = Date_TimeZone::getDefault();
3836         }
3837     }
3838
3839
3840     // }}}
3841     // {{{ setTZ()
3842
3843     /**
3844      * Sets the time zone of this Date
3845      *
3846      * Sets the time zone of this date with the given
3847      * Date_TimeZone object.  Does not alter the date/time,
3848      * only assigns a new time zone.  For conversion, use
3849      * {@link Date::convertTZ()}.
3850      *
3851      * @param object $tz the Date_TimeZone object to use.  If called with a
3852      *                    parameter that is not a Date_TimeZone object, will
3853      *                    fall through to setTZByID().
3854      *
3855      * @return   void
3856      * @access   public
3857      * @see      Date::setTZByID(), Date::convertTZ(),
3858      *            Date_TimeZone::Date_TimeZone(), Date_TimeZone
3859      */
3860     public function setTZ($tz)
3861     {
3862         if (is_a($tz, 'Date_Timezone')) {
3863             $this->setTZByID($tz->getID());
3864         } else {
3865             $res = $this->setTZByID($tz);
3866             if (PEAR::isError($res)) {
3867                 return $res;
3868             }
3869         }
3870     }
3871
3872
3873     // }}}
3874     // {{{ setTZByID()
3875
3876     /**
3877      * Sets the time zone of this date with the given time zone ID
3878      *
3879      * The time zone IDs are drawn from the 'tz data-base' (see
3880      * {@link http://en.wikipedia.org/wiki/Zoneinfo}), which is the de facto
3881      * internet and IT standard.  (There is no official standard, and
3882      * the tz data-base is not intended to be a regulating body
3883      * anyway.)  Lists of valid IDs are maintained at:
3884      *
3885      *  - {@link http://en.wikipedia.org/wiki/List_of_zoneinfo_timezones}
3886      *  - {@link http://www.php.net/manual/en/timezones.php}
3887      *
3888      * If no time-zone is specified and PHP version >= 5.1.0, the time
3889      * zone is set automatically to the output of date_default_timezone_get()
3890      * if set and valid, else the value returned by
3891      * '{@link http://www.php.net/date date("e")}'
3892      * if valid, else the default specified if the global
3893      * constant '$GLOBALS["_DATE_TIMEZONE_DEFAULT"]', which if itself
3894      * left unset, defaults to "UTC".
3895      *
3896      * N.B. this function preserves the local date and time, that is,
3897      * whether in local Summer time or local standard time.  For example,
3898      * if the time is set to 11.00 Summer time, and the time zone is then
3899      * set to another time zone, using this function, in which the date
3900      * falls in standard time, then the time will remain set to 11.00 UTC,
3901      * and not 10.00.  You can convert a date to another time zone by
3902      * calling '{@link Date::convertTZ()}', which preserves the actual
3903      * time as measured against UTC.
3904      *
3905      * The ID can also be specified as a UTC offset in one of the following
3906      * forms, i.e. an offset with no geographical or political base:
3907      *
3908      *  - <b>UTC[+/-][h]</b>       - e.g. UTC-1     (the preferred form)
3909      *  - <b>UTC[+/-][hh]</b>      - e.g. UTC+03
3910      *  - <b>UTC[+/-][hh][mm]</b>  - e.g. UTC-0530
3911      *  - <b>UTC[+/-][hh]:[mm]</b> - e.g. UTC+03:00
3912      *
3913      * N.B. 'UTC' seems to be technically preferred over 'GMT'.  GMT-based
3914      * IDs still exist in the tz data-base, but beware of POSIX-style
3915      * offsets which are the opposite way round to what people normally
3916      * expect.
3917      *
3918      * @param string $ps_id a valid time zone id, e.g. 'Europe/London'
3919      *
3920      * @return   void
3921      * @access   public
3922      * @see      Date::getTZID(), Date::setTZ(), Date::convertTZByID(),
3923      *            Date_TimeZone::isValidID(), Date_TimeZone::Date_TimeZone(),
3924      *            Date_TimeZone
3925      */
3926     public function setTZByID($ps_id = null)
3927     {
3928         // Whether the date is in Summer time forms the default for
3929         // the new time zone (if needed, which is very unlikely anyway).
3930         // This is mainly to prevent unexpected (defaulting) behaviour
3931         // if the user is in the repeated hour, and switches to a time
3932         // zone that is also in the repeated hour (e.g. 'Europe/London'
3933         // and 'Europe/Lisbon').
3934         //
3935         $hb_insummertime = $this->inDaylightTime();
3936         if (PEAR::isError($hb_insummertime)) {
3937             if ($hb_insummertime->getCode() == DATE_ERROR_INVALIDTIME) {
3938                 $hb_insummertime = false;
3939             } else {
3940                 return $hb_insummertime;
3941             }
3942         }
3943
3944         if (is_null($ps_id)) {
3945             $this->_setTZToDefault();
3946         } elseif (Date_TimeZone::isValidID($ps_id)) {
3947             $this->tz = new Date_TimeZone($ps_id);
3948         } else {
3949             return PEAR::raiseError(
3950                 "Invalid time zone ID '$ps_id'",
3951                 DATE_ERROR_INVALIDTIMEZONE
3952             );
3953         }
3954
3955         $this->setLocalTime(
3956             $this->day,
3957             $this->month,
3958             $this->year,
3959             $this->hour,
3960             $this->minute,
3961             $this->second,
3962             $this->partsecond,
3963             $hb_insummertime
3964         );
3965     }
3966
3967
3968     // }}}
3969     // {{{ getTZLongName()
3970
3971     /**
3972      * Returns the long name of the time zone
3973      *
3974      * Returns long form of time zone name, e.g. 'Greenwich Mean Time'.
3975      * N.B. if the date falls in Summer time, the Summer time name will be
3976      * returned instead, e.g. 'British Summer Time'.
3977      *
3978      * N.B. this is not a unique identifier for the time zone - for this
3979      * purpose use the time zone ID.
3980      *
3981      * @return   string     the long name of the time zone
3982      * @access   public
3983      * @see      Date::getTZID(), Date::getTZShortName(),
3984      *            Date_TimeZone::getLongName()
3985      * @since    Method available since Release 1.5.0
3986      */
3987     public function getTZLongName()
3988     {
3989         if ($this->ob_invalidtime) {
3990             return $this->_getErrorInvalidTime();
3991         }
3992
3993         return $this->tz->getLongName($this->inDaylightTime());
3994     }
3995
3996
3997     // }}}
3998     // {{{ getTZShortName()
3999
4000     /**
4001      * Returns the short name of the time zone
4002      *
4003      * Returns abbreviated form of time zone name, e.g. 'GMT'.  N.B. if the
4004      * date falls in Summer time, the Summer time name will be returned
4005      * instead, e.g. 'BST'.
4006      *
4007      * N.B. this is not a unique identifier - for this purpose use the
4008      * time zone ID.
4009      *
4010      * @return   string     the short name of the time zone
4011      * @access   public
4012      * @see      Date::getTZID(), Date::getTZLongName(),
4013      *            Date_TimeZone::getShortName()
4014      * @since    Method available since Release 1.5.0
4015      */
4016     public function getTZShortName()
4017     {
4018         if ($this->ob_invalidtime) {
4019             return $this->_getErrorInvalidTime();
4020         }
4021
4022         return $this->tz->getShortName($this->inDaylightTime());
4023     }
4024
4025
4026     // }}}
4027     // {{{ getTZOffset()
4028
4029     /**
4030      * Returns the DST-corrected offset from UTC for the given date
4031      *
4032      * Gets the offset to UTC for a given date/time, taking into
4033      * account daylight savings time, if the time zone observes it and if
4034      * it is in effect.
4035      *
4036      * N.B. that the offset is calculated historically
4037      * and in the future according to the current Summer time rules,
4038      * and so this function is proleptically correct, but not necessarily
4039      * historically correct.  (Although if you want to be correct about
4040      * times in the distant past, this class is probably not for you
4041      * because the whole notion of time zones does not apply, and
4042      * historically there are so many time zone changes, Summer time
4043      * rule changes, name changes, calendar changes, that calculating
4044      * this sort of information is beyond the scope of this package
4045      * altogether.)
4046      *
4047      * @return   int        the corrected offset to UTC in milliseconds
4048      * @access   public
4049      * @see      Date_TimeZone::getOffset()
4050      * @since    Method available since Release 1.5.0
4051      */
4052     public function getTZOffset()
4053     {
4054         if ($this->ob_invalidtime) {
4055             return $this->_getErrorInvalidTime();
4056         }
4057
4058         return $this->tz->getOffset($this->inDaylightTime());
4059     }
4060
4061
4062     // }}}
4063     // {{{ inDaylightTime()
4064
4065     /**
4066      * Tests if this date/time is in DST
4067      *
4068      * Returns true if daylight savings time is in effect for
4069      * this date in this date's time zone.
4070      *
4071      * @param bool $pb_repeatedhourdefault value to return if repeated hour is
4072      *                                      specified (defaults to false)
4073      *
4074      * @return   boolean    true if DST is in effect for this date
4075      * @access   public
4076      * @see      Date_TimeZone::hasDaylightTime(), Date_TimeZone::inDaylightTime()
4077      */
4078     public function inDaylightTime($pb_repeatedhourdefault = false)
4079     {
4080         if (!$this->tz->hasDaylightTime()) {
4081             return false;
4082         }
4083         if ($this->ob_invalidtime) {
4084             return $this->_getErrorInvalidTime();
4085         }
4086
4087         // The return value is 'cached' whenever the date/time is set:
4088         //
4089         return $this->hour != $this->on_standardhour ||
4090             $this->minute != $this->on_standardminute ||
4091             $this->second != $this->on_standardsecond ||
4092             $this->partsecond != $this->on_standardpartsecond ||
4093             $this->day != $this->on_standardday ||
4094             $this->month != $this->on_standardmonth ||
4095             $this->year != $this->on_standardyear;
4096         //
4097         // (these last 3 conditions are theoretical
4098         // possibilities but normally will never occur)
4099     }
4100
4101
4102     // }}}
4103     // {{{ convertTZ()
4104
4105     /**
4106      * Converts this date to a new time zone
4107      *
4108      * Previously this might not have worked correctly if your system did
4109      * not allow {@link http://www.php.net/putenv putenv()} or if
4110      * {@link http://www.php.net/localtime localtime()} did not work in
4111      * your environment, but this implementation is no longer used.
4112      *
4113      * @param object $tz Date_TimeZone object to convert to
4114      *
4115      * @return   void
4116      * @access   public
4117      * @see      Date::convertTZByID(), Date::toUTC(),
4118      *            Date_TimeZone::Date_TimeZone(), Date_TimeZone
4119      */
4120     public function convertTZ($tz)
4121     {
4122         if ($this->getTZID() == $tz->getID()) {
4123             return;
4124         }
4125         if ($this->ob_invalidtime) {
4126             return $this->_getErrorInvalidTime();
4127         }
4128
4129         $hn_rawoffset = $tz->getRawOffset() - $this->tz->getRawOffset();
4130         $this->tz = new Date_TimeZone($tz->getID());
4131
4132         list($hn_standardyear,
4133             $hn_standardmonth,
4134             $hn_standardday,
4135             $hn_standardhour,
4136             $hn_standardminute,
4137             $hn_standardsecond,
4138             $hn_standardpartsecond) =
4139             $this->_addOffset(
4140                 $hn_rawoffset,
4141                 $this->on_standardday,
4142                 $this->on_standardmonth,
4143                 $this->on_standardyear,
4144                 $this->on_standardhour,
4145                 $this->on_standardminute,
4146                 $this->on_standardsecond,
4147                 $this->on_standardpartsecond
4148             );
4149
4150         $this->setStandardTime(
4151             $hn_standardday,
4152             $hn_standardmonth,
4153             $hn_standardyear,
4154             $hn_standardhour,
4155             $hn_standardminute,
4156             $hn_standardsecond,
4157             $hn_standardpartsecond
4158         );
4159     }
4160
4161
4162     // }}}
4163     // {{{ toUTC()
4164
4165     /**
4166      * Converts this date to UTC and sets this date's timezone to UTC
4167      *
4168      * @return   void
4169      * @access   public
4170      * @see      Date::convertTZ(), Date::convertTZByID(), Date::toUTCbyOffset()
4171      */
4172     public function toUTC()
4173     {
4174         if ($this->getTZID() == "UTC") {
4175             return;
4176         }
4177         if ($this->ob_invalidtime) {
4178             return $this->_getErrorInvalidTime();
4179         }
4180
4181         $res = $this->convertTZ(new Date_TimeZone("UTC"));
4182         if (PEAR::isError($res)) {
4183             return $res;
4184         }
4185     }
4186
4187
4188     // }}}
4189     // {{{ convertTZByID()
4190
4191     /**
4192      * Converts this date to a new time zone, given a valid time zone ID
4193      *
4194      * Previously this might not have worked correctly if your system did
4195      * not allow {@link http://www.php.net/putenv putenv()} or if
4196      * {@link http://www.php.net/localtime localtime()} did not work
4197      * in your environment, but this implementation is no longer used.
4198      *
4199      * @param string $ps_id a valid time zone id, e.g. 'Europe/London'
4200      *
4201      * @return   void
4202      * @access   public
4203      * @see      Date::convertTZ(), Date::toUTC(), Date::setTZByID(),
4204      *            Date_TimeZone::isValidID(), Date_TimeZone::Date_TimeZone(),
4205      *            Date_TimeZone
4206      */
4207     public function convertTZByID($ps_id)
4208     {
4209         if (!Date_TimeZone::isValidID($ps_id)) {
4210             return PEAR::raiseError(
4211                 "Invalid time zone ID '$ps_id'",
4212                 DATE_ERROR_INVALIDTIMEZONE
4213             );
4214         }
4215
4216         $res = $this->convertTZ(new Date_TimeZone($ps_id));
4217
4218         if (PEAR::isError($res)) {
4219             return $res;
4220         }
4221     }
4222
4223
4224     // }}}
4225     // {{{ toUTCbyOffset()
4226
4227     /**
4228      * Converts the date/time to UTC by the offset specified
4229      *
4230      * This function is no longer called from within the Date class
4231      * itself because a time zone can be set using a pure offset
4232      * (e.g. UTC+1), i.e. not a geographical time zone.  However
4233      * it is retained for backwards compaibility.
4234      *
4235      * @param string $ps_offset offset of the form '<b>[+/-][hh]:[mm]</b>',
4236      *                           '<b>[+/-][hh][mm]</b>', or '<b>Z</b>'
4237      *
4238      * @return   bool
4239      * @access   private
4240      * @see      Date::toUTC(), Date::convertTZ(), Date::convertTZByID()
4241      */
4242     public function toUTCbyOffset($ps_offset)
4243     {
4244         if ($ps_offset == "Z" ||
4245             preg_match('/^[+\-](00:?00|0{1,2})$/', $ps_offset)) {
4246             $hs_tzid = "UTC";
4247         } elseif (preg_match(
4248             '/^[+\-]([0-9]{2,2}:?[0-5][0-9]|[0-9]{1,2})$/',
4249             $ps_offset
4250         )) {
4251             $hs_tzid = "UTC" . $ps_offset;
4252         } else {
4253             return PEAR::raiseError("Invalid offset '$ps_offset'");
4254         }
4255
4256         // If the time is invalid, it does not matter here:
4257         //
4258         $this->setTZByID($hs_tzid);
4259
4260         // Now the time will be valid because it is a time zone that
4261         // does not observe Summer time:
4262         //
4263         $this->toUTC();
4264     }
4265
4266
4267     // }}}
4268     // {{{ addYears()
4269
4270     /**
4271      * Converts the date to the specified no of years from the given date
4272      *
4273      * To subtract years use a negative value for the '$pn_years'
4274      * parameter
4275      *
4276      * @param int $pn_years years to add
4277      *
4278      * @return   void
4279      * @access   public
4280      * @since    Method available since Release 1.5.0
4281      */
4282     public function addYears($pn_years)
4283     {
4284         list($hs_year, $hs_month, $hs_day) =
4285             explode(" ", Date_Calc::addYears(
4286                 $pn_years,
4287                 $this->day,
4288                 $this->month,
4289                 $this->year,
4290                 "%Y %m %d"
4291             ));
4292         $this->setLocalTime(
4293             $hs_day,
4294             $hs_month,
4295             $hs_year,
4296             $this->hour,
4297             $this->minute,
4298             $this->second,
4299             $this->partsecond
4300         );
4301     }
4302
4303
4304     // }}}
4305     // {{{ addMonths()
4306
4307     /**
4308      * Converts the date to the specified no of months from the given date
4309      *
4310      * To subtract months use a negative value for the '$pn_months'
4311      * parameter
4312      *
4313      * @param int $pn_months months to add
4314      *
4315      * @return   void
4316      * @access   public
4317      * @since    Method available since Release 1.5.0
4318      */
4319     public function addMonths($pn_months)
4320     {
4321         list($hs_year, $hs_month, $hs_day) =
4322             explode(" ", Date_Calc::addMonths(
4323                 $pn_months,
4324                 $this->day,
4325                 $this->month,
4326                 $this->year,
4327                 "%Y %m %d"
4328             ));
4329         $this->setLocalTime(
4330             $hs_day,
4331             $hs_month,
4332             $hs_year,
4333             $this->hour,
4334             $this->minute,
4335             $this->second,
4336             $this->partsecond
4337         );
4338     }
4339
4340
4341     // }}}
4342     // {{{ addDays()
4343
4344     /**
4345      * Converts the date to the specified no of days from the given date
4346      *
4347      * To subtract days use a negative value for the '$pn_days' parameter
4348      *
4349      * @param int $pn_days days to add
4350      *
4351      * @return   void
4352      * @access   public
4353      * @since    Method available since Release 1.5.0
4354      */
4355     public function addDays($pn_days)
4356     {
4357         list($hs_year, $hs_month, $hs_day) =
4358             explode(" ", Date_Calc::addDays(
4359                 $pn_days,
4360                 $this->day,
4361                 $this->month,
4362                 $this->year,
4363                 "%Y %m %d"
4364             ));
4365         $this->setLocalTime(
4366             $hs_day,
4367             $hs_month,
4368             $hs_year,
4369             $this->hour,
4370             $this->minute,
4371             $this->second,
4372             $this->partsecond
4373         );
4374     }
4375
4376
4377     // }}}
4378     // {{{ addHours()
4379
4380     /**
4381      * Converts the date to the specified no of hours from the given date
4382      *
4383      * To subtract hours use a negative value for the '$pn_hours' parameter
4384      *
4385      * @param int $pn_hours hours to add
4386      *
4387      * @return   void
4388      * @access   public
4389      * @since    Method available since Release 1.5.0
4390      */
4391     public function addHours($pn_hours)
4392     {
4393         if ($this->ob_invalidtime) {
4394             return $this->_getErrorInvalidTime();
4395         }
4396
4397         list($hn_standardyear,
4398             $hn_standardmonth,
4399             $hn_standardday,
4400             $hn_standardhour) =
4401             Date_Calc::addHours(
4402                 $pn_hours,
4403                 $this->on_standardday,
4404                 $this->on_standardmonth,
4405                 $this->on_standardyear,
4406                 $this->on_standardhour
4407             );
4408
4409         $this->setStandardTime(
4410             $hn_standardday,
4411             $hn_standardmonth,
4412             $hn_standardyear,
4413             $hn_standardhour,
4414             $this->on_standardminute,
4415             $this->on_standardsecond,
4416             $this->on_standardpartsecond
4417         );
4418     }
4419
4420
4421     // }}}
4422     // {{{ addMinutes()
4423
4424     /**
4425      * Converts the date to the specified no of minutes from the given date
4426      *
4427      * To subtract minutes use a negative value for the '$pn_minutes' parameter
4428      *
4429      * @param int $pn_minutes minutes to add
4430      *
4431      * @return   void
4432      * @access   public
4433      * @since    Method available since Release 1.5.0
4434      */
4435     public function addMinutes($pn_minutes)
4436     {
4437         if ($this->ob_invalidtime) {
4438             return $this->_getErrorInvalidTime();
4439         }
4440
4441         list($hn_standardyear,
4442             $hn_standardmonth,
4443             $hn_standardday,
4444             $hn_standardhour,
4445             $hn_standardminute) =
4446             Date_Calc::addMinutes(
4447                 $pn_minutes,
4448                 $this->on_standardday,
4449                 $this->on_standardmonth,
4450                 $this->on_standardyear,
4451                 $this->on_standardhour,
4452                 $this->on_standardminute
4453             );
4454
4455         $this->setStandardTime(
4456             $hn_standardday,
4457             $hn_standardmonth,
4458             $hn_standardyear,
4459             $hn_standardhour,
4460             $hn_standardminute,
4461             $this->on_standardsecond,
4462             $this->on_standardpartsecond
4463         );
4464     }
4465
4466
4467     // }}}
4468     // {{{ addSeconds()
4469
4470     /**
4471      * Adds a given number of seconds to the date
4472      *
4473      * @param mixed $sec the no of seconds to add as integer or float
4474      * @param bool $pb_countleap whether to count leap seconds (defaults to
4475      *                             value of count-leap-second object property)
4476      *
4477      * @return   void
4478      * @access   public
4479      */
4480     public function addSeconds($sec, $pb_countleap = null)
4481     {
4482         if ($this->ob_invalidtime) {
4483             return $this->_getErrorInvalidTime();
4484         }
4485         if (!is_int($sec) && !is_float($sec)) {
4486             settype($sec, 'int');
4487         }
4488         if (!is_null($pb_countleap)) {
4489             $pb_countleap = $this->ob_countleapseconds;
4490         }
4491
4492         if ($pb_countleap) {
4493             // Convert to UTC:
4494             //
4495             list($hn_standardyear,
4496                 $hn_standardmonth,
4497                 $hn_standardday,
4498                 $hn_standardhour,
4499                 $hn_standardminute,
4500                 $hn_standardsecond,
4501                 $hn_standardpartsecond) =
4502                 $this->_addOffset(
4503                     $this->tz->getRawOffset() * -1,
4504                     $this->on_standardday,
4505                     $this->on_standardmonth,
4506                     $this->on_standardyear,
4507                     $this->on_standardhour,
4508                     $this->on_standardminute,
4509                     $this->on_standardsecond,
4510                     $this->on_standardpartsecond
4511                 );
4512             list($hn_standardyear,
4513                 $hn_standardmonth,
4514                 $hn_standardday,
4515                 $hn_standardhour,
4516                 $hn_standardminute,
4517                 $hn_secondraw) =
4518                 Date_Calc::addSeconds(
4519                     $sec,
4520                     $hn_standardday,
4521                     $hn_standardmonth,
4522                     $hn_standardyear,
4523                     $hn_standardhour,
4524                     $hn_standardminute,
4525                     $hn_standardpartsecond == 0.0 ?
4526                         $hn_standardsecond :
4527                         $hn_standardsecond +
4528                         $hn_standardpartsecond,
4529                     $pb_countleap
4530                 );
4531
4532             if (is_float($hn_secondraw)) {
4533                 $hn_standardsecond = intval($hn_secondraw);
4534                 $hn_standardpartsecond = $hn_secondraw - $hn_standardsecond;
4535             } else {
4536                 $hn_standardsecond = $hn_secondraw;
4537                 $hn_standardpartsecond = 0.0;
4538             }
4539
4540             list($hn_standardyear,
4541                 $hn_standardmonth,
4542                 $hn_standardday,
4543                 $hn_standardhour,
4544                 $hn_standardminute,
4545                 $hn_standardsecond,
4546                 $hn_standardpartsecond) =
4547                 $this->_addOffset(
4548                     $this->tz->getRawOffset(),
4549                     $hn_standardday,
4550                     $hn_standardmonth,
4551                     $hn_standardyear,
4552                     $hn_standardhour,
4553                     $hn_standardminute,
4554                     $hn_standardsecond,
4555                     $hn_standardpartsecond
4556                 );
4557         } else {
4558             // Use local standard time:
4559             //
4560             list($hn_standardyear,
4561                 $hn_standardmonth,
4562                 $hn_standardday,
4563                 $hn_standardhour,
4564                 $hn_standardminute,
4565                 $hn_secondraw) =
4566                 Date_Calc::addSeconds(
4567                     $sec,
4568                     $this->on_standardday,
4569                     $this->on_standardmonth,
4570                     $this->on_standardyear,
4571                     $this->on_standardhour,
4572                     $this->on_standardminute,
4573                     $this->on_standardpartsecond == 0.0 ?
4574                         $this->on_standardsecond :
4575                         $this->on_standardsecond +
4576                         $this->on_standardpartsecond,
4577                     false
4578                 );
4579
4580             if (is_float($hn_secondraw)) {
4581                 $hn_standardsecond = intval($hn_secondraw);
4582                 $hn_standardpartsecond = $hn_secondraw - $hn_standardsecond;
4583             } else {
4584                 $hn_standardsecond = $hn_secondraw;
4585                 $hn_standardpartsecond = 0.0;
4586             }
4587         }
4588
4589         $this->setStandardTime(
4590             $hn_standardday,
4591             $hn_standardmonth,
4592             $hn_standardyear,
4593             $hn_standardhour,
4594             $hn_standardminute,
4595             $hn_standardsecond,
4596             $hn_standardpartsecond
4597         );
4598     }
4599
4600
4601     // }}}
4602     // {{{ subtractSeconds()
4603
4604     /**
4605      * Subtracts a given number of seconds from the date
4606      *
4607      * @param mixed $sec the no of seconds to subtract as integer or
4608      *                             float
4609      * @param bool $pb_countleap whether to count leap seconds (defaults to
4610      *                             value of count-leap-second object property)
4611      *
4612      * @return   void
4613      * @access   public
4614      */
4615     public function subtractSeconds($sec, $pb_countleap = null)
4616     {
4617         if (is_null($pb_countleap)) {
4618             $pb_countleap = $this->ob_countleapseconds;
4619         }
4620
4621         $res = $this->addSeconds(-$sec, $pb_countleap);
4622
4623         if (PEAR::isError($res)) {
4624             return $res;
4625         }
4626     }
4627
4628
4629     // }}}
4630     // {{{ addSpan()
4631
4632     /**
4633      * Adds a time span to the date
4634      *
4635      * A time span is defined as a unsigned no of days, hours, minutes
4636      * and seconds, where the no of minutes and seconds must be less than
4637      * 60, and the no of hours must be less than 24.
4638      *
4639      * A span is added (and subtracted) according to the following logic:
4640      *
4641      *  Hours, minutes and seconds are added such that if they fall over
4642      *   a leap second, the leap second is ignored, and not counted.
4643      *   For example, if a leap second occurred at 23.59.60, the
4644      *   following calculations:
4645      *
4646      *    - 23.59.59 + one second
4647      *    - 23.59.00 + one minute
4648      *    - 23.00.00 + one hour
4649      *
4650      *   would all produce 00.00.00 the next day.
4651      *
4652      *  A day is treated as equivalent to 24 hours, so if the clocks
4653      *   went backwards at 01.00, and one day was added to the time
4654      *   00.30, the result would be 23.30 the same day.
4655      *
4656      * This is the implementation which is thought to yield the behaviour
4657      * that the user is most likely to expect, or in another way of
4658      * looking at it, it is the implementation that produces the least
4659      * unexpected behaviour.  It basically works in hours, that is, a day
4660      * is treated as exactly equivalent to 24 hours, and minutes and
4661      * seconds are treated as equivalent to 1/60th and 1/3600th of an
4662      * hour.  It should be obvious that working in days is impractical;
4663      * working in seconds is problematic when it comes to adding days
4664      * that fall over leap seconds, where it would appear to most users
4665      * that the function adds only 23 hours, 59 minutes and 59 seconds.
4666      * It is also problematic to work in any kind of mixture of days,
4667      * hours, minutes, and seconds, because then the addition of a span
4668      * would sometimes depend on which order you add the constituent
4669      * parts, which undermines the concept of a span altogether.
4670      *
4671      * If you want alternative functionality, you must use a mixture of
4672      * the following functions instead:
4673      *
4674      *  - {@link Date::addYears()}
4675      *  - {@link Date::addMonths()}
4676      *  - {@link Date::addDays()}
4677      *  - {@link Date::addHours()}
4678      *  - {@link Date::addMinutes()}
4679      *  - {@link Date::addSeconds()}
4680      *
4681      * @param object $span the time span to add
4682      *
4683      * @return   void
4684      * @access   public
4685      * @see      Date_Span
4686      */
4687     public function addSpan($span)
4688     {
4689         if (!is_a($span, 'Date_Span')) {
4690             return PEAR::raiseError("Invalid argument - not 'Date_Span' object");
4691         } elseif ($this->ob_invalidtime) {
4692             return $this->_getErrorInvalidTime();
4693         }
4694
4695         $hn_days = $span->day;
4696         $hn_standardhour = $this->on_standardhour + $span->hour;
4697         $hn_standardminute = $this->on_standardminute + $span->minute;
4698         $hn_standardsecond = $this->on_standardsecond + $span->second;
4699
4700         if ($hn_standardsecond >= 60) {
4701             ++$hn_standardminute;
4702             $hn_standardsecond -= 60;
4703         }
4704
4705         if ($hn_standardminute >= 60) {
4706             ++$hn_standardhour;
4707             $hn_standardminute -= 60;
4708         }
4709
4710         if ($hn_standardhour >= 24) {
4711             ++$hn_days;
4712             $hn_standardhour -= 24;
4713         }
4714
4715         list($hn_standardyear, $hn_standardmonth, $hn_standardday) =
4716             explode(
4717                 " ",
4718                 Date_Calc::addDays(
4719                     $hn_days,
4720                     $this->on_standardday,
4721                     $this->on_standardmonth,
4722                     $this->on_standardyear,
4723                     "%Y %m %d"
4724                 )
4725             );
4726
4727         $this->setStandardTime(
4728             $hn_standardday,
4729             $hn_standardmonth,
4730             $hn_standardyear,
4731             $hn_standardhour,
4732             $hn_standardminute,
4733             $hn_standardsecond,
4734             $this->on_standardpartsecond
4735         );
4736     }
4737
4738
4739     // }}}
4740     // {{{ subtractSpan()
4741
4742     /**
4743      * Subtracts a time span from the date
4744      *
4745      * N.B. it is impossible for this function to count leap seconds,
4746      * because the result would be dependent on which order the consituent
4747      * parts of the span are subtracted from the date.  Therefore, leap
4748      * seconds are ignored by this function.  If you want to count leap
4749      * seconds, use {@link Date::subtractSeconds()}.
4750      *
4751      * @param object $span the time span to subtract
4752      *
4753      * @return   void
4754      * @access   public
4755      * @see      Date_Span
4756      */
4757     public function subtractSpan($span)
4758     {
4759         if (!is_a($span, 'Date_Span')) {
4760             return PEAR::raiseError("Invalid argument - not 'Date_Span' object");
4761         } elseif ($this->ob_invalidtime) {
4762             return $this->_getErrorInvalidTime();
4763         }
4764
4765         $hn_days = -$span->day;
4766         $hn_standardhour = $this->on_standardhour - $span->hour;
4767         $hn_standardminute = $this->on_standardminute - $span->minute;
4768         $hn_standardsecond = $this->on_standardsecond - $span->second;
4769
4770         if ($hn_standardsecond < 0) {
4771             --$hn_standardminute;
4772             $hn_standardsecond += 60;
4773         }
4774
4775         if ($hn_standardminute < 0) {
4776             --$hn_standardhour;
4777             $hn_standardminute += 60;
4778         }
4779
4780         if ($hn_standardhour < 0) {
4781             --$hn_days;
4782             $hn_standardhour += 24;
4783         }
4784
4785         list($hn_standardyear, $hn_standardmonth, $hn_standardday) =
4786             explode(
4787                 " ",
4788                 Date_Calc::addDays(
4789                     $hn_days,
4790                     $this->on_standardday,
4791                     $this->on_standardmonth,
4792                     $this->on_standardyear,
4793                     "%Y %m %d"
4794                 )
4795             );
4796
4797         $this->setStandardTime(
4798             $hn_standardday,
4799             $hn_standardmonth,
4800             $hn_standardyear,
4801             $hn_standardhour,
4802             $hn_standardminute,
4803             $hn_standardsecond,
4804             $this->on_standardpartsecond
4805         );
4806     }
4807
4808
4809     // }}}
4810     // {{{ dateDiff()
4811
4812     /**
4813      * Subtract supplied date and return answer in days
4814      *
4815      * If the second parameter '$pb_ignoretime' is specified as false, the time
4816      * parts of the two dates will be ignored, and the integral no of days
4817      * between the day/month/year parts of the two dates will be returned.  If
4818      * either of the two dates have an invalid time, the integral no of days
4819      * will also be returned, else the returned value will be the no of days as
4820      * a float, with each hour being treated as 1/24th of a day and so on.
4821      *
4822      * For example,
4823      *
4824      *  - 21/11/2007 13.00 minus 21/11/2007 01.00
4825      *
4826      * returns 0.5
4827      *
4828      * Note that if the passed date is in the past, a positive value will be
4829      * returned, and if it is in the future, a negative value will be returned.
4830      *
4831      * @param object $po_date date to subtract
4832      * @param bool $pb_ignoretime whether to ignore the time values of the two
4833      *                               dates in subtraction (defaults to false)
4834      *
4835      * @return   mixed      days between two dates as int or float
4836      * @access   public
4837      * @since    Method available since Release 1.5.0
4838      */
4839     public function dateDiff($po_date, $pb_ignoretime = false)
4840     {
4841         if ($pb_ignoretime || $this->ob_invalidtime) {
4842             return Date_Calc::dateToDays(
4843                     $this->day,
4844                     $this->month,
4845                     $this->year
4846                 ) -
4847                 Date_Calc::dateToDays(
4848                     $po_date->getDay(),
4849                     $po_date->getMonth(),
4850                     $po_date->getYear()
4851                 );
4852         }
4853
4854         $hn_secondscompare = $po_date->getStandardSecondsPastMidnight();
4855         if (PEAR::isError($hn_secondscompare)) {
4856             if ($hn_secondscompare->getCode() != DATE_ERROR_INVALIDTIME) {
4857                 return $hn_secondscompare;
4858             }
4859
4860             return Date_Calc::dateToDays(
4861                     $this->day,
4862                     $this->month,
4863                     $this->year
4864                 ) -
4865                 Date_Calc::dateToDays(
4866                     $po_date->getDay(),
4867                     $po_date->getMonth(),
4868                     $po_date->getYear()
4869                 );
4870         }
4871
4872         $hn_seconds = $this->getStandardSecondsPastMidnight();
4873
4874         // If time parts are equal, return int, else return float:
4875         //
4876         return Date_Calc::dateToDays(
4877                 $this->on_standardday,
4878                 $this->on_standardmonth,
4879                 $this->on_standardyear
4880             ) -
4881             Date_Calc::dateToDays(
4882                 $po_date->getStandardDay(),
4883                 $po_date->getStandardMonth(),
4884                 $po_date->getStandardYear()
4885             ) +
4886             ($hn_seconds == $hn_secondscompare ? 0 :
4887                 ($hn_seconds - $hn_secondscompare) / 86400);
4888     }
4889
4890
4891     // }}}
4892     // {{{ inEquivalentTimeZones()
4893
4894     /**
4895      * Tests whether two dates are in equivalent time zones
4896      *
4897      * Equivalence in this context consists in the time zones of the two dates
4898      * having:
4899      *
4900      *  - an equal offset from UTC in both standard and Summer time (if
4901      *               the time zones observe Summer time)
4902      *  - the same Summer time start and end rules, that is, the two time zones
4903      *               must switch from standard time to Summer time, and
4904      *               vice versa, on the same day and at the same time
4905      *
4906      * An example of two equivalent time zones is 'Europe/London' and
4907      * 'Europe/Lisbon', which in London is known as GMT/BST, and in Lisbon as
4908      * WET/WEST.
4909      *
4910      * @param object $po_date1 the first Date object to compare
4911      * @param object $po_date2 the second Date object to compare
4912      *
4913      * @return   bool       true if the time zones are equivalent
4914      * @access   public
4915      * @static
4916      * @see      Date_TimeZone::isEquivalent()
4917      * @since    Method available since Release 1.5.0
4918      */
4919     public function inEquivalentTimeZones($po_date1, $po_date2)
4920     {
4921         return $po_date1->tz->isEquivalent($po_date2->getTZID());
4922     }
4923
4924
4925     // }}}
4926     // {{{ compare()
4927
4928     /**
4929      * Compares two dates
4930      *
4931      * Suitable for use in sorting functions
4932      *
4933      * @param object $od1 the first Date object to compare
4934      * @param object $od2 the second Date object to compare
4935      *
4936      * @return   int        0 if the dates are equal, -1 if '$od1' is
4937      *                       before '$od2', 1 if '$od1' is after '$od2'
4938      * @access   public
4939      * @static
4940      */
4941     public function compare($od1, $od2)
4942     {
4943         $d1 = new Date($od1);
4944         $d2 = new Date($od2);
4945
4946         // If the time zones are equivalent, do nothing:
4947         //
4948         if (!Date::inEquivalentTimeZones($d1, $d2)) {
4949             // Only a time zone with a valid time can be converted:
4950             //
4951             if ($d2->isValidTime()) {
4952                 $d2->convertTZByID($d1->getTZID());
4953             } elseif ($d1->isValidTime()) {
4954                 $d1->convertTZByID($d2->getTZID());
4955             } else {
4956                 // No comparison can be made without guessing the time:
4957                 //
4958                 return PEAR::raiseError(
4959                     "Both dates have invalid time",
4960                     DATE_ERROR_INVALIDTIME
4961                 );
4962             }
4963         }
4964
4965         $days1 = Date_Calc::dateToDays(
4966             $d1->getDay(),
4967             $d1->getMonth(),
4968             $d1->getYear()
4969         );
4970         $days2 = Date_Calc::dateToDays(
4971             $d2->getDay(),
4972             $d2->getMonth(),
4973             $d2->getYear()
4974         );
4975         if ($days1 < $days2) {
4976             return -1;
4977         }
4978         if ($days1 > $days2) {
4979             return 1;
4980         }
4981
4982         $hn_hour1 = $d1->getStandardHour();
4983         if (PEAR::isError($hn_hour1)) {
4984             return $hn_hour1;
4985         }
4986         $hn_hour2 = $d2->getStandardHour();
4987         if (PEAR::isError($hn_hour2)) {
4988             return $hn_hour2;
4989         }
4990
4991         if ($hn_hour1 < $hn_hour2) {
4992             return -1;
4993         }
4994         if ($hn_hour1 > $hn_hour2) {
4995             return 1;
4996         }
4997         if ($d1->getStandardMinute() < $d2->getStandardMinute()) {
4998             return -1;
4999         }
5000         if ($d1->getStandardMinute() > $d2->getStandardMinute()) {
5001             return 1;
5002         }
5003         if ($d1->getStandardSecond() < $d2->getStandardSecond()) {
5004             return -1;
5005         }
5006         if ($d1->getStandardSecond() > $d2->getStandardSecond()) {
5007             return 1;
5008         }
5009         if ($d1->getStandardPartSecond() < $d2->getStandardPartSecond()) {
5010             return -1;
5011         }
5012         if ($d1->getStandardPartSecond() > $d2->getStandardPartSecond()) {
5013             return 1;
5014         }
5015         return 0;
5016     }
5017
5018
5019     // }}}
5020     // {{{ before()
5021
5022     /**
5023      * Test if this date/time is before a certain date/time
5024      *
5025      * @param object $when the Date object to test against
5026      *
5027      * @return   boolean    true if this date is before $when
5028      * @access   public
5029      */
5030     public function before($when)
5031     {
5032         $hn_compare = Date::compare($this, $when);
5033         if (PEAR::isError($hn_compare)) {
5034             return $hn_compare;
5035         }
5036
5037         if ($hn_compare == -1) {
5038             return true;
5039         } else {
5040             return false;
5041         }
5042     }
5043
5044
5045     // }}}
5046     // {{{ after()
5047
5048     /**
5049      * Test if this date/time is after a certain date/time
5050      *
5051      * @param object $when the Date object to test against
5052      *
5053      * @return   boolean    true if this date is after $when
5054      * @access   public
5055      */
5056     public function after($when)
5057     {
5058         $hn_compare = Date::compare($this, $when);
5059         if (PEAR::isError($hn_compare)) {
5060             return $hn_compare;
5061         }
5062
5063         if ($hn_compare == 1) {
5064             return true;
5065         } else {
5066             return false;
5067         }
5068     }
5069
5070
5071     // }}}
5072     // {{{ equals()
5073
5074     /**
5075      * Test if this date/time is exactly equal to a certain date/time
5076      *
5077      * @param object $when the Date object to test against
5078      *
5079      * @return   boolean    true if this date is exactly equal to $when
5080      * @access   public
5081      */
5082     public function equals($when)
5083     {
5084         $hn_compare = Date::compare($this, $when);
5085         if (PEAR::isError($hn_compare)) {
5086             return $hn_compare;
5087         }
5088
5089         if ($hn_compare == 0) {
5090             return true;
5091         } else {
5092             return false;
5093         }
5094     }
5095
5096
5097     // }}}
5098     // {{{ isFuture()
5099
5100     /**
5101      * Determine if this date is in the future
5102      *
5103      * @return   boolean    true if this date is in the future
5104      * @access   public
5105      */
5106     public function isFuture()
5107     {
5108         $now = new Date();
5109         return $this->after($now);
5110     }
5111
5112
5113     // }}}
5114     // {{{ isPast()
5115
5116     /**
5117      * Determine if this date is in the past
5118      *
5119      * @return   boolean    true if this date is in the past
5120      * @access   public
5121      */
5122     public function isPast()
5123     {
5124         $now = new Date();
5125         return $this->before($now);
5126     }
5127
5128
5129     // }}}
5130     // {{{ isLeapYear()
5131
5132     /**
5133      * Determine if the year in this date is a leap year
5134      *
5135      * @return   boolean    true if this year is a leap year
5136      * @access   public
5137      */
5138     public function isLeapYear()
5139     {
5140         return Date_Calc::isLeapYear($this->year);
5141     }
5142
5143
5144     // }}}
5145     // {{{ getJulianDate()
5146
5147     /**
5148      * Returns the no of days (1-366) since 31st December of the previous year
5149      *
5150      * N.B. this function does not return (and never has returned) the 'Julian
5151      * Date', as described, for example, at:
5152      *
5153      *  - {@link http://en.wikipedia.org/wiki/Julian_day}
5154      *
5155      * If you want the day of the year (0-366), use {@link Date::getDayOfYear()}
5156      * instead.  If you want the true Julian Day, call one of the following:
5157      *
5158      *   - {@link Date::formatLikeStrftime()} using code '<b>%E</b>'
5159      *   - {@link Date::formatLikeSQL()} using code '<b>J</b>'
5160      *
5161      * There currently is no function that calls the Julian Date (as opposed
5162      * to the 'Julian Day'), although the Julian Day is an approximation.
5163      *
5164      * @return     int        the Julian date
5165      * @access     public
5166      * @see        Date::getDayOfYear()
5167      * @deprecated Method deprecated in Release 1.5.0
5168      */
5169     public function getJulianDate()
5170     {
5171         return Date_Calc::julianDate($this->day, $this->month, $this->year);
5172     }
5173
5174
5175     // }}}
5176     // {{{ getDayOfYear()
5177
5178     /**
5179      * Returns the no of days (1-366) since 31st December of the previous year
5180      *
5181      * @return   int        an integer between 1 and 366
5182      * @access   public
5183      * @since    Method available since Release 1.5.0
5184      */
5185     public function getDayOfYear()
5186     {
5187         return Date_Calc::dayOfYear($this->day, $this->month, $this->year);
5188     }
5189
5190
5191     // }}}
5192     // {{{ getDayOfWeek()
5193
5194     /**
5195      * Gets the day of the week for this date (0 = Sunday)
5196      *
5197      * @return   int        the day of the week (0 = Sunday)
5198      * @access   public
5199      */
5200     public function getDayOfWeek()
5201     {
5202         return Date_Calc::dayOfWeek($this->day, $this->month, $this->year);
5203     }
5204
5205
5206     // }}}
5207     // {{{ getWeekOfYear()
5208
5209     /**
5210      * Gets the week of the year for this date
5211      *
5212      * @return   int        the week of the year
5213      * @access   public
5214      */
5215     public function getWeekOfYear()
5216     {
5217         return Date_Calc::weekOfYear($this->day, $this->month, $this->year);
5218     }
5219
5220
5221     // }}}
5222     // {{{ getQuarterOfYear()
5223
5224     /**
5225      * Gets the quarter of the year for this date
5226      *
5227      * @return   int        the quarter of the year (1-4)
5228      * @access   public
5229      */
5230     public function getQuarterOfYear()
5231     {
5232         return Date_Calc::quarterOfYear($this->day, $this->month, $this->year);
5233     }
5234
5235
5236     // }}}
5237     // {{{ getDaysInMonth()
5238
5239     /**
5240      * Gets number of days in the month for this date
5241      *
5242      * @return   int        number of days in this month
5243      * @access   public
5244      */
5245     public function getDaysInMonth()
5246     {
5247         return Date_Calc::daysInMonth($this->month, $this->year);
5248     }
5249
5250
5251     // }}}
5252     // {{{ getWeeksInMonth()
5253
5254     /**
5255      * Gets the number of weeks in the month for this date
5256      *
5257      * @return   int        number of weeks in this month
5258      * @access   public
5259      */
5260     public function getWeeksInMonth()
5261     {
5262         return Date_Calc::weeksInMonth($this->month, $this->year);
5263     }
5264
5265
5266     // }}}
5267     // {{{ getDayName()
5268
5269     /**
5270      * Gets the full name or abbreviated name of this weekday
5271      *
5272      * @param bool $abbr abbreviate the name
5273      * @param int $length length of abbreviation
5274      *
5275      * @return   string     name of this day
5276      * @access   public
5277      */
5278     public function getDayName($abbr = false, $length = 3)
5279     {
5280         if ($abbr) {
5281             return Date_Calc::getWeekdayAbbrname(
5282                 $this->day,
5283                 $this->month,
5284                 $this->year,
5285                 $length
5286             );
5287         } else {
5288             return Date_Calc::getWeekdayFullname(
5289                 $this->day,
5290                 $this->month,
5291                 $this->year
5292             );
5293         }
5294     }
5295
5296
5297     // }}}
5298     // {{{ getMonthName()
5299
5300     /**
5301      * Gets the full name or abbreviated name of this month
5302      *
5303      * @param boolean $abbr abbreviate the name
5304      *
5305      * @return   string     name of this month
5306      * @access   public
5307      */
5308     public function getMonthName($abbr = false)
5309     {
5310         if ($abbr) {
5311             return Date_Calc::getMonthAbbrname($this->month);
5312         } else {
5313             return Date_Calc::getMonthFullname($this->month);
5314         }
5315     }
5316
5317
5318     // }}}
5319     // {{{ getNextDay()
5320
5321     /**
5322      * Get a Date object for the day after this one
5323      *
5324      * The time of the returned Date object is the same as this time.
5325      *
5326      * @return   object     Date object representing the next day
5327      * @access   public
5328      */
5329     public function getNextDay()
5330     {
5331         $ret = new Date($this);
5332         $ret->addDays(1);
5333         return $ret;
5334     }
5335
5336
5337     // }}}
5338     // {{{ getPrevDay()
5339
5340     /**
5341      * Get a Date object for the day before this one
5342      *
5343      * The time of the returned Date object is the same as this time.
5344      *
5345      * @return   object     Date object representing the previous day
5346      * @access   public
5347      */
5348     public function getPrevDay()
5349     {
5350         $ret = new Date($this);
5351         $ret->addDays(-1);
5352         return $ret;
5353     }
5354
5355
5356     // }}}
5357     // {{{ getNextWeekday()
5358
5359     /**
5360      * Get a Date object for the weekday after this one
5361      *
5362      * The time of the returned Date object is the same as this time.
5363      *
5364      * @return   object     Date object representing the next week-day
5365      * @access   public
5366      */
5367     public function getNextWeekday()
5368     {
5369         $ret = new Date($this);
5370         list($hs_year, $hs_month, $hs_day) =
5371             explode(" ", Date_Calc::nextWeekday(
5372                 $this->day,
5373                 $this->month,
5374                 $this->year,
5375                 "%Y %m %d"
5376             ));
5377         $ret->setDayMonthYear($hs_day, $hs_month, $hs_year);
5378         return $ret;
5379     }
5380
5381
5382     // }}}
5383     // {{{ getPrevWeekday()
5384
5385     /**
5386      * Get a Date object for the weekday before this one
5387      *
5388      * The time of the returned Date object is the same as this time.
5389      *
5390      * @return   object     Date object representing the previous week-day
5391      * @access   public
5392      */
5393     public function getPrevWeekday()
5394     {
5395         $ret = new Date($this);
5396         list($hs_year, $hs_month, $hs_day) =
5397             explode(" ", Date_Calc::prevWeekday(
5398                 $this->day,
5399                 $this->month,
5400                 $this->year,
5401                 "%Y %m %d"
5402             ));
5403         $ret->setDayMonthYear($hs_day, $hs_month, $hs_year);
5404         return $ret;
5405     }
5406
5407
5408     // }}}
5409     // {{{ getYear()
5410
5411     /**
5412      * Returns the year field of the date object
5413      *
5414      * @return   int        the year
5415      * @access   public
5416      */
5417     public function getYear()
5418     {
5419         return $this->year;
5420     }
5421
5422
5423     // }}}
5424     // {{{ getMonth()
5425
5426     /**
5427      * Returns the month field of the date object
5428      *
5429      * @return   int        the minute
5430      * @access   public
5431      */
5432     public function getMonth()
5433     {
5434         return $this->month;
5435     }
5436
5437
5438     // }}}
5439     // {{{ getDay()
5440
5441     /**
5442      * Returns the day field of the date object
5443      *
5444      * @return   int        the day
5445      * @access   public
5446      */
5447     public function getDay()
5448     {
5449         return $this->day;
5450     }
5451
5452
5453     // }}}
5454     // {{{ _getErrorInvalidTime()
5455
5456     /**
5457      * Returns invalid time PEAR Error
5458      *
5459      * @return   object
5460      * @access   private
5461      * @since    Method available since Release 1.5.0
5462      */
5463     public function _getErrorInvalidTime()
5464     {
5465         return PEAR::raiseError(
5466             "Invalid time '" .
5467             sprintf(
5468                 "%02d.%02d.%02d",
5469                 $this->hour,
5470                 $this->minute,
5471                 $this->second
5472             ) .
5473             "' specified for date '" .
5474             Date_Calc::dateFormat(
5475                 $this->day,
5476                 $this->month,
5477                 $this->year,
5478                 "%Y-%m-%d"
5479             ) .
5480             "' and in this timezone",
5481             DATE_ERROR_INVALIDTIME
5482         );
5483     }
5484
5485
5486     // }}}
5487     // {{{ _secondsInDayIsValid()
5488
5489     /**
5490      * If leap seconds are observed, checks if the seconds in the day is valid
5491      *
5492      * Note that only the local standard time is accessed.
5493      *
5494      * @return   bool
5495      * @access   private
5496      * @since    Method available since Release 1.5.0
5497      */
5498     public function _secondsInDayIsValid()
5499     {
5500         if ($this->ob_countleapseconds) {
5501             // Convert to UTC:
5502             //
5503             list($hn_year,
5504                 $hn_month,
5505                 $hn_day,
5506                 $hn_hour,
5507                 $hn_minute,
5508                 $hn_second,
5509                 $hn_partsecond) =
5510                 $this->_addOffset(
5511                     $this->tz->getRawOffset() * -1,
5512                     $this->on_standardday,
5513                     $this->on_standardmonth,
5514                     $this->on_standardyear,
5515                     $this->on_standardhour,
5516                     $this->on_standardminute,
5517                     $this->on_standardsecond,
5518                     $this->on_standardpartsecond
5519                 );
5520             return Date_Calc::secondsPastMidnight(
5521                     $hn_hour,
5522                     $hn_minute,
5523                     $hn_second +
5524                     $hn_partsecond
5525                 ) <
5526                 Date_Calc::getSecondsInDay($hn_day, $hn_month, $hn_year);
5527         } else {
5528             return $this->getStandardSecondsPastMidnight() < 86400;
5529         }
5530     }
5531
5532
5533     // }}}
5534     // {{{ isValidTime()
5535
5536     /**
5537      * Returns whether the stored date/time is valid, i.e as a local time
5538      * for the current time-zone.
5539      *
5540      * An invalid time is one that lies in the 'skipped hour' at the point
5541      * that the clocks go forward (if the time-zone uses Summer time).
5542      *
5543      * Note that the stored date (i.e. the day/month/year), is set more
5544      * strictly:  it is not possible to set an invalid day/month/year
5545      * using {@link Date::setDate()} and it is only possible to do so with
5546      * {@link setYear()} etc. for backwards-compatibility (and anyway, this
5547      * can be switched off by default by setting
5548      * {@link DATE_VALIDATE_DATE_BY_DEFAULT} to 'true').
5549      *
5550      * The object is able to store an invalid time because a user might
5551      * unwittingly and correctly store a valid time, and then add one day so
5552      * as to put the object in the 'skipped' hour (when the clocks go forward).
5553      * This could be corrected by a conversion to Summer time (by adding one
5554      * hour); however, if the user then added another day, and had no need for
5555      * or interest in the time anyway, the behaviour may be rather unexpected.
5556      * And anyway in this situation, the time originally specified would now,
5557      * two days on, be valid again.
5558      *
5559      * So this class allows an invalid time like this so long as the user does
5560      * not in any way make use of or request the time while it is in this
5561      * semi-invalid state, in order to allow for for the fact that he might be
5562      * only interested in the date, and not the time, and in order not to behave
5563      * in an unexpected way, especially without throwing an exception to tell
5564      * the user about it.
5565      *
5566      * @return   bool
5567      * @access   public
5568      * @see      Date::isValidDate(), Date::isNull(),
5569      *            DATE_VALIDATE_DATE_BY_DEFAULT, DATE_CORRECTINVALIDTIME_DEFAULT
5570      * @since    Method available since Release 1.5.0
5571      */
5572     public function isValidTime()
5573     {
5574         return !$this->ob_invalidtime;
5575     }
5576
5577
5578     // }}}
5579     // {{{ getHour()
5580
5581     /**
5582      * Returns the hour field of the date object
5583      *
5584      * @return   int        the hour
5585      * @access   public
5586      */
5587     public function getHour()
5588     {
5589         if ($this->ob_invalidtime) {
5590             return $this->_getErrorInvalidTime();
5591         }
5592
5593         return $this->hour;
5594     }
5595
5596
5597     // }}}
5598     // {{{ getMinute()
5599
5600     /**
5601      * Returns the minute field of the date object
5602      *
5603      * @return   int        the minute
5604      * @access   public
5605      */
5606     public function getMinute()
5607     {
5608         if ($this->ob_invalidtime) {
5609             return $this->_getErrorInvalidTime();
5610         }
5611
5612         return $this->minute;
5613     }
5614
5615
5616     // }}}
5617     // {{{ getSecond()
5618
5619     /**
5620      * Returns the second field of the date object
5621      *
5622      * @return   int        the second
5623      * @access   public
5624      */
5625     public function getSecond()
5626     {
5627         if ($this->ob_invalidtime) {
5628             return $this->_getErrorInvalidTime();
5629         }
5630
5631         return $this->second;
5632     }
5633
5634
5635     // }}}
5636     // {{{ getSecondsPastMidnight()
5637
5638     /**
5639      * Returns the no of seconds since midnight (0-86400) as float
5640      *
5641      * @return   float      float which is at least 0 and less than 86400
5642      * @access   public
5643      * @since    Method available since Release 1.5.0
5644      */
5645     public function getSecondsPastMidnight()
5646     {
5647         if ($this->ob_invalidtime) {
5648             return $this->_getErrorInvalidTime();
5649         }
5650
5651         return Date_Calc::secondsPastMidnight(
5652                 $this->hour,
5653                 $this->minute,
5654                 $this->second
5655             ) +
5656             $this->partsecond;
5657     }
5658
5659
5660     // }}}
5661     // {{{ getPartSecond()
5662
5663     /**
5664      * Returns the part-second field of the date object
5665      *
5666      * @return   float      the part-second
5667      * @access   protected
5668      * @since    Method available since Release 1.5.0
5669      */
5670     public function getPartSecond()
5671     {
5672         if ($this->ob_invalidtime) {
5673             return $this->_getErrorInvalidTime();
5674         }
5675
5676         return $this->partsecond;
5677     }
5678
5679
5680     // }}}
5681     // {{{ getStandardYear()
5682
5683     /**
5684      * Returns the year field of the local standard time
5685      *
5686      * @return   int        the year
5687      * @access   public
5688      * @since    Method available since Release 1.5.0
5689      */
5690     public function getStandardYear()
5691     {
5692         if ($this->ob_invalidtime) {
5693             return $this->_getErrorInvalidTime();
5694         }
5695
5696         return $this->on_standardyear;
5697     }
5698
5699
5700     // }}}
5701     // {{{ getStandardMonth()
5702
5703     /**
5704      * Returns the month field of the local standard time
5705      *
5706      * @return   int        the minute
5707      * @access   public
5708      * @since    Method available since Release 1.5.0
5709      */
5710     public function getStandardMonth()
5711     {
5712         if ($this->ob_invalidtime) {
5713             return $this->_getErrorInvalidTime();
5714         }
5715
5716         return $this->on_standardmonth;
5717     }
5718
5719
5720     // }}}
5721     // {{{ getStandardDay()
5722
5723     /**
5724      * Returns the day field of the local standard time
5725      *
5726      * @return   int        the day
5727      * @access   public
5728      * @since    Method available since Release 1.5.0
5729      */
5730     public function getStandardDay()
5731     {
5732         if ($this->ob_invalidtime) {
5733             return $this->_getErrorInvalidTime();
5734         }
5735
5736         return $this->on_standardday;
5737     }
5738
5739
5740     // }}}
5741     // {{{ getStandardHour()
5742
5743     /**
5744      * Returns the hour field of the local standard time
5745      *
5746      * @return   int        the hour
5747      * @access   public
5748      * @since    Method available since Release 1.5.0
5749      */
5750     public function getStandardHour()
5751     {
5752         if ($this->ob_invalidtime) {
5753             return $this->_getErrorInvalidTime();
5754         }
5755
5756         return $this->on_standardhour;
5757     }
5758
5759
5760     // }}}
5761     // {{{ getStandardMinute()
5762
5763     /**
5764      * Returns the minute field of the local standard time
5765      *
5766      * @return   int        the minute
5767      * @access   public
5768      * @since    Method available since Release 1.5.0
5769      */
5770     public function getStandardMinute()
5771     {
5772         if ($this->ob_invalidtime) {
5773             return $this->_getErrorInvalidTime();
5774         }
5775
5776         return $this->on_standardminute;
5777     }
5778
5779
5780     // }}}
5781     // {{{ getStandardSecond()
5782
5783     /**
5784      * Returns the second field of the local standard time
5785      *
5786      * @return   int        the second
5787      * @access   public
5788      * @since    Method available since Release 1.5.0
5789      */
5790     public function getStandardSecond()
5791     {
5792         if ($this->ob_invalidtime) {
5793             return $this->_getErrorInvalidTime();
5794         }
5795
5796         return $this->on_standardsecond;
5797     }
5798
5799
5800     // }}}
5801     // {{{ getStandardSecondsPastMidnight()
5802
5803     /**
5804      * Returns the no of seconds since midnight (0-86400) of the
5805      * local standard time as float
5806      *
5807      * @return   float      float which is at least 0 and less than 86400
5808      * @access   public
5809      * @since    Method available since Release 1.5.0
5810      */
5811     public function getStandardSecondsPastMidnight()
5812     {
5813         if ($this->ob_invalidtime) {
5814             return $this->_getErrorInvalidTime();
5815         }
5816
5817         return Date_Calc::secondsPastMidnight(
5818                 $this->on_standardhour,
5819                 $this->on_standardminute,
5820                 $this->on_standardsecond
5821             ) +
5822             $this->on_standardpartsecond;
5823     }
5824
5825
5826     // }}}
5827     // {{{ getStandardPartSecond()
5828
5829     /**
5830      * Returns the part-second field of the local standard time
5831      *
5832      * @return   float      the part-second
5833      * @access   protected
5834      * @since    Method available since Release 1.5.0
5835      */
5836     public function getStandardPartSecond()
5837     {
5838         if ($this->ob_invalidtime) {
5839             return $this->_getErrorInvalidTime();
5840         }
5841
5842         return $this->on_standardpartsecond;
5843     }
5844
5845
5846     // }}}
5847     // {{{ _addOffset()
5848
5849     /**
5850      * Add a time zone offset to the passed date/time
5851      *
5852      * @param int $pn_offset the offset to add in milliseconds
5853      * @param int $pn_day the day
5854      * @param int $pn_month the month
5855      * @param int $pn_year the year
5856      * @param int $pn_hour the hour
5857      * @param int $pn_minute the minute
5858      * @param int $pn_second the second
5859      * @param float $pn_partsecond the part-second
5860      *
5861      * @return   array      array of year, month, day, hour, minute, second,
5862      *                       and part-second
5863      * @access   private
5864      * @static
5865      * @since    Method available since Release 1.5.0
5866      */
5867     public function _addOffset(
5868         $pn_offset,
5869         $pn_day,
5870         $pn_month,
5871         $pn_year,
5872         $pn_hour,
5873         $pn_minute,
5874         $pn_second,
5875         $pn_partsecond
5876     )
5877     {
5878         if ($pn_offset == 0) {
5879             return array((int)$pn_year,
5880                 (int)$pn_month,
5881                 (int)$pn_day,
5882                 (int)$pn_hour,
5883                 (int)$pn_minute,
5884                 (int)$pn_second,
5885                 (float)$pn_partsecond);
5886         }
5887
5888         if ($pn_offset % 3600000 == 0) {
5889             list($hn_year,
5890                 $hn_month,
5891                 $hn_day,
5892                 $hn_hour) =
5893                 Date_Calc::addHours(
5894                     $pn_offset / 3600000,
5895                     $pn_day,
5896                     $pn_month,
5897                     $pn_year,
5898                     $pn_hour
5899                 );
5900
5901             $hn_minute = (int)$pn_minute;
5902             $hn_second = (int)$pn_second;
5903             $hn_partsecond = (float)$pn_partsecond;
5904         } elseif ($pn_offset % 60000 == 0) {
5905             list($hn_year,
5906                 $hn_month,
5907                 $hn_day,
5908                 $hn_hour,
5909                 $hn_minute) =
5910                 Date_Calc::addMinutes(
5911                     $pn_offset / 60000,
5912                     $pn_day,
5913                     $pn_month,
5914                     $pn_year,
5915                     $pn_hour,
5916                     $pn_minute
5917                 );
5918
5919             $hn_second = (int)$pn_second;
5920             $hn_partsecond = (float)$pn_partsecond;
5921         } else {
5922             list($hn_year,
5923                 $hn_month,
5924                 $hn_day,
5925                 $hn_hour,
5926                 $hn_minute,
5927                 $hn_secondraw) =
5928                 Date_Calc::addSeconds(
5929                     $pn_offset / 1000,
5930                     $pn_day,
5931                     $pn_month,
5932                     $pn_year,
5933                     $pn_hour,
5934                     $pn_partsecond == 0.0 ?
5935                         $pn_second :
5936                         $pn_second + $pn_partsecond,
5937                     false
5938                 );  // N.B. do not count
5939             // leap seconds
5940
5941             if (is_float($hn_secondraw)) {
5942                 $hn_second = intval($hn_secondraw);
5943                 $hn_partsecond = $hn_secondraw - $hn_second;
5944             } else {
5945                 $hn_second = $hn_secondraw;
5946                 $hn_partsecond = 0.0;
5947             }
5948         }
5949
5950         return array($hn_year,
5951             $hn_month,
5952             $hn_day,
5953             $hn_hour,
5954             $hn_minute,
5955             $hn_second,
5956             $hn_partsecond);
5957     }
5958
5959
5960     // }}}
5961     // {{{ setLocalTime()
5962
5963     /**
5964      * Sets local time (Summer-time-adjusted) and then calculates local
5965      * standard time
5966      *
5967      * @param int $pn_day the day
5968      * @param int $pn_month the month
5969      * @param int $pn_year the year
5970      * @param int $pn_hour the hour
5971      * @param int $pn_minute the minute
5972      * @param int $pn_second the second
5973      * @param float $pn_partsecond the part-second
5974      * @param bool $pb_repeatedhourdefault whether to assume Summer time if a
5975      *                                       repeated hour is specified (defaults
5976      *                                       to false)
5977      * @param bool $pb_correctinvalidtime whether to correct, by adding the
5978      *                                       local Summer time offset, the
5979      *                                       specified time if it falls in the
5980      *                                       skipped hour (defaults to
5981      *                                       {@link DATE_CORRECTINVALIDTIME_DEFAULT})
5982      *
5983      * @return   void
5984      * @access   protected
5985      * @see      Date::setStandardTime()
5986      * @since    Method available since Release 1.5.0
5987      */
5988     public function setLocalTime(
5989         $pn_day,
5990         $pn_month,
5991         $pn_year,
5992         $pn_hour,
5993         $pn_minute,
5994         $pn_second,
5995         $pn_partsecond,
5996         $pb_repeatedhourdefault = false,
5997         $pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT
5998     )
5999     {
6000         settype($pn_day, "int");
6001         settype($pn_month, "int");
6002         settype($pn_year, "int");
6003         settype($pn_hour, "int");
6004         settype($pn_minute, "int");
6005         settype($pn_second, "int");
6006         settype($pn_partsecond, "float");
6007
6008         $hb_insummertime =
6009             $this->tz->inDaylightTime(
6010                 array($pn_day,
6011                     $pn_month, $pn_year, Date_Calc::secondsPastMidnight(
6012                         $pn_hour,
6013                         $pn_minute,
6014                         $pn_second
6015                     ) + $pn_partsecond),
6016                 $pb_repeatedhourdefault
6017             );
6018         if (PEAR::isError($hb_insummertime)) {
6019             if ($hb_insummertime->getCode() != DATE_ERROR_INVALIDTIME) {
6020                 return $hb_insummertime;
6021             } elseif ($pb_correctinvalidtime) {
6022                 // Store passed time as local standard time:
6023                 //
6024                 $this->on_standardday = $pn_day;
6025                 $this->on_standardmonth = $pn_month;
6026                 $this->on_standardyear = $pn_year;
6027                 $this->on_standardhour = $pn_hour;
6028                 $this->on_standardminute = $pn_minute;
6029                 $this->on_standardsecond = $pn_second;
6030                 $this->on_standardpartsecond = $pn_partsecond;
6031
6032                 // Add Summer time offset to passed time:
6033                 //
6034                 list($this->year,
6035                     $this->month,
6036                     $this->day,
6037                     $this->hour,
6038                     $this->minute,
6039                     $this->second,
6040                     $this->partsecond) =
6041                     $this->_addOffset(
6042                         $this->tz->getDSTSavings(),
6043                         $pn_day,
6044                         $pn_month,
6045                         $pn_year,
6046                         $pn_hour,
6047                         $pn_minute,
6048                         $pn_second,
6049                         $pn_partsecond
6050                     );
6051
6052                 $this->ob_invalidtime = !$this->_secondsInDayIsValid();
6053             } else {
6054                 // Hedge bets - if the user adds/subtracts a day, then the time
6055                 // will be uncorrupted, and if the user does
6056                 // addition/subtraction with the time, or requests the time,
6057                 // then return an error at that point:
6058                 //
6059                 $this->day = $pn_day;
6060                 $this->month = $pn_month;
6061                 $this->year = $pn_year;
6062                 $this->hour = $pn_hour;
6063                 $this->minute = $pn_minute;
6064                 $this->second = $pn_second;
6065                 $this->partsecond = $pn_partsecond;
6066
6067                 $this->ob_invalidtime = true;
6068             }
6069
6070             return;
6071         } else {
6072             // Passed time is valid as local time:
6073             //
6074             $this->day = $pn_day;
6075             $this->month = $pn_month;
6076             $this->year = $pn_year;
6077             $this->hour = $pn_hour;
6078             $this->minute = $pn_minute;
6079             $this->second = $pn_second;
6080             $this->partsecond = $pn_partsecond;
6081         }
6082
6083         $this->ob_invalidtime = !$this->_secondsInDayIsValid();
6084
6085         if ($hb_insummertime) {
6086             // Calculate local standard time:
6087             //
6088             list($this->on_standardyear,
6089                 $this->on_standardmonth,
6090                 $this->on_standardday,
6091                 $this->on_standardhour,
6092                 $this->on_standardminute,
6093                 $this->on_standardsecond,
6094                 $this->on_standardpartsecond) =
6095                 $this->_addOffset(
6096                     $this->tz->getDSTSavings() * -1,
6097                     $pn_day,
6098                     $pn_month,
6099                     $pn_year,
6100                     $pn_hour,
6101                     $pn_minute,
6102                     $pn_second,
6103                     $pn_partsecond
6104                 );
6105         } else {
6106             // Time is already local standard time:
6107             //
6108             $this->on_standardday = $pn_day;
6109             $this->on_standardmonth = $pn_month;
6110             $this->on_standardyear = $pn_year;
6111             $this->on_standardhour = $pn_hour;
6112             $this->on_standardminute = $pn_minute;
6113             $this->on_standardsecond = $pn_second;
6114             $this->on_standardpartsecond = $pn_partsecond;
6115         }
6116     }
6117
6118
6119     // }}}
6120     // {{{ setStandardTime()
6121
6122     /**
6123      * Sets local standard time and then calculates local time (i.e.
6124      * Summer-time-adjusted)
6125      *
6126      * @param int $pn_day the day
6127      * @param int $pn_month the month
6128      * @param int $pn_year the year
6129      * @param int $pn_hour the hour
6130      * @param int $pn_minute the minute
6131      * @param int $pn_second the second
6132      * @param float $pn_partsecond the part-second
6133      *
6134      * @return   void
6135      * @access   protected
6136      * @see      Date::setLocalTime()
6137      * @since    Method available since Release 1.5.0
6138      */
6139     public function setStandardTime(
6140         $pn_day,
6141         $pn_month,
6142         $pn_year,
6143         $pn_hour,
6144         $pn_minute,
6145         $pn_second,
6146         $pn_partsecond
6147     )
6148     {
6149         settype($pn_day, "int");
6150         settype($pn_month, "int");
6151         settype($pn_year, "int");
6152         settype($pn_hour, "int");
6153         settype($pn_minute, "int");
6154         settype($pn_second, "int");
6155         settype($pn_partsecond, "float");
6156
6157         $this->on_standardday = $pn_day;
6158         $this->on_standardmonth = $pn_month;
6159         $this->on_standardyear = $pn_year;
6160         $this->on_standardhour = $pn_hour;
6161         $this->on_standardminute = $pn_minute;
6162         $this->on_standardsecond = $pn_second;
6163         $this->on_standardpartsecond = $pn_partsecond;
6164
6165         $this->ob_invalidtime = !$this->_secondsInDayIsValid();
6166
6167         if ($this->tz->inDaylightTimeStandard(array($pn_day, $pn_month,
6168             $pn_year, Date_Calc::secondsPastMidnight(
6169                 $pn_hour,
6170                 $pn_minute,
6171                 $pn_second
6172             ) + $pn_partsecond))) {
6173
6174             // Calculate local time:
6175             //
6176             list($this->year,
6177                 $this->month,
6178                 $this->day,
6179                 $this->hour,
6180                 $this->minute,
6181                 $this->second,
6182                 $this->partsecond) =
6183                 $this->_addOffset(
6184                     $this->tz->getDSTSavings(),
6185                     $pn_day,
6186                     $pn_month,
6187                     $pn_year,
6188                     $pn_hour,
6189                     $pn_minute,
6190                     $pn_second,
6191                     $pn_partsecond
6192                 );
6193         } else {
6194             // Time is already local time:
6195             //
6196             $this->day = $pn_day;
6197             $this->month = $pn_month;
6198             $this->year = $pn_year;
6199             $this->hour = $pn_hour;
6200             $this->minute = $pn_minute;
6201             $this->second = $pn_second;
6202             $this->partsecond = $pn_partsecond;
6203         }
6204     }
6205
6206
6207     // }}}
6208     // {{{ setYear()
6209
6210     /**
6211      * Sets the year field of the date object
6212      *
6213      * If specified year forms an invalid date, then PEAR error will be
6214      * returned, unless the validation is over-ridden using the second
6215      * parameter.
6216      *
6217      * @param int $y the year
6218      * @param bool $pb_validate whether to check that the new date is valid
6219      *                           (defaults to {@link DATE_VALIDATE_DATE_BY_DEFAULT})
6220      *
6221      * @return   void
6222      * @access   public
6223      * @see      Date::setDayMonthYear(), Date::setDateTime()
6224      */
6225     public function setYear($y, $pb_validate = DATE_VALIDATE_DATE_BY_DEFAULT)
6226     {
6227         if ($pb_validate && !Date_Calc::isValidDate($this->day, $this->month, $y)) {
6228             return PEAR::raiseError(
6229                 "'" .
6230                 Date_Calc::dateFormat(
6231                     $this->day,
6232                     $this->month,
6233                     $y,
6234                     "%Y-%m-%d"
6235                 ) .
6236                 "' is invalid calendar date",
6237                 DATE_ERROR_INVALIDDATE
6238             );
6239         } else {
6240             $this->setLocalTime(
6241                 $this->day,
6242                 $this->month,
6243                 $y,
6244                 $this->hour,
6245                 $this->minute,
6246                 $this->second,
6247                 $this->partsecond
6248             );
6249         }
6250     }
6251
6252
6253     // }}}
6254     // {{{ setMonth()
6255
6256     /**
6257      * Sets the month field of the date object
6258      *
6259      * If specified year forms an invalid date, then PEAR error will be
6260      * returned, unless the validation is over-ridden using the second
6261      * parameter.
6262      *
6263      * @param int $m the month
6264      * @param bool $pb_validate whether to check that the new date is valid
6265      *                           (defaults to {@link DATE_VALIDATE_DATE_BY_DEFAULT})
6266      *
6267      * @return   void
6268      * @access   public
6269      * @see      Date::setDayMonthYear(), Date::setDateTime()
6270      */
6271     public function setMonth($m, $pb_validate = DATE_VALIDATE_DATE_BY_DEFAULT)
6272     {
6273         if ($pb_validate && !Date_Calc::isValidDate($this->day, $m, $this->year)) {
6274             return PEAR::raiseError(
6275                 "'" .
6276                 Date_Calc::dateFormat(
6277                     $this->day,
6278                     $m,
6279                     $this->year,
6280                     "%Y-%m-%d"
6281                 ) .
6282                 "' is invalid calendar date",
6283                 DATE_ERROR_INVALIDDATE
6284             );
6285         } else {
6286             $this->setLocalTime(
6287                 $this->day,
6288                 $m,
6289                 $this->year,
6290                 $this->hour,
6291                 $this->minute,
6292                 $this->second,
6293                 $this->partsecond
6294             );
6295         }
6296     }
6297
6298
6299     // }}}
6300     // {{{ setDay()
6301
6302     /**
6303      * Sets the day field of the date object
6304      *
6305      * If specified year forms an invalid date, then PEAR error will be
6306      * returned, unless the validation is over-ridden using the second
6307      * parameter.
6308      *
6309      * @param int $d the day
6310      * @param bool $pb_validate whether to check that the new date is valid
6311      *                           (defaults to {@link DATE_VALIDATE_DATE_BY_DEFAULT})
6312      *
6313      * @return   void
6314      * @access   public
6315      * @see      Date::setDayMonthYear(), Date::setDateTime()
6316      */
6317     public function setDay($d, $pb_validate = DATE_VALIDATE_DATE_BY_DEFAULT)
6318     {
6319         if ($pb_validate && !Date_Calc::isValidDate($d, $this->month, $this->year)) {
6320             return PEAR::raiseError(
6321                 "'" .
6322                 Date_Calc::dateFormat(
6323                     $d,
6324                     $this->month,
6325                     $this->year,
6326                     "%Y-%m-%d"
6327                 ) .
6328                 "' is invalid calendar date",
6329                 DATE_ERROR_INVALIDDATE
6330             );
6331         } else {
6332             $this->setLocalTime(
6333                 $d,
6334                 $this->month,
6335                 $this->year,
6336                 $this->hour,
6337                 $this->minute,
6338                 $this->second,
6339                 $this->partsecond
6340             );
6341         }
6342     }
6343
6344
6345     // }}}
6346     // {{{ setDayMonthYear()
6347
6348     /**
6349      * Sets the day, month and year fields of the date object
6350      *
6351      * If specified year forms an invalid date, then PEAR error will be
6352      * returned.  Note that setting each of these fields separately
6353      * may unintentionally return a PEAR error if a transitory date is
6354      * invalid between setting these fields.
6355      *
6356      * @param int $d the day
6357      * @param int $m the month
6358      * @param int $y the year
6359      *
6360      * @return   void
6361      * @access   public
6362      * @see      Date::setDateTime()
6363      * @since    Method available since Release 1.5.0
6364      */
6365     public function setDayMonthYear($d, $m, $y)
6366     {
6367         if (!Date_Calc::isValidDate($d, $m, $y)) {
6368             return PEAR::raiseError(
6369                 "'" .
6370                 Date_Calc::dateFormat(
6371                     $d,
6372                     $m,
6373                     $y,
6374                     "%Y-%m-%d"
6375                 ) .
6376                 "' is invalid calendar date",
6377                 DATE_ERROR_INVALIDDATE
6378             );
6379         } else {
6380             $this->setLocalTime(
6381                 $d,
6382                 $m,
6383                 $y,
6384                 $this->hour,
6385                 $this->minute,
6386                 $this->second,
6387                 $this->partsecond
6388             );
6389         }
6390     }
6391
6392
6393     // }}}
6394     // {{{ setHour()
6395
6396     /**
6397      * Sets the hour field of the date object
6398      *
6399      * Expects an hour in 24-hour format.
6400      *
6401      * @param int $h the hour
6402      * @param bool $pb_repeatedhourdefault whether to assume Summer time if a
6403      *                                      repeated hour is specified (defaults
6404      *                                      to false)
6405      *
6406      * @return   void
6407      * @access   public
6408      * @see      Date::setHourMinuteSecond(), Date::setDateTime()
6409      */
6410     public function setHour($h, $pb_repeatedhourdefault = false)
6411     {
6412         if ($h > 23 || $h < 0) {
6413             return PEAR::raiseError("Invalid hour value '$h'");
6414         } else {
6415             $ret = $this->setHourMinuteSecond(
6416                 $h,
6417                 $this->minute,
6418                 $this->partsecond == 0.0 ?
6419                     $this->second :
6420                     $this->second + $this->partsecond,
6421                 $pb_repeatedhourdefault
6422             );
6423
6424             if (PEAR::isError($ret)) {
6425                 return $ret;
6426             }
6427         }
6428     }
6429
6430
6431     // }}}
6432     // {{{ setMinute()
6433
6434     /**
6435      * Sets the minute field of the date object
6436      *
6437      * @param int $m the minute
6438      * @param bool $pb_repeatedhourdefault whether to assume Summer time if a
6439      *                                      repeated hour is specified (defaults
6440      *                                      to false)
6441      *
6442      * @return   void
6443      * @access   public
6444      * @see      Date::setHourMinuteSecond(), Date::setDateTime()
6445      */
6446     public function setMinute($m, $pb_repeatedhourdefault = false)
6447     {
6448         if ($m > 59 || $m < 0) {
6449             return PEAR::raiseError("Invalid minute value '$m'");
6450         } else {
6451             $ret = $this->setHourMinuteSecond(
6452                 $this->hour,
6453                 $m,
6454                 $this->partsecond == 0.0 ?
6455                     $this->second :
6456                     $this->second + $this->partsecond,
6457                 $pb_repeatedhourdefault
6458             );
6459
6460             if (PEAR::isError($ret)) {
6461                 return $ret;
6462             }
6463         }
6464     }
6465
6466
6467     // }}}
6468     // {{{ setSecond()
6469
6470     /**
6471      * Sets the second field of the date object
6472      *
6473      * @param mixed $s the second as integer or float
6474      * @param bool $pb_repeatedhourdefault whether to assume Summer time if a
6475      *                                       repeated hour is specified
6476      *                                       (defaults to false)
6477      *
6478      * @return   void
6479      * @access   public
6480      * @see      Date::setHourMinuteSecond(), Date::setDateTime()
6481      */
6482     public function setSecond($s, $pb_repeatedhourdefault = false)
6483     {
6484         if ($s > 60 || // Leap seconds possible
6485             $s < 0) {
6486             return PEAR::raiseError("Invalid second value '$s'");
6487         } else {
6488             $ret = $this->setHourMinuteSecond(
6489                 $this->hour,
6490                 $this->minute,
6491                 $s,
6492                 $pb_repeatedhourdefault
6493             );
6494
6495             if (PEAR::isError($ret)) {
6496                 return $ret;
6497             }
6498         }
6499     }
6500
6501
6502     // }}}
6503     // {{{ setPartSecond()
6504
6505     /**
6506      * Sets the part-second field of the date object
6507      *
6508      * @param float $pn_ps the part-second
6509      * @param bool $pb_repeatedhourdefault whether to assume Summer time if a
6510      *                                      repeated hour is specified (defaults
6511      *                                      to false)
6512      *
6513      * @return   void
6514      * @access   protected
6515      * @see      Date::setHourMinuteSecond(), Date::setDateTime()
6516      * @since    Method available since Release 1.5.0
6517      */
6518     public function setPartSecond($pn_ps, $pb_repeatedhourdefault = false)
6519     {
6520         if ($pn_ps >= 1 || $pn_ps < 0) {
6521             return PEAR::raiseError("Invalid part-second value '$pn_ps'");
6522         } else {
6523             $ret = $this->setHourMinuteSecond(
6524                 $this->hour,
6525                 $this->minute,
6526                 $this->second + $pn_ps,
6527                 $pb_repeatedhourdefault
6528             );
6529
6530             if (PEAR::isError($ret)) {
6531                 return $ret;
6532             }
6533         }
6534     }
6535
6536
6537     // }}}
6538     // {{{ setHourMinuteSecond()
6539
6540     /**
6541      * Sets the hour, minute, second and part-second fields of the date object
6542      *
6543      * N.B. if the repeated hour, due to the clocks going back, is specified,
6544      * the default is to assume local standard time.
6545      *
6546      * @param int $h the hour
6547      * @param int $m the minute
6548      * @param mixed $s the second as integer or float
6549      * @param bool $pb_repeatedhourdefault whether to assume Summer time if a
6550      *                                       repeated hour is specified
6551      *                                       (defaults to false)
6552      *
6553      * @return   void
6554      * @access   public
6555      * @see      Date::setDateTime()
6556      * @since    Method available since Release 1.5.0
6557      */
6558     public function setHourMinuteSecond($h, $m, $s, $pb_repeatedhourdefault = false)
6559     {
6560         // Split second into integer and part-second:
6561         //
6562         if (is_float($s)) {
6563             $hn_second = intval($s);
6564             $hn_partsecond = $s - $hn_second;
6565         } else {
6566             $hn_second = (int)$s;
6567             $hn_partsecond = 0.0;
6568         }
6569
6570         $this->setLocalTime(
6571             $this->day,
6572             $this->month,
6573             $this->year,
6574             $h,
6575             $m,
6576             $hn_second,
6577             $hn_partsecond,
6578             $pb_repeatedhourdefault
6579         );
6580     }
6581
6582
6583     // }}}
6584     // {{{ setDateTime()
6585
6586     /**
6587      * Sets all the fields of the date object (day, month, year, hour, minute
6588      * and second)
6589      *
6590      * If specified year forms an invalid date, then PEAR error will be
6591      * returned.  Note that setting each of these fields separately
6592      * may unintentionally return a PEAR error if a transitory date is
6593      * invalid between setting these fields.
6594      *
6595      * N.B. if the repeated hour, due to the clocks going back, is specified,
6596      * the default is to assume local standard time.
6597      *
6598      * @param int $pn_day the day
6599      * @param int $pn_month the month
6600      * @param int $pn_year the year
6601      * @param int $pn_hour the hour
6602      * @param int $pn_minute the minute
6603      * @param mixed $pm_second the second as integer or float
6604      * @param bool $pb_repeatedhourdefault whether to assume Summer time if a
6605      *                                       repeated hour is specified
6606      *                                       (defaults to false)
6607      *
6608      * @return   void
6609      * @access   public
6610      * @see      Date::setDayMonthYear(), Date::setHourMinuteSecond()
6611      * @since    Method available since Release 1.5.0
6612      */
6613     public function setDateTime(
6614         $pn_day,
6615         $pn_month,
6616         $pn_year,
6617         $pn_hour,
6618         $pn_minute,
6619         $pm_second,
6620         $pb_repeatedhourdefault = false
6621     )
6622     {
6623         if (!Date_Calc::isValidDate($d, $m, $y)) {
6624             return PEAR::raiseError(
6625                 "'" .
6626                 Date_Calc::dateFormat(
6627                     $d,
6628                     $m,
6629                     $y,
6630                     "%Y-%m-%d"
6631                 ) .
6632                 "' is invalid calendar date",
6633                 DATE_ERROR_INVALIDDATE
6634             );
6635         } else {
6636             // Split second into integer and part-second:
6637             //
6638             if (is_float($pm_second)) {
6639                 $hn_second = intval($pm_second);
6640                 $hn_partsecond = $pm_second - $hn_second;
6641             } else {
6642                 $hn_second = (int)$pm_second;
6643                 $hn_partsecond = 0.0;
6644             }
6645
6646             $this->setLocalTime(
6647                 $d,
6648                 $m,
6649                 $y,
6650                 $h,
6651                 $m,
6652                 $hn_second,
6653                 $hn_partsecond,
6654                 $pb_repeatedhourdefault
6655             );
6656         }
6657     }
6658
6659
6660     // }}}
6661 }
6662
6663 // }}}
6664
6665 /*
6666  * Local variables:
6667  * mode: php
6668  * tab-width: 4
6669  * c-basic-offset: 4
6670  * c-hanging-comment-ender-p: nil
6671  * End:
6672  */