Source for file Date.php
Documentation is available at Date.php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
* Generic date handling class for PEAR
* Handles time zones and changes from local standard to local Summer
* time (daylight-saving time) through the Date_TimeZone class.
* Supports several operations from Date_Calc on Date objects.
* Copyright (c) 1997-2007 Baba Buehler, Pierre-Alain Joye, Firman
* Wandayandi, C.A. Woodcock
* Redistribution and use in source and binary forms, with or without
* modification, are permitted under the terms of the BSD License.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* @category Date and Time
* @author Baba Buehler <baba@babaz.com>
* @author Pierre-Alain Joye <pajoye@php.net>
* @author Firman Wandayandi <firman@php.net>
* @author C.A. Woodcock <c01234@netcomuk.co.uk>
* @copyright 1997-2007 Baba Buehler, Pierre-Alain Joye, Firman Wandayandi, C.A. Woodcock
* @license http://www.opensource.org/licenses/bsd-license.php
* @version CVS: $Id: Date.php,v 1.89 2008/03/23 18:34:16 c01234 Exp $
* @link http://pear.php.net/package/Date
define('DATE_ERROR_INVALIDDATE', 1 );
define('DATE_ERROR_INVALIDTIME', 2 );
define('DATE_ERROR_INVALIDTIMEZONE', 3 );
define('DATE_ERROR_INVALIDDATEFORMAT', 4 );
define('DATE_ERROR_INVALIDFORMATSTRING', 5 );
require_once 'Date/TimeZone.php';
require_once 'Date/Calc.php';
require_once 'Date/Span.php';
* Whether to capture the micro-time (in microseconds) by default
* in calls to 'Date::setNow()'. Note that this makes a call to
* 'gettimeofday()', which may not work on all systems.
* @since Constant available since Release 1.5.0
define('DATE_CAPTURE_MICROTIME_BY_DEFAULT', false );
* Whether to correct, by adding the local Summer time offset, the
* specified time if it falls in the 'skipped hour' (encountered
* when the clocks go forward).
* N.B. if specified as 'false', and if a time zone that adjusts
* for Summer time is specified, then an object of this class will
* be set to a semi-invalid state if an invalid time is set. That
* is, an error will not be returned, unless the user then calls
* a function, directly or indirectly, that accesses the time
* part of the object. So, for example, if the user calls:
* <code>$date_object->format2('HH.MI.SS')</code> or:
* <code>$date->object->addSeconds(30)</code>,
* an error will be returned if the time is invalid. However,
* <code>$date->object->addDays(1)</code>,
* for example, such that the time is no longer invalid, then the
* object will no longer be in this invalid state. This behaviour
* is intended to minimize unexpected errors when a user uses the
* class to do addition with days only, and does not intend to
* Of course, this constant will be unused if the user chooses to
* work in UTC or a time zone without Summer time, in which case
* this situation will never arise.
* This constant is set to 'true' by default for backwards-compatibility
* reasons, however, you are recommended to set it to 'false'. Note that the
* behaviour is not intended to match that of previous versions of the class
* in terms of ignoring the Summer time offset when making calculations which
* involve dates in both standard and Summer time - this was recognized as a
* bug - but in terms of returning a PEAR error object when the user sets the
* object to an invalid date (i.e. a time in the hour which is skipped when
* the clocks go forwards, which in Europe would be a time such as 01.30).
* Backwards compatibility here means that the behaviour is the same as it
* used to be, less the bug.
* Note that this problem is not an issue for the user if:
* (a) the user uses a time zone that does not observe Summer time, e.g. UTC
* (b) the user never accesses the time, that is, he never makes a call to
* Date::getHour() or Date::format("%H"), for example, even if he sets
* the time to something invalid
* (c) the user sets DATE_CORRECTINVALIDTIME_DEFAULT to true
* @since Constant available since Release 1.5.0
define('DATE_CORRECTINVALIDTIME_DEFAULT', true );
* Whether to validate dates (i.e. day-month-year, ignoring the time) by
* disallowing invalid dates (e.g. 31st February) being set by the following
* If the constant is set to 'true', then the date will be checked (by
* default), and if invalid, an error will be returned with the Date object
* This constant is set to 'false' by default for backwards-compatibility
* reasons, however, you are recommended to set it to 'true'.
* Note that setHour(), setMinute(), setSecond() and setPartSecond()
* allow an invalid date/time to be set regardless of the value of this
* @since Constant available since Release 1.5.0
define('DATE_VALIDATE_DATE_BY_DEFAULT', false );
* Whether, by default, to accept times including leap seconds (i.e. '23.59.60')
* when setting the date/time, and whether to count leap seconds in the
* Date::subtractSeconds()
* Date_Calc::addSeconds()
* This constant is set to 'false' by default for backwards-compatibility
* reasons, however, you are recommended to set it to 'true'.
* Note that this constant does not affect Date::addSpan() and
* Date::subtractSpan() which will not count leap seconds in any case.
* @since Constant available since Release 1.5.0
define('DATE_COUNT_LEAP_SECONDS', false );
// {{{ Output format constants (used in 'Date::getDate()')
* "YYYYMMSSTHHMMSS(Z|(+/-)HHMM)?"
define('DATE_FORMAT_ISO_BASIC', 2 );
* "YYYY-MM-SSTHH:MM:SS(Z|(+/-)HH:MM)?"
define('DATE_FORMAT_ISO_EXTENDED', 3 );
* "YYYY-MM-SSTHH:MM:SS(.S*)?(Z|(+/-)HH:MM)?"
define('DATE_FORMAT_ISO_EXTENDED_MICROTIME', 6 );
define('DATE_FORMAT_TIMESTAMP', 4 );
* long int, seconds since the unix epoch
define('DATE_FORMAT_UNIXTIME', 5 );
* Generic date handling class for PEAR
* Supports time zones with the Date_TimeZone class. Supports several
* operations from Date_Calc on Date objects.
* Note to developers: the class stores the local time and date in the
* local standard time. That is, it does not store the time as the
* local Summer time when and if the time zone is in Summer time. It
* is much easier to store local standard time and remember to offset
* it when the user requests it.
* @category Date and Time
* @author Baba Buehler <baba@babaz.com>
* @author Pierre-Alain Joye <pajoye@php.net>
* @author Firman Wandayandi <firman@php.net>
* @author C.A. Woodcock <c01234@netcomuk.co.uk>
* @copyright 1997-2007 Baba Buehler, Pierre-Alain Joye, Firman Wandayandi, C.A. Woodcock
* @license http://www.opensource.org/licenses/bsd-license.php
* @version Release: 1.5.0a1
* @link http://pear.php.net/package/Date
* @since Property available since Release 1.0
* @since Property available since Release 1.0
* @since Property available since Release 1.0
* @since Property available since Release 1.0
* @since Property available since Release 1.0
* @since Property available since Release 1.0
* @since Property available since Release 1.4.3
* The year in local standard time
* @since Property available since Release 1.5.0
* The month in local standard time
* @since Property available since Release 1.5.0
* The day in local standard time
* @since Property available since Release 1.5.0
* The hour in local standard time
* @since Property available since Release 1.5.0
* The minute in local standard time
* @since Property available since Release 1.5.0
* The second in local standard time
* @since Property available since Release 1.5.0
* The part-second in local standard time
* @since Property available since Release 1.5.0
var $on_standardpartsecond;
* Whether the object should accept and count leap seconds
* @since Property available since Release 1.5.0
var $ob_countleapseconds;
* Whether the time is valid as a local time (an invalid time
* is one that lies in the 'skipped hour' at the point that
* @see Date::isTimeValid()
* @since Property available since Release 1.5.0
var $ob_invalidtime = null;
* Date_TimeZone object for this date
* @var object Date_TimeZone object
* @since Property available since Release 1.0
* Defines the default weekday abbreviation length
* Formerly used by Date::format(), but now redundant - the abbreviation
* for the current locale of the machine is used.
* @since Property available since Release 1.4.4
var $getWeekdayAbbrnameLength = 3;
* Creates a new Date Object initialized to the current date/time in the
* system-default timezone by default. A date optionally
* passed in may be in the ISO 8601, TIMESTAMP or UNIXTIME format,
* or another Date object. If no date is passed, the current date/time
* If a date is passed and an exception is returned by 'setDate()'
* there is nothing that this function can do, so for this reason, it
* is advisable to pass no parameter and to make a separate call to
* 'setDate()'. A date/time should only be passed if known to be a
* valid ISO 8601 string or a valid Unix timestamp.
* @param mixed $date optional ISO 8601 date/time to initialize;
* @param bool $pb_countleapseconds whether to count leap seconds
* (defaults to DATE_COUNT_LEAP_SECONDS)
function Date($date = null ,
$pb_countleapseconds = DATE_COUNT_LEAP_SECONDS )
$this->ob_countleapseconds = $pb_countleapseconds;
if (is_a($date, 'Date')) {
// 'setDate()' expects a time zone to be already set:
$this->_setTZToDefault ();
* Copy values from another Date object
* Makes this Date a copy of another Date object. This is a
* PHP4-compatible implementation of '__clone()' in PHP5.
* @param object $date Date object to copy
$this->year = $date->year;
$this->month = $date->month;
$this->hour = $date->hour;
$this->minute = $date->minute;
$this->second = $date->second;
$this->partsecond = $date->partsecond;
$this->on_standardyear = $date->on_standardyear;
$this->on_standardmonth = $date->on_standardmonth;
$this->on_standardday = $date->on_standardday;
$this->on_standardhour = $date->on_standardhour;
$this->on_standardminute = $date->on_standardminute;
$this->on_standardsecond = $date->on_standardsecond;
$this->on_standardpartsecond = $date->on_standardpartsecond;
$this->ob_countleapseconds = $date->ob_countleapseconds;
$this->ob_invalidtime = $date->ob_invalidtime;
$this->getWeekdayAbbrnameLength = $date->getWeekdayAbbrnameLength;
* Copy values from another Date object
* Makes this Date a copy of another Date object. For PHP5
// This line of code would be preferable, but will only
// $this->tz = clone $this->tz;
* Sets the fields of a Date object based on the input date and format
* Format parameter should be one of the specified DATE_FORMAT_* constants:
* <code>DATE_FORMAT_ISO</code>
* - 'YYYY-MM-DD HH:MI:SS'
* <code>DATE_FORMAT_ISO_BASIC</code>
* - 'YYYYMMSSTHHMMSS(Z|(+/-)HHMM)?'
* <code>DATE_FORMAT_ISO_EXTENDED</code>
* - 'YYYY-MM-SSTHH:MM:SS(Z|(+/-)HH:MM)?'
* <code>DATE_FORMAT_ISO_EXTENDED_MICROTIME</code>
* - 'YYYY-MM-SSTHH:MM:SS(.S*)?(Z|(+/-)HH:MM)?'
* <code>DATE_FORMAT_TIMESTAMP</code>
* <code>DATE_FORMAT_UNIXTIME'</code>
* - long integer of the no of seconds since
* (1st January 1970 00.00.00 GMT)
* @param string $date input date
* @param int $format optional format constant
* (DATE_FORMAT_*) of the input date.
* This parameter is not needed,
* except to force the setting of the
* date from a Unix time-stamp
* (DATE_FORMAT_UNIXTIME).
* @param bool $pb_repeatedhourdefault value to return if repeated
* hour is specified (defaults
$format = DATE_FORMAT_ISO ,
$pb_repeatedhourdefault = false )
if (preg_match('/^([0-9]{4,4})-?(0[1-9]|1[0-2])-?(0[1-9]|[12][0-9]|3[01])' .
'([T\s]?([01][0-9]|2[0-3]):?' . // [hh]
'([0-5][0-9]):?([0-5][0-9]|60)(\.\d+)?' . // [mi]:[ss]
'(Z|[+\-][0-9]{2,2}(:?[0-5][0-9])?)?)?$/i', // offset
// DATE_FORMAT_ISO, ISO_BASIC, ISO_EXTENDED, and TIMESTAMP
// These formats are extremely close to each other. This regex
// is very loose and accepts almost any butchered format you could
// throw at it. e.g. 2003-10-07 19:45:15 and 2003-10071945:15
// are the same thing in the eyes of this regex, even though the
// latter is not a valid ISO 8601 date.
return PEAR ::raiseError ("'" .
"' is invalid calendar date",
isset ($regs[5 ]) ? $regs[5 ] : 0 ,
isset ($regs[6 ]) ? $regs[6 ] : 0 ,
isset ($regs[7 ]) ? $regs[7 ] : 0 ,
isset ($regs[8 ]) ? $regs[8 ] : 0.0 ,
$pb_repeatedhourdefault);
// Unix Time; N.B. Unix Time is defined relative to GMT,
// so it needs to be adjusted for the current time zone;
// however we do not know if it is in Summer time until
// we have converted it from Unix time:
// Get current time zone details:
// Input Unix time as UTC:
// Convert back to correct time zone:
return PEAR ::raiseError ("Date not in ISO 8601 format",
* Sets to local current time and time zone
* @param bool $pb_setmicrotime whether to set micro-time (defaults to the
* DATE_CAPTURE_MICROTIME_BY_DEFAULT)
* @since Method available since Release 1.5.0
function setNow($pb_setmicrotime = DATE_CAPTURE_MICROTIME_BY_DEFAULT )
$this->_setTZToDefault ();
$ha_unixtime = array ("sec" => time());
$this->setDate(date("Y-m-d H:i:s", $ha_unixtime["sec"]) .
(isset ($ha_unixtime["usec"]) ?
"." . sprintf("%06d", $ha_unixtime["usec"]) :
* Rounds the date according to the specified precision (defaults
* The precision parameter must be one of the following constants:
* <code>DATE_PRECISION_YEAR</code>
* <code>DATE_PRECISION_MONTH</code>
* <code>DATE_PRECISION_DAY</code>
* <code>DATE_PRECISION_HOUR</code>
* <code>DATE_PRECISION_10MINUTES</code>
* <code>DATE_PRECISION_MINUTE</code>
* <code>DATE_PRECISION_10SECONDS</code>
* <code>DATE_PRECISION_SECOND</code>
* N.B. the default is DATE_PRECISION_DAY
* The precision can also be specified as an integral offset from
* one of these constants, where the offset reflects a precision
* of 10 to the power of the offset greater than the constant.
* <code>DATE_PRECISION_YEAR - 1</code> rounds the date to the nearest 10
* <code>DATE_PRECISION_YEAR - 3</code> rounds the date to the nearest 1000
* <code>DATE_PRECISION_SECOND + 1</code> rounds the date to 1 decimal
* <code>DATE_PRECISION_SECOND + 3</code> rounds the date to 3 decimal
* <code>DATE_PRECISION_SECOND - 1</code> rounds the date to the nearest 10
* seconds (thus it is equivalent to
* DATE_PRECISION_10SECONDS)
* @param int $pn_precision a 'DATE_PRECISION_*' constant
* @param bool $pb_correctinvalidtime whether to correct, by adding the
* local Summer time offset, the rounded
* time if it falls in the skipped hour
* DATE_CORRECTINVALIDTIME_DEFAULT)
* @since Method available since Release 1.5.0
function round($pn_precision = DATE_PRECISION_DAY ,
$pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT )
$this->partsecond == 0.0 ?
$this->second + $this->partsecond,
$this->ob_countleapseconds);
$hn_second = intval($hn_secondraw);
$hn_partsecond = $hn_secondraw - $hn_second;
$hn_second = $hn_secondraw;
true , // This is unlikely anyway, but the
// day starts with the repeated hour
// ($pn_precision >= DATE_PRECISION_HOUR)
$this->on_standardminute,
$this->on_standardpartsecond == 0.0 ?
$this->on_standardsecond :
$this->on_standardsecond +
$this->on_standardpartsecond,
$this->ob_countleapseconds);
$hn_second = intval($hn_secondraw);
$hn_partsecond = $hn_secondraw - $hn_second;
$hn_second = $hn_secondraw;
// Very unlikely anyway (as I write, the only time zone like this
// is Lord Howe Island in Australia (offset of half an hour)):
// (This algorithm could be better)
$this->partsecond == 0.0 ?
$this->second + $this->partsecond,
$this->ob_countleapseconds);
$hn_second = intval($hn_secondraw);
$hn_partsecond = $hn_secondraw - $hn_second;
$hn_second = $hn_secondraw;
false , // This will be right half the time
$pb_correctinvalidtime); // This will be right
* Rounds seconds up or down to the nearest specified unit
* N.B. this function is equivalent to calling:
* <code>'round(DATE_PRECISION_SECOND + $pn_precision)'</code>
* @param int $pn_precision number of digits after the decimal point
* @param bool $pb_correctinvalidtime whether to correct, by adding the
* local Summer time offset, the rounded
* time if it falls in the skipped hour
* DATE_CORRECTINVALIDTIME_DEFAULT)
* @since Method available since Release 1.5.0
$pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT )
* Truncates the date according to the specified precision (by
* default, it truncates the time part of the date)
* The precision parameter must be one of the following constants:
* <code>DATE_PRECISION_YEAR</code>
* <code>DATE_PRECISION_MONTH</code>
* <code>DATE_PRECISION_DAY</code>
* <code>DATE_PRECISION_HOUR</code>
* <code>DATE_PRECISION_10MINUTES</code>
* <code>DATE_PRECISION_MINUTE</code>
* <code>DATE_PRECISION_10SECONDS</code>
* <code>DATE_PRECISION_SECOND</code>
* N.B. the default is DATE_PRECISION_DAY
* The precision can also be specified as an integral offset from
* one of these constants, where the offset reflects a precision
* of 10 to the power of the offset greater than the constant.
* <code>DATE_PRECISION_YEAR</code> truncates the month, day and time
* <code>DATE_PRECISION_YEAR - 1</code> truncates the unit part of the
* year, e.g. 1987 becomes 1980
* <code>DATE_PRECISION_YEAR - 3</code> truncates the hundreds part of the
* year, e.g. 1987 becomes 1000
* <code>DATE_PRECISION_SECOND + 1</code> truncates the part of the second
* less than 0.1 of a second, e.g.
* 3.26301 becomes 3.2 seconds
* <code>DATE_PRECISION_SECOND + 3</code> truncates the part of the second
* less than 0.001 of a second, e.g.
* 3.26301 becomes 3.263 seconds
* <code>DATE_PRECISION_SECOND - 1</code> truncates the unit part of the
* seconds (thus it is equivalent to
* DATE_PRECISION_10SECONDS)
* @param int $pn_precision a 'DATE_PRECISION_*' constant
* @param bool $pb_correctinvalidtime whether to correct, by adding the
* local Summer time offset, the
* truncated time if it falls in the
* skipped hour (defaults to
* DATE_CORRECTINVALIDTIME_DEFAULT)
* @since Method available since Release 1.5.0
function trunc($pn_precision = DATE_PRECISION_DAY ,
$pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT )
if ($hn_invprecision > 0 ) {
$hn_year = intval($this->year / pow(10 , $hn_invprecision)) *
pow(10 , $hn_invprecision);
// (Conversion to int necessary for PHP <= 4.0.6)
$hn_month = $this->month;
$hn_month = $this->month;
true , // This is unlikely anyway, but the
// day starts with the repeated
// hour the first time around
// Precision is at least equal to DATE_PRECISION_HOUR
- $this->second - $this->partsecond);
// (leap seconds irrelevant)
- $this->second - $this->partsecond);
// (leap seconds irrelevant)
(- $this->second % 10 ) - $this->partsecond);
// (leap seconds irrelevant)
// Assume Summer time offset cannot be composed of part-seconds:
$hn_partsecond = intval($this->on_standardpartsecond *
pow(10 , $hn_precision)) /
$this->on_standardminute,
$this->on_standardsecond,
* Truncates seconds according to the specified precision
* N.B. this function is equivalent to calling:
* <code>'Date::trunc(DATE_PRECISION_SECOND + $pn_precision)'</code>
* @param int $pn_precision number of digits after the decimal point
* @param bool $pb_correctinvalidtime whether to correct, by adding the
* local Summer time offset, the
* truncated time if it falls in the
* skipped hour (defaults to
* DATE_CORRECTINVALIDTIME_DEFAULT)
* @since Method available since Release 1.5.0
$pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT )
* Gets a string (or other) representation of this date
* Returns a date in the format specified by the DATE_FORMAT_* constants.
* @param int $format format constant (DATE_FORMAT_*) of the output date
* @return string the date in the requested format
function getDate($format = DATE_FORMAT_ISO )
return $this->format("%Y-%m-%d %T");
$format = "%Y%m%dT%H%M%S";
return $this->format($format);
$format = "%Y-%m-%dT%H:%M:%S";
return $this->format($format);
$format = "%Y-%m-%dT%H:%M:%s";
return $this->format($format);
return $this->format("%Y%m%d%H%M%S");
// Enter a time in UTC, so use 'gmmktime()' (the alternative
// is to offset additionally by the local time, but the object
// is not necessarily using local time):
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$this->on_standardminute,
$this->on_standardsecond,
$this->on_standardyear) -
$this->tz->getRawOffset() / 1000; // N.B. Unix-time excludes
* Date pretty printing, similar to strftime()
* Formats the date in the given format, much like
* strftime(). Most strftime() options are supported.<br><br>
* Formatting options:<br><br>
* <code>%a </code> abbreviated weekday name (Sun, Mon, Tue) <br>
* <code>%A </code> full weekday name (Sunday, Monday, Tuesday) <br>
* <code>%b </code> abbreviated month name (Jan, Feb, Mar) <br>
* <code>%B </code> full month name (January, February, March) <br>
* <code>%C </code> century number (the year divided by 100 and truncated
* to an integer, range 00 to 99) <br>
* <code>%d </code> day of month (range 00 to 31) <br>
* <code>%D </code> equivalent to "%m/%d/%y" <br>
* <code>%e </code> day of month without leading noughts (range 0 to 31) <br>
* <code>%E </code> Julian day - no of days since Monday, 24th November,
* 4714 B.C. (in the proleptic Gregorian calendar) <br>
* <code>%g </code> like %G, but without the century <br>
* <code>%G </code> the 4-digit year corresponding to the ISO week
* number (see %V). This has the same format and value
* as %Y, except that if the ISO week number belongs
* to the previous or next year, that year is used
* <code>%h </code> hour as decimal number without leading noughts (0
* <code>%H </code> hour as decimal number (00 to 23) <br>
* <code>%i </code> hour as decimal number on 12-hour clock without
* leading noughts (1 to 12) <br>
* <code>%I </code> hour as decimal number on 12-hour clock (01 to 12) <br>
* <code>%j </code> day of year (range 001 to 366) <br>
* <code>%m </code> month as decimal number (range 01 to 12) <br>
* <code>%M </code> minute as a decimal number (00 to 59) <br>
* <code>%n </code> newline character ("\n") <br>
* <code>%o </code> raw timezone offset expressed as '+/-HH:MM' <br>
* <code>%O </code> dst-corrected timezone offset expressed as '+/-HH:MM' <br>
* <code>%p </code> either 'am' or 'pm' depending on the time <br>
* <code>%P </code> either 'AM' or 'PM' depending on the time <br>
* <code>%r </code> time in am/pm notation; equivalent to "%I:%M:%S %p" <br>
* <code>%R </code> time in 24-hour notation; equivalent to "%H:%M" <br>
* <code>%s </code> seconds including the micro-time (the decimal
* representation less than one second to six decimal
* <code>%S </code> seconds as a decimal number (00 to 59) <br>
* <code>%t </code> tab character ("\t") <br>
* <code>%T </code> current time; equivalent to "%H:%M:%S" <br>
* <code>%u </code> day of week as decimal (1 to 7; where 1 = Monday) <br>
* <code>%U </code> week number of the current year as a decimal
* number, starting with the first Sunday as the first
* day of the first week (i.e. the first full week of
* the year, and the week that contains 7th January)
* <code>%V </code> the ISO 8601:1988 week number of the current year
* as a decimal number, range 01 to 53, where week 1
* is the first week that has at least 4 days in the
* current year, and with Monday as the first day of
* the week. (Use %G or %g for the year component
* that corresponds to the week number for the
* <code>%w </code> day of week as decimal (0 to 6; where 0 = Sunday) <br>
* <code>%W </code> week number of the current year as a decimal
* number, starting with the first Monday as the first
* day of the first week (i.e. the first full week of
* the year, and the week that contains 7th January)
* <code>%y </code> year as decimal (range 00 to 99) <br>
* <code>%Y </code> year as decimal including century (range 0000 to
* <code>%Z </code> Abbreviated form of time zone name, e.g. 'GMT', or
* the abbreviation for Summer time if the date falls
* in Summer time, e.g. 'BST'. <br>
* <code>%% </code> literal '%' <br>
* The following codes render a different output to that of 'strftime()':
* <code>%e</code> in 'strftime()' a single digit is preceded by a space
* <code>%h</code> in 'strftime()' is equivalent to '%b'
* <code>%U</code> '%U' and '%W' are different in 'strftime()' in that
* if week 1 does not start on 1st January, '00' is
* returned, whereas this function returns '53', that is,
* the week is counted as the last of the previous year.
* @param string $format the format string for returned date/time
* @return string date/time in given format
for ($strpos = 0; $strpos < strlen($format); $strpos++ ) {
$char = substr($format, $strpos, 1 );
$nextchar = substr($format, $strpos + 1 , 1 );
$this->month, $this->year,
$this->getWeekdayAbbrnameLength);
$this->month, $this->year);
$output .= sprintf("%02d", $this->day);
$output .= sprintf("%02d/%02d/%02d", $this->month,
$this->day, $this->year);
list ($hn_isoyear, $hn_isoweek, $hn_isoday) =
$output .= sprintf("%02d", $hn_isoyear % 100 );
list ($hn_isoyear, $hn_isoweek, $hn_isoday) =
$output .= sprintf("%04d", $hn_isoyear);
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$output .= sprintf("%d", $this->hour);
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$output .= sprintf("%02d", $this->hour);
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$hour = $this->hour + 1 > 12 ?
$output .= sprintf("%02d", $this->month);
$output .= sprintf("%02d", $this->minute);
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$direction = $offms >= 0 ? "+" : "-";
$offmins = abs($offms) / 1000 / 60;
$minutes = $offmins % 60;
$output .= sprintf("%s%02d:%02d", $direction, $hours, $minutes);
$direction = $offms >= 0 ? "+" : "-";
$offmins = abs($offms) / 1000 / 60;
$minutes = $offmins % 60;
$output .= sprintf("%s%02d:%02d", $direction, $hours, $minutes);
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$output .= $this->hour >= 12 ? "pm" : "am";
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$output .= $this->hour >= 12 ? "PM" : "AM";
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$hour = $this->hour + 1 > 12 ?
$output .= sprintf("%02d:%02d:%02d %s",
$this->hour >= 12 ? "PM" : "AM");
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$output .= sprintf("%02d:%02d", $this->hour, $this->minute);
(float) ((float) $this->second +
$output .= sprintf("%02d", $this->second);
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$output .= sprintf("%02d:%02d:%02d",
$output .= $hn_dayofweek == 0 ? 7 : $hn_dayofweek;
$output .= sprintf("%02d", $ha_week[1 ]);
list ($hn_isoyear, $hn_isoweek, $hn_isoday) =
$output .= sprintf("%02d", $ha_week[1 ]);
($this->year < 0 ? '3' : '2') .
($this->year < 0 ? '5' : '4') .
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$output .= $char. $nextchar;
// {{{ _getOrdinalSuffix()
* Returns appropriate ordinal suffix (i.e. 'th', 'st', 'nd' or 'rd')
* @param int $pn_num number with which to determine suffix
* @param bool $pb_uppercase boolean specifying if the suffix should be
* @since Method available since Release 1.5.0
function _getOrdinalSuffix ($pn_num, $pb_uppercase = true )
switch (($pn_numabs = abs($pn_num)) % 100 ) {
switch ($pn_numabs % 10 ) {
return $pb_uppercase ? strtoupper($hs_suffix) : $hs_suffix;
* Converts a number to its word representation
* Private helper function, particularly for 'format2()'. N.B. The
* second argument is the 'SP' code which can be specified in the
* format string for 'format2()' and is interpreted as follows:
* 'SP' - returns upper-case spelling, e.g. 'FOUR HUNDRED'
* 'Sp' - returns spelling with first character of each word
* capitalized, e.g. 'Four Hundred'
* 'sp' - returns lower-case spelling, e.g. 'four hundred'
* @param int $pn_num number to be converted to words
* @param bool $pb_ordinal boolean specifying if the number should
* @param string $ps_capitalization string for specifying capitalization
* @param string $ps_locale language name abbreviation used for
* formatting numbers as spelled-out words
* @since Method available since Release 1.5.0
function _spellNumber ($pn_num,
$ps_capitalization = "SP",
include_once "Numbers/Words.php";
$hs_words = Numbers_Words ::toWords ($pn_num, $ps_locale);
if (Pear ::isError ($hs_words)) {
if ($pb_ordinal && substr($ps_locale, 0 , 2 ) == "en") {
if (($pn_rem = ($pn_numabs = abs($pn_num)) % 100 ) == 12 ) {
$hs_words = substr($hs_words, 0 , -2 ) . "fth";
} else if ($pn_rem >= 11 && $pn_rem <= 15 ) {
switch ($pn_numabs % 10 ) {
$hs_words = substr($hs_words, 0 , -3 ) . "first";
$hs_words = substr($hs_words, 0 , -3 ) . "second";
$hs_words = substr($hs_words, 0 , -3 ) . "ird";
$hs_words = substr($hs_words, 0 , -2 ) . "fth";
switch (substr($hs_words, -1 )) {
$hs_words = substr($hs_words, 0 , -1 ) . "th";
$hs_words = substr($hs_words, 0 , -1 ) . "ieth";
if (($hs_char = substr($ps_capitalization, 0 , 1 )) ==
} else if (($hs_char = substr($ps_capitalization, 1 , 1 )) ==
* Formats a number according to the specified format string
* Private helper function, for 'format2()', which interprets the
* codes 'SP' and 'TH' and the combination of the two as follows:
* <code>TH</code> Ordinal number
* <code>SP</code> Spelled cardinal number
* <code>SPTH</code> Spelled ordinal number (combination of 'SP' and 'TH'
* Code 'SP' can have the following three variations (which can also be used
* in combination with 'TH'):
* <code>SP</code> returns upper-case spelling, e.g. 'FOUR HUNDRED'
* <code>Sp</code> returns spelling with first character of each word
* capitalized, e.g. 'Four Hundred'
* <code>sp</code> returns lower-case spelling, e.g. 'four hundred'
* Code 'TH' can have the following two variations (although in combination
* with code 'SP', the case specification of 'SP' takes precedence):
* <code>TH</code> returns upper-case ordinal suffix, e.g. 400TH
* <code>th</code> returns lower-case ordinal suffix, e.g. 400th
* N.B. The format string is passed by reference, in order to pass back
* the part of the format string that matches the valid codes 'SP' and
* 'TH'. If none of these are found, then it is set to an empty string;
* If both codes are found then a string is returned with code 'SP'
* preceding code 'TH' (i.e. 'SPTH', 'Spth' or 'spth').
* @param int $pn_num integer to be converted to words
* @param string &$ps_format string of formatting codes (max. length 4)
* @param int $pn_numofdigits no of digits to display if displayed as
* numeral (i.e. not spelled out), not
* including the sign (if negative); to
* allow all digits specify 0
* @param bool $pb_nopad boolean specifying whether to suppress
* padding with leading noughts (if displayed
* @param bool $pb_nosign boolean specifying whether to suppress the
* display of the sign (if negative)
* @param string $ps_locale language name abbreviation used for
* @param string $ps_thousandsep optional thousand-separator (e.g. a comma)
* numbers as spelled-out words
* @param int $pn_padtype optional integer to specify padding (if
* displayed as numeral) - can be
* STR_PAD_LEFT or STR_PAD_RIGHT
* @since Method available since Release 1.5.0
function _formatNumber ($pn_num,
$pn_padtype = STR_PAD_LEFT )
$hs_code1 = substr($ps_format, 0 , 2 );
$hs_code2 = substr($ps_format, 2 , 2 );
$hn_absnum = abs($pn_num);
if ($pn_numofdigits > 0 && strlen($hn_absnum) > $pn_numofdigits) {
(is_null($hs_th) ? "" : ($hs_sp == "SP" ? "TH" : "th"));
return $this->_spellNumber (!$pb_nosign && $pn_num < 0 ?
// Display number as Arabic numeral:
$hs_num = str_pad($hs_num, $pn_numofdigits, "0", $pn_padtype);
for ($i = strlen($hs_num) - 3; $i > 0; $i -= 3 ) {
$hs_num = substr($hs_num, 0 , $i) .
$this->_getOrdinalSuffix ($pn_num,
* Extended version of 'format()' with variable-length formatting codes
* Most codes reproduce the no of digits equal to the length of the code,
* for example, 'YYY' will return the last 3 digits of the year, and so
* the year 2007 will produce '007', and the year 89 will produce '089',
* unless the no-padding code is used as in 'NPYYY', which will return
* For negative values, the sign will be discarded, unless the 'S' code
* is used in combination, but note that for positive values the value
* will be padded with a leading space unless it is suppressed with
* the no-padding modifier, for example for 2007:
* <code>YYYY</code> returns '2007'
* <code>SYYYY</code> returns ' 2007'
* <code>NPSYYYY</code> returns '2007'
* The no-padding modifier 'NP' can be used with numeric codes to
* suppress leading (or trailing in the case of code 'F') noughts, and
* with character-returning codes such as 'DAY' to suppress trailing
* spaces, which will otherwise be padded to the maximum possible length
* of the return-value of the code; for example, for Monday:
* <code>Day</code> returns 'Monday ' because the maximum length of
* this code is 'Wednesday';
* <code>NPDay</code> returns 'Monday'
* N.B. this code affects the code immediately following only, and
* without this code the default is always to apply padding.
* Most character-returning codes, such as 'MONTH', will
* set the capitalization according to the code, so for example:
* <code>MONTH</code> returns upper-case spelling, e.g. 'JANUARY'
* <code>Month</code> returns spelling with first character of each word
* capitalized, e.g. 'January'
* <code>month</code> returns lower-case spelling, e.g. 'january'
* Where it makes sense, numeric codes can be combined with a following
* 'SP' code which spells out the number, or with a 'TH' code, which
* renders the code as an ordinal ('TH' only works in English), for
* example, for 31st December:
* <code>DD</code> returns '31'
* <code>DDTH</code> returns '31ST'
* <code>DDth</code> returns '31st'
* <code>DDSP</code> returns 'THIRTY-ONE'
* <code>DDSp</code> returns 'Thirty-one'
* <code>DDsp</code> returns 'thirty-one'
* <code>DDSPTH</code> returns 'THIRTY-FIRST'
* <code>DDSpth</code> returns 'Thirty-first'
* <code>DDspth</code> returns 'thirty-first'
* All formatting options:
* <code>-</code> All punctuation and white-space is reproduced unchanged
* <code>"text"</code> Quoted text is reproduced unchanged (escape using
* <code>AD</code> AD indicator with or without full stops; N.B. if you
* are using 'Astronomical' year numbering then 'A.D./B.C.'
* indicators will be out for negative years
* <code>AM</code> Meridian indicator with or without full stops
* <code>BC</code> BC indicator with or without full stops
* <code>BCE</code> BCE indicator with or without full stops
* <code>CC</code> Century, i.e. the year divided by 100, discarding the
* remainder; 'S' prefixes negative years with a minus sign
* <code>CE</code> CE indicator with or without full stops
* <code>D</code> Day of week (0-6), where 0 represents Sunday
* <code>DAY</code> Name of day, padded with blanks to display width of the
* widest name of day in the locale of the machine
* <code>DD</code> Day of month (1-31)
* <code>DDD</code> Day of year (1-366)
* <code>DY</code> Abbreviated name of day
* <code>FFF</code> Fractional seconds; no radix character is printed. The
* no of 'F's determines the no of digits of the
* part-second to return; e.g. 'HH:MI:SS.FF'
* <code>F[integer]</code> The integer after 'F' specifies the number of
* digits of the part-second to return. This is an
* alternative to using F[integer], and 'F3' is thus
* equivalent to using 'FFF'.
* <code>HH</code> Hour of day (0-23)
* <code>HH12</code> Hour of day (1-12)
* <code>HH24</code> Hour of day (0-23)
* <code>ID</code> Day of week (1-7) based on the ISO standard
* <code>IW</code> Week of year (1-52 or 1-53) based on the ISO standard
* <code>IYYY</code> 4-digit year based on the ISO 8601 standard; 'S'
* prefixes negative years with a minus sign
* <code>IYY</code> Last 3, 2, or 1 digit(s) of ISO year
* <code>J</code> Julian day - the number of days since Monday, 24th
* November, 4714 B.C. (proleptic Gregorian calendar)
* <code>MI</code> Minute (0-59)
* <code>MM</code> Month (01-12; January = 01)
* <code>MON</code> Abbreviated name of month
* <code>MONTH</code> Name of month, padded with blanks to display width of
* the widest name of month in the date language used for
* <code>PM</code> Meridian indicator with or without full stops
* <code>Q</code> Quarter of year (1, 2, 3, 4; January - March = 1)
* <code>RM</code> Roman numeral month (I-XII; January = I); N.B. padded
* <code>SS</code> Second (0-59)
* <code>SSSSS</code> Seconds past midnight (0-86399)
* <code>TZC</code> Abbreviated form of time zone name, e.g. 'GMT', or the
* abbreviation for Summer time if the date falls in Summer
* N.B. this is not a unique identifier - for this purpose
* use the time zone region (code 'TZR').
* <code>TZH</code> Time zone hour; 'S' prefixes the hour with the correct
* sign, (+/-), which otherwise is not displayed. Note
* that the leading nought can be suppressed with the
* no-padding code 'NP'). Also note that if you combine
* with the 'SP' code, the sign will not be spelled out.
* (I.e. 'STZHSp' will produce '+One', for example, and
* 'TZH:TZM' will produce, for example, '+05:30'. (Also
* <code>TZI</code> Whether or not the date is in Summer time (daylight
* saving time). Returns '1' if Summer time, else '0'.
* <code>TZM</code> Time zone minute, without any +/- sign. (Also see
* <code>TZN</code> Long form of time zone name, e.g.
* 'Greenwich Mean Time', or the name of the Summer time if
* the date falls in Summer time, e.g.
* 'British Summer Time'. N.B. this is not a unique
* identifier - for this purpose use the time zone region
* <code>TZO</code> Time zone offset in ISO 8601 form - that is, 'Z' if
* UTC, else [+/-][hh]:[mm] (which would be equivalent
* to 'STZH:TZM'). Note that this result is right padded
* with spaces by default, (i.e. if 'Z').
* <code>TZS</code> Time zone offset in seconds; 'S' prefixes negative
* sign with minus sign '-' if negative, and no sign if
* positive (i.e. -43200 to 50400).
* <code>TZR</code> Time zone region, that is, the name or ID of the time
* zone e.g. 'Europe/London'. This value is unique for
* <code>U</code> Seconds since the Unix Epoch -
* January 1 1970 00:00:00 GMT
* <code>W</code> 'Absolute' week of month (1-5), counting week 1 as
* 1st-7th of the year, regardless of the day
* <code>W1</code> Week of year (1-54), counting week 1 as the week that
* <code>W4</code> Week of year (1-53), counting week 1 as the week that
* contains 4th January (i.e. first week with at least 4
* <code>W7</code> Week of year (1-53), counting week 1 as the week that
* contains 7th January (i.e. first full week)
* <code>WW</code> 'Absolute' week of year (1-53), counting week 1 as
* 1st-7th of the year, regardless of the day
* <code>YEAR</code> Year, spelled out; 'S' prefixes negative years with
* 'MINUS'; N.B. 'YEAR' differs from 'YYYYSP' in that the
* first will render 1923, for example, as 'NINETEEN
* TWENTY-THREE, and the second as 'ONE THOUSAND NINE
* <code>YYYY</code> 4-digit year; 'S' prefixes negative years with a minus
* <code>YYY</code> Last 3, 2, or 1 digit(s) of year
* <code>Y,YYY</code> Year with thousands-separator in this position; five
* <code>Y·YYY</code> N.B. space-dot (mid-dot, interpunct) is valid only in
* ISO 8859-1 (so take care when using UTF-8 in
* In addition the following codes can be used in combination with other
* Codes that modify the next code in the format string:
* <code>NP</code> 'No Padding' - Returns a value with no trailing blanks
* and no leading or trailing noughts; N.B. that the
* default is to include this padding in the return string.
* N.B. affects the code immediately following only.
* Codes that modify the previous code in the format string (can only
* be used with integral codes such as 'MM'):
* <code>TH</code> Ordinal number
* <code>SP</code> Spelled cardinal number
* <code>SPTH</code> Spelled ordinal number (combination of 'SP' and 'TH'
* Code 'SP' can have the following three variations (which can also be used
* in combination with 'TH'):
* <code>SP</code> returns upper-case spelling, e.g. 'FOUR HUNDRED'
* <code>Sp</code> returns spelling with first character of each word
* capitalized, e.g. 'Four Hundred'
* <code>sp</code> returns lower-case spelling, e.g. 'four hundred'
* Code 'TH' can have the following two variations (although in combination
* with code 'SP', the case specification of 'SP' takes precedence):
* <code>TH</code> returns upper-case ordinal suffix, e.g. 400TH
* <code>th</code> returns lower-case ordinal suffix, e.g. 400th
* @param string $ps_format format string for returned date/time
* @param string $ps_locale language name abbreviation used for formatting
* numbers as spelled-out words
* @return string date/time in given format
* @since Method available since Release 1.5.0
function format2($ps_format, $ps_locale = "en_GB")
if (!preg_match('/^("([^"\\\\]|\\\\\\\\|\\\\")*"|(D{1,3}|S?C+|' .
'HH(12|24)?|I[DW]|S?IY*|J|M[IM]|Q|SS(SSS)?|S?TZ[HS]|' .
'TZM|U|W[W147]?|S?Y{1,3}([,.·\' ]?YYY)*)(SP(TH)?|' .
'TH(SP)?)?|AD|A\.D\.|AM|A\.M\.|BCE?|B\.C\.(E\.)?|CE|' .
'C\.E\.|DAY|DY|F(F*|[1-9][0-9]*)|MON(TH)?|NP|PM|' .
'P\.M\.|RM|TZ[CINOR]|S?YEAR|[^A-Z0-9"])*$/i',
return PEAR ::raiseError (" Invalid date format '$ps_format'" ,
$hb_showsignflag = false;
while ($i < strlen($ps_format)) {
$hb_showsignflag = false;
switch ($hs_char = substr($ps_format, $i, 1 )) {
$i += strlen($ha_matches[0 ][0 ]) + 1;
$ret .= $this->year >= 0 ?
($hb_lower ? "a.d." : "A.D.") :
($hb_lower ? "b.c." : "B.C.");
$ret .= $this->year >= 0 ?
($hb_lower ? "ad" : "AD") :
($hb_lower ? "bc" : "BC");
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$ret .= $this->hour < 12 ?
($hb_lower ? "a.m." : "A.M.") :
($hb_lower ? "p.m." : "P.M.");
$ret .= $this->hour < 12 ?
($hb_lower ? "am" : "AM") :
($hb_lower ? "pm" : "PM");
// Check for 'B.C.E.' first:
$hs_era = $hb_lower ? "c.e." : "C.E.";
str_pad($hs_era, 6 , " ", STR_PAD_RIGHT );
$ret .= $hb_lower ? "b.c.e." : "B.C.E.";
$hs_era = $hb_lower ? "ce" : "CE";
str_pad($hs_era, 3 , " ", STR_PAD_RIGHT );
$ret .= $hb_lower ? "bce" : "BCE";
$ret .= $this->year >= 0 ?
($hb_lower ? "a.d." : "A.D.") :
($hb_lower ? "b.c." : "B.C.");
$ret .= $this->year >= 0 ?
($hb_lower ? "ad" : "AD") :
($hb_lower ? "bc" : "BC");
$hs_era = $hb_lower ? "c.e." : "C.E.";
str_pad($hs_era, 6 , " ", STR_PAD_RIGHT );
$ret .= $hb_lower ? "b.c.e." : "B.C.E.";
$hs_era = $hb_lower ? "ce" : "CE";
str_pad($hs_era, 3 , " ", STR_PAD_RIGHT );
$ret .= $hb_lower ? "bce" : "BCE";
// Check next code is not 'CE' or 'C.E.'
$hn_century = intval($this->year / 100 );
$hs_numberformat = substr($ps_format, $i + $hn_codelen, 4 );
$hs_century = $this->_formatNumber ($hn_century,
if (Pear ::isError ($hs_century))
$i += $hn_codelen + strlen($hs_numberformat);
// Set week-day padding variable:
$hn_weekdaypad = max($hn_weekdaypad,
(substr($ps_format, $i + 1 , 1 ) == "A" ?
(substr($ps_format, $i + 1 , 1 ) == "Y" ?
$hs_numberformat = substr($ps_format, $i + 3 , 4 );
$hs_day = $this->_formatNumber ($hn_day,
if (Pear ::isError ($hs_day))
$i += 3 + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + 2 , 4 );
$hs_day = $this->_formatNumber ($this->day,
if (Pear ::isError ($hs_day))
$i += 2 + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + 1 , 4 );
$hs_day = $this->_formatNumber ($hn_day,
if (Pear ::isError ($hs_day))
$i += 1 + strlen($hs_numberformat);
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$hn_partsecdigits = substr($ps_format, $i + 1 , $hn_codelen - 1 );
// Check next code is not F[numeric]:
$hn_partsecdigits = $hn_codelen;
$hs_partsec = (string) $this->partsecond;
if (preg_match('/^([0-9]+)(\.([0-9]+))?E-([0-9]+)$/i',
$hs_partsec = substr($hs_partsec, 2 );
$hs_partsec = substr($hs_partsec, 0 , $hn_partsecdigits);
// '_formatNumber() will not work for this because the
// part-second is an int, and we want it to behave like a float:
$hs_partsec = rtrim($hs_partsec, "0");
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$hn_hour = $this->hour % 12;
$hs_numberformat = substr($ps_format, $i + $hn_codelen, 4 );
$hs_hour = $this->_formatNumber ($hn_hour,
if (Pear ::isError ($hs_hour))
$i += $hn_codelen + strlen($hs_numberformat);
list ($hn_isoyear, $hn_isoweek, $hn_isoday) =
$hs_numberformat = substr($ps_format, $i + 2 , 4 );
$hs_isoday = $this->_formatNumber ($hn_isoday,
if (Pear ::isError ($hs_isoday))
$i += 2 + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + 2 , 4 );
$hs_isoweek = $this->_formatNumber ($hn_isoweek,
if (Pear ::isError ($hs_isoweek))
$i += 2 + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + $hn_codelen, 4 );
$hs_isoyear = $this->_formatNumber ($hn_isoyear,
if (Pear ::isError ($hs_isoyear))
$i += $hn_codelen + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + 1 , 4 );
// Allow sign if negative; allow all digits (specify nought);
$hs_jd = $this->_formatNumber ($hn_jd,
if (Pear ::isError ($hs_jd))
$i += 1 + strlen($hs_numberformat);
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$hs_numberformat = substr($ps_format, $i + 2 , 4 );
$hs_minute = $this->_formatNumber ($this->minute,
if (Pear ::isError ($hs_minute))
$i += 2 + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + 2 , 4 );
$hs_month = $this->_formatNumber ($this->month,
if (Pear ::isError ($hs_month))
$i += 2 + strlen($hs_numberformat);
// Set month padding variable:
$hn_monthpad = max($hn_monthpad,
(substr($ps_format, $i + 1 , 1 ) == "O" ?
(substr($ps_format, $i + 1 , 1 ) == "O" ?
// No-Padding rule 'NP' applies to the next code (either trailing
// spaces or leading/trailing noughts):
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$ret .= $this->hour < 12 ?
($hb_lower ? "a.m." : "A.M.") :
($hb_lower ? "p.m." : "P.M.");
$ret .= $this->hour < 12 ?
($hb_lower ? "am" : "AM") :
($hb_lower ? "pm" : "PM");
// N.B. Current implementation ignores the day and year, but
// it is possible that a different implementation might be
// desired, so pass these parameters anyway:
$hs_numberformat = substr($ps_format, $i + 1 , 4 );
$hs_quarter = $this->_formatNumber ($hn_quarter,
if (Pear ::isError ($hs_quarter))
$i += 1 + strlen($hs_numberformat);
$hs_monthroman = $hb_lower ?
str_pad($hs_monthroman, 4 , " ", STR_PAD_LEFT );
// Check for 'SSSSS' before 'SS':
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$hs_numberformat = substr($ps_format, $i + 5 , 4 );
$hs_second = $this->_formatNumber ($hn_second,
if (Pear ::isError ($hs_second))
$i += 5 + strlen($hs_numberformat);
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$hs_numberformat = substr($ps_format, $i + 2 , 4 );
$hs_second = $this->_formatNumber ($this->second,
if (Pear ::isError ($hs_second))
$i += 2 + strlen($hs_numberformat);
// One of the following codes:
// This time-zone-related code can be called when the time is
// invalid, but the others should return an error:
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$hs_numberformat = substr($ps_format, $i + 3 , 4 );
$hn_tzh = intval($hn_tzoffset / 3600000 );
// Suppress sign here (it is added later):
$hs_tzh = $this->_formatNumber ($hn_tzh,
if (Pear ::isError ($hs_tzh))
// Display sign, even if positive:
$ret .= ($hb_nosign ? "" : ($hn_tzh >= 0 ? '+' : '-')) .
$i += 3 + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + 3 , 4 );
$hn_tzm = intval(($hn_tzoffset % 3600000 ) / 60000 );
$hs_tzm = $this->_formatNumber ($hn_tzm,
if (Pear ::isError ($hs_tzm))
$i += 3 + strlen($hs_numberformat);
$hn_tzh = intval(abs($hn_tzoffset) / 3600000 );
$hn_tzm = intval((abs($hn_tzoffset) % 3600000 ) / 60000 );
$ret .= $hb_nopad ? "Z" : "Z ";
// Display sign, even if positive:
$ret .= ($hn_tzoffset >= 0 ? '+' : '-') .
$hs_numberformat = substr($ps_format, $i + 3 , 4 );
$hn_tzs = intval($hn_tzoffset / 1000 );
$hs_tzs = $this->_formatNumber ($hn_tzs,
if (Pear ::isError ($hs_tzs))
$i += 3 + strlen($hs_numberformat);
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$hs_numberformat = substr($ps_format, $i + 1 , 4 );
// Allow sign if negative; allow all digits (specify nought);
$hs_unixtime = $this->_formatNumber ($hn_unixtime,
if (Pear ::isError ($hs_unixtime))
$i += 1 + strlen($hs_numberformat);
// Check for 'WW' before 'W':
$hs_numberformat = substr($ps_format, $i + 2 , 4 );
$hs_week = $this->_formatNumber ($hn_week,
if (Pear ::isError ($hs_week))
$i += 2 + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + 2 , 4 );
$hs_week = $this->_formatNumber ($hn_week,
if (Pear ::isError ($hs_week))
$i += 2 + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + 2 , 4 );
$hs_week = $this->_formatNumber ($hn_week,
if (Pear ::isError ($hs_week))
$i += 2 + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + 2 , 4 );
$hs_week = $this->_formatNumber ($hn_week,
if (Pear ::isError ($hs_week))
$i += 2 + strlen($hs_numberformat);
$hs_numberformat = substr($ps_format, $i + 1 , 4 );
$hs_week = $this->_formatNumber ($hn_week,
if (Pear ::isError ($hs_week))
$i += 1 + strlen($hs_numberformat);
// Check for 'YEAR' first:
switch (substr($ps_format, $i, 2 )) {
if (($hn_yearabs = abs($this->year)) < 100 ||
$hn_yearabs % 100 < 10 ) {
$hs_numberformat = $hs_spformat;
// Allow all digits (specify nought); padding irrelevant:
$hs_year = $this->_formatNumber ($this->year,
if (Pear ::isError ($hs_year))
// Year is spelled 'Nineteen Twelve' rather than
// 'One thousand Nine Hundred Twelve':
$hn_century = intval($this->year / 100 );
$hs_numberformat = $hs_spformat;
// Allow all digits (specify nought); padding irrelevant:
$hs_century = $this->_formatNumber ($hn_century,
if (Pear ::isError ($hs_century))
$ret .= $hs_century . " ";
$hs_numberformat = $hs_spformat;
// Discard sign; padding irrelevant:
$hs_year = $this->_formatNumber ($this->year,
if (Pear ::isError ($hs_year))
$hs_thousandsep = $ha_matches[1 ];
// Check next code is not 'YEAR'
$hs_numberformat = substr($ps_format, $i + $hn_codelen, 4 );
$hs_year = $this->_formatNumber ($this->year,
if (Pear ::isError ($hs_year))
$i += $hn_codelen + strlen($hs_numberformat);
* Formats the date in the same way as 'format()', but using the
* formatting codes used by the PHP function 'date()'
* All 'date()' formatting options are supported except 'B'. This
* function also responds to the DATE_* constants, such as DATE_COOKIE,
* which are specified at:
* http://www.php.net/manual/en/ref.datetime.php#datetime.constants
* <code>d</code> Day of the month, 2 digits with leading zeros (01 to 31)
* <code>D</code> A textual representation of a day, three letters ('Mon'
* <code>j</code> Day of the month without leading zeros (1 to 31)
* <code>l</code> [lowercase 'L'] A full textual representation of the day
* of the week ('Sunday' to 'Saturday')
* <code>N</code> ISO-8601 numeric representation of the day of the week
* (1 (for Monday) to 7 (for Sunday))
* <code>S</code> English ordinal suffix for the day of the month, 2
* characters ('st', 'nd', 'rd' or 'th')
* <code>w</code> Numeric representation of the day of the week (0 (for
* Sunday) to 6 (for Saturday))
* <code>z</code> The day of the year, starting from 0 (0 to 365)
* <code>W</code> ISO-8601 week number of year, weeks starting on Monday
* <code>F</code> A full textual representation of a month ('January' to
* <code>m</code> Numeric representation of a month, with leading zeros
* <code>M</code> A short textual representation of a month, three letters
* <code>n</code> Numeric representation of a month, without leading zeros
* <code>t</code> Number of days in the given month (28 to 31)
* <code>L</code> Whether it is a leap year (1 if it is a leap year, 0
* <code>o</code> ISO-8601 year number. This has the same value as Y,
* except that if the ISO week number (W) belongs to the
* previous or next year, that year is used instead.
* <code>Y</code> A full numeric representation of a year, 4 digits (0000
* <code>y</code> A two digit representation of a year (00 to 99)
* <code>a</code> Lowercase Ante meridiem and Post meridiem ('am' or
* <code>A</code> Uppercase Ante meridiem and Post meridiem ('AM' or
* <code>g</code> 12-hour format of an hour without leading zeros (1 to 12)
* <code>G</code> 24-hour format of an hour without leading zeros (0 to 23)
* <code>h</code> 12-hour format of an hour with leading zeros (01 to 12)
* <code>H</code> 24-hour format of an hour with leading zeros (00 to 23)
* <code>i</code> Minutes with leading zeros (00 to 59)
* <code>s</code> Seconds, with leading zeros (00 to 59)
* <code>u</code> Milliseconds, e.g. '54321'
* <code>e</code> Timezone identifier, e.g. Europe/London
* <code>I</code> Whether or not the date is in Summer time (1 if Summer
* <code>O</code> Difference to Greenwich time (GMT) in hours, e.g. '+0200'
* <code>P</code> Difference to Greenwich time (GMT) with colon between
* hours and minutes, e.g. '+02:00'
* <code>T</code> Timezone abbreviation, e.g. 'GMT', 'EST'
* <code>Z</code> Timezone offset in seconds. The offset for timezones west
* of UTC is always negative, and for those east of UTC is
* always positive. (-43200 to 50400)
* <code>c</code> ISO 8601 date, e.g. '2004-02-12T15:19:21+00:00'
* <code>r</code> RFC 2822 formatted date, e.g.
* 'Thu, 21 Dec 2000 16:01:07 +0200'
* <code>U</code> Seconds since the Unix Epoch
* (January 1 1970 00:00:00 GMT)
* @param string $ps_format the format string for returned date/time
* @return string date/time in given format
* @since Method available since Release 1.5.0
for ($i = 0; $i < strlen($ps_format); ++ $i) {
switch ($hs_char = substr($ps_format, $i, 1 )) {
$hs_format2str .= 'NPDy';
$hs_format2str .= 'NPDD';
$hs_format2str .= 'NPDay';
$hs_format2str .= 'NPMonth';
$hs_format2str .= 'NPMon';
$hs_format2str .= 'NPMM';
$hs_format2str .= '"' . ($this->isLeapYear() ? 1 : 0 ) . '"';
$hs_format2str .= 'IYYY';
$hs_format2str .= 'YYYY';
$hs_format2str .= 'NPHH12';
$hs_format2str .= 'NPHH24';
$hs_format2str .= 'HH12';
$hs_format2str .= 'HH24';
$hs_format2str .= 'SSFFF';
$hs_format2str .= 'STZHTZM';
$hs_format2str .= 'STZH:TZM';
$hs_format2str .= 'YYYY-MM-DD"T"HH24:MI:SSSTZH:TZM';
$hs_format2str .= 'Dy, DD Mon YYYY HH24:MI:SS STZHTZM';
$hs_char = substr($ps_format, ++ $i, 1 );
$hs_format2str .= '"' . ($hs_char == '\\' ? '\\\\' : $hs_char) . '"';
$hs_format2str .= '"\\""';
$hs_format2str .= '"' . $hs_char . '"';
$ret = $this->format2($hs_format2str);
if (PEAR ::isError ($ret) &&
return PEAR ::raiseError (" Invalid date format '$ps_format'" ,
* Returns the date/time in Unix time() format
* Returns a representation of this date in Unix time() format. This may
* only be valid for dates from 1970 to ~2038.
* @return int number of seconds since the unix epoch
* Returns the unique ID of the time zone, e.g. 'America/Chicago'
* @return string the time zone ID
* @since Method available since Release 1.5.0
return $this->tz->getID();
* sets time zone to the default time zone
* If PHP version >= 5.1.0, uses the php.ini configuration directive
* 'date.timezone' if set and valid, else the value returned by
* 'date("e")' if valid, else the default specified if the global
* constant '$GLOBALS["_DATE_TIMEZONE_DEFAULT"]', which if itself
* left unset, defaults to "UTC".
* N.B. this is a private method; to set the time zone to the
* default publicly you should call 'setTZByID()', that is, with no
* parameter (or a parameter of null).
* @since Method available since Release 1.5.0
function _setTZToDefault ()
* Sets the time zone of this Date
* Sets the time zone of this date with the given
* Date_TimeZone object. Does not alter the date/time,
* only assigns a new time zone. For conversion, use
* @param object $tz the Date_TimeZone object to use. If called with a
* parameter that is not a Date_TimeZone object, will
* fall through to setTZByID().
if (is_a($tz, 'Date_Timezone')) {
* Sets the time zone of this date with the given time zone ID
* The time zone IDs are drawn from the 'tz data-base' (see
* http://en.wikipedia.org/wiki/Zoneinfo), which is the de facto
* internet and IT standard. (There is no official standard, and
* the tz data-base is not intended to be a regulating body
* anyway.) Lists of valid IDs are maintained at:
* http://en.wikipedia.org/wiki/List_of_zoneinfo_timezones
* http://www.php.net/manual/en/timezones.php
* If no time-zone is specified and PHP version >= 5.1.0, the time
* zone is set automatically to the php.ini configuration directive
* 'date.timezone' if set and valid, else the value returned by
* 'date("e")' if valid, else the default specified if the global
* constant '$GLOBALS["_DATE_TIMEZONE_DEFAULT"]', which if itself
* left unset, defaults to "UTC".
* N.B. this function preserves the local date and time, that is,
* whether in local Summer time or local standard time. For example,
* if the time is set to 11.00 Summer time, and the time zone is then
* set to another time zone, using this function, in which the date
* falls in standard time, then the time will remain set to 11.00 UTC,
* and not 10.00. You can convert a date to another time zone by
* The ID can also be specified as a UTC offset in one of the following
* forms, i.e. an offset with no geographical or political base:
* UTC[+/-][h] - e.g. UTC-1 (the preferred form)
* UTC[+/-][hh] - e.g. UTC+03
* UTC[+/-][hh][mm] - e.g. UTC-0530
* UTC[+/-][hh]:[mm] - e.g. UTC+03:00
* N.B. 'UTC' seems to be technically preferred over 'GMT'. GMT-based
* IDs still exist in the tz data-base, but beware of POSIX-style
* offsets which are the opposite way round to what people normally
* @param string $ps_id a valid time zone id, e.g. 'Europe/London'
* @see Date::convertTZByID(), Date_TimeZone::isValidID(),
* Date_TimeZone::Date_TimeZone()
// Whether the date is in Summer time forms the default for
// the new time zone (if needed, which is very unlikely anyway).
// This is mainly to prevent unexpected (defaulting) behaviour
// if the user is in the repeated hour, and switches to a time
// zone that is also in the repeated hour (e.g. 'Europe/London'
if (PEAR ::isError ($hb_insummertime)) {
$hb_insummertime = false;
$this->_setTZToDefault ();
return PEAR ::raiseError (" Invalid time zone ID '$ps_id'" ,
* Returns the long name of the time zone
* Returns long form of time zone name, e.g. 'Greenwich Mean Time'.
* N.B. if the date falls in Summer time, the Summer time name will be
* returned instead, e.g. 'British Summer Time'.
* N.B. this is not a unique identifier for the time zone - for this
* purpose use the time zone ID.
* @return string the long name of the time zone
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
* Returns the short name of the time zone
* Returns abbreviated form of time zone name, e.g. 'GMT'. N.B. if the
* date falls in Summer time, the Summer time name will be returned
* N.B. this is not a unique identifier - for this purpose use the
* @return string the short name of the time zone
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
* Returns the DST-corrected offset from UTC for the given date
* Gets the offset to UTC for a given date/time, taking into
* account daylight savings time, if the time zone observes it and if
* N.B. that the offset is calculated historically
* and in the future according to the current Summer time rules,
* and so this function is proleptically correct, but not necessarily
* historically correct. (Although if you want to be correct about
* times in the distant past, this class is probably not for you
* because the whole notion of time zones does not apply, and
* historically there are so many time zone changes, Summer time
* rule changes, name changes, calendar changes, that calculating
* this sort of information is beyond the scope of this package
* @return int the corrected offset to UTC in milliseconds
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
* Tests if this date/time is in DST
* Returns true if daylight savings time is in effect for
* this date in this date's time zone.
* @param bool $pb_repeatedhourdefault value to return if repeated hour is
* specified (defaults to false)
* @return boolean true if DST is in effect for this date
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
// The return value is 'cached' whenever the date/time is set:
return $this->hour != $this->on_standardhour ||
$this->minute != $this->on_standardminute ||
$this->second != $this->on_standardsecond ||
$this->partsecond != $this->on_standardpartsecond ||
$this->day != $this->on_standardday ||
$this->month != $this->on_standardmonth ||
$this->year != $this->on_standardyear;
// (these last 3 conditions are theoretical
// possibilities but normally will never occur)
* Converts this date to a new time zone
* Previously this might not have worked correctly if your system did
* not allow putenv() or if localtime() did not work in your
* environment, but this implementation is no longer used.
* @param object $tz Date_TimeZone object to convert to
* @see Date::convertTZByID()
if ($this->getTZID() == $tz->getID ())
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$hn_rawoffset = $tz->getRawOffset () - $this->tz->getRawOffset();
$hn_standardpartsecond) =
$this->_addOffset ($hn_rawoffset,
$this->on_standardminute,
$this->on_standardsecond,
$this->on_standardpartsecond);
* Converts this date to UTC and sets this date's timezone to UTC
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
* Converts this date to a new time zone, given a valid time zone ID
* Previously this might not have worked correctly if your system did
* not allow putenv() or if localtime() does not work in your
* environment, but this implementation is no longer used.
* @param string $ps_id a valid time zone id, e.g. 'Europe/London'
* @see Date::setTZByID(), Date_TimeZone::isValidID(),
* Date_TimeZone::Date_TimeZone()
return PEAR ::raiseError (" Invalid time zone ID '$ps_id'" ,
* Converts the date/time to UTC by the offset specified
* This function is no longer called from within the Date class
* itself because a time zone can be set using a pure offset
* (e.g. UTC+1), i.e. not a geographical time zone. However
* it is retained for backwards compaibility.
* @param string $ps_offset offset of the form '[+/-][hh]:[mm]',
* '[+/-][hh][mm]', or 'Z'
function toUTCbyOffset ($ps_offset)
preg_match('/^[+\-](00:?00|0{1,2})$/', $ps_offset)) {
} else if (preg_match('/^[+\-]([0-9]{2,2}:?[0-5][0-9]|[0-9]{1,2})$/',
$hs_tzid = "UTC" . $ps_offset;
return PEAR ::raiseError (" Invalid offset '$ps_offset'" );
// If the time is invalid, it does not matter here:
// Now the time will be valid because it is a time zone that
// does not observe Summer time:
* Converts the date to the specified no of years from the given date
* To subtract years use a negative value for the '$pn_years'
* @param int $pn_years years to add
* @since Method available since Release 1.5.0
list ($hs_year, $hs_month, $hs_day) =
* Converts the date to the specified no of months from the given date
* To subtract months use a negative value for the '$pn_months'
* @param int $pn_months months to add
* @since Method available since Release 1.5.0
list ($hs_year, $hs_month, $hs_day) =
* Converts the date to the specified no of days from the given date
* To subtract days use a negative value for the '$pn_days' parameter
* @param int $pn_days days to add
* @since Method available since Release 1.5.0
list ($hs_year, $hs_month, $hs_day) =
* Converts the date to the specified no of hours from the given date
* To subtract hours use a negative value for the '$pn_hours' parameter
* @param int $pn_hours hours to add
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$this->on_standardminute,
$this->on_standardsecond,
$this->on_standardpartsecond);
* Converts the date to the specified no of minutes from the given date
* To subtract minutes use a negative value for the '$pn_minutes' parameter
* @param int $pn_minutes minutes to add
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$this->on_standardminute);
$this->on_standardsecond,
$this->on_standardpartsecond);
* Adds a given number of seconds to the date
* @param mixed $sec the no of seconds to add as integer or float
* @param bool $pb_countleap whether to count leap seconds (defaults to
* value of count-leap-second object property)
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$pb_countleap = $this->ob_countleapseconds;
$hn_standardpartsecond) =
$this->on_standardminute,
$this->on_standardsecond,
$this->on_standardpartsecond);
$hn_standardpartsecond == 0.0 ?
$hn_standardsecond = intval($hn_secondraw);
$hn_standardpartsecond = $hn_secondraw - $hn_standardsecond;
$hn_standardsecond = $hn_secondraw;
$hn_standardpartsecond = 0.0;
$hn_standardpartsecond) =
// Use local standard time:
$this->on_standardminute,
$this->on_standardpartsecond == 0.0 ?
$this->on_standardsecond :
$this->on_standardsecond +
$this->on_standardpartsecond,
$hn_standardsecond = intval($hn_secondraw);
$hn_standardpartsecond = $hn_secondraw - $hn_standardsecond;
$hn_standardsecond = $hn_secondraw;
$hn_standardpartsecond = 0.0;
* Subtracts a given number of seconds from the date
* @param mixed $sec the no of seconds to subtract as integer or
* @param bool $pb_countleap whether to count leap seconds (defaults to
* value of count-leap-second object property)
$pb_countleap = $this->ob_countleapseconds;
* Adds a time span to the date
* A time span is defined as a unsigned no of days, hours, minutes
* and seconds, where the no of minutes and seconds must be less than
* 60, and the no of hours must be less than 24.
* A span is added (and subtracted) according to the following logic:
* Hours, minutes and seconds are added such that if they fall over
* a leap second, the leap second is ignored, and not counted.
* For example, if a leap second occurred at 23.59.60, the
* following calculations:
* would all produce 00.00.00 the next day.
* A day is treated as equivalent to 24 hours, so if the clocks
* went backwards at 01.00, and one day was added to the time
* 00.30, the result would be 23.30 the same day.
* This is the implementation which is thought to yield the behaviour
* that the user is most likely to expect, or in another way of
* looking at it, it is the implementation that produces the least
* unexpected behaviour. It basically works in hours, that is, a day
* is treated as exactly equivalent to 24 hours, and minutes and
* seconds are treated as equivalent to 1/60th and 1/3600th of an
* hour. It should be obvious that working in days is impractical;
* working in seconds is problematic when it comes to adding days
* that fall over leap seconds, where it would appear to most users
* that the function adds only 23 hours, 59 minutes and 59 seconds.
* It is also problematic to work in any kind of mixture of days,
* hours, minutes, and seconds, because then the addition of a span
* would sometimes depend on which order you add the constituent
* parts, which undermines the concept of a span altogether.
* If you want alternative functionality, you must use a mixture of
* the following functions instead:
* @param object $span the time span to add
if (!is_a($span, 'Date_Span')) {
return PEAR ::raiseError ("Invalid argument - not 'Date_Span' object");
} else if ($this->ob_invalidtime) {
return $this->_getErrorInvalidTime ();
$hn_standardhour = $this->on_standardhour + $span->hour;
$hn_standardminute = $this->on_standardminute + $span->minute;
$hn_standardsecond = $this->on_standardsecond + $span->second;
if ($hn_standardsecond >= 60 ) {
$hn_standardsecond -= 60;
if ($hn_standardminute >= 60 ) {
$hn_standardminute -= 60;
if ($hn_standardhour >= 24 ) {
list ($hn_standardyear, $hn_standardmonth, $hn_standardday) =
$this->on_standardpartsecond);
* Subtracts a time span from the date
* N.B. it is impossible for this function to count leap seconds,
* because the result would be dependent on which order the consituent
* parts of the span are subtracted from the date. Therefore, leap
* seconds are ignored by this function. If you want to count leap
* seconds, use 'subtractSeconds()'.
* @param object $span the time span to subtract
if (!is_a($span, 'Date_Span')) {
return PEAR ::raiseError ("Invalid argument - not 'Date_Span' object");
} else if ($this->ob_invalidtime) {
return $this->_getErrorInvalidTime ();
$hn_standardhour = $this->on_standardhour - $span->hour;
$hn_standardminute = $this->on_standardminute - $span->minute;
$hn_standardsecond = $this->on_standardsecond - $span->second;
if ($hn_standardsecond < 0 ) {
$hn_standardsecond += 60;
if ($hn_standardminute < 0 ) {
$hn_standardminute += 60;
if ($hn_standardhour < 0 ) {
list ($hn_standardyear, $hn_standardmonth, $hn_standardday) =
$this->on_standardpartsecond);
* Subtract supplied date and return answer in days
* If the second parameter '$pb_ignoretime' is specified as false, the time
* parts of the two dates will be ignored, and the integral no of days
* between the day-month-year parts of the two dates will be returned. If
* either of the two dates have an invalid time, the integral no of days
* will also be returned, else the returned value will be the no of days as
* a float, with each hour being treated as 1/24th of a day and so on.
* 21/11/2007 13.00 minus 21/11/2007 01.00
* Note that if the passed date is in the past, a positive value will be
* returned, and if it is in the future, a negative value will be returned.
* @param object $po_date date to subtract
* @param bool $pb_ignoretime whether to ignore the time values of the two
* dates in subtraction (defaults to false)
* @return mixed days between two dates as int or float
* @since Method available since Release 1.5.0
function dateDiff($po_date, $pb_ignoretime = false )
if ($pb_ignoretime || $this->ob_invalidtime) {
$hn_secondscompare = $po_date->getStandardSecondsPastMidnight ();
if (PEAR ::isError ($hn_secondscompare)) {
return $hn_secondscompare;
// If time parts are equal, return int, else return float:
$this->on_standardyear) -
$po_date->getStandardMonth (),
$po_date->getStandardYear ()) +
($hn_seconds == $hn_secondscompare ? 0 :
($hn_seconds - $hn_secondscompare) / 86400 );
// {{{ inEquivalentTimeZones()
* Tests whether two dates are in equivalent time zones
* Equivalence in this context consists in the time zones of the two dates
* an equal offset from UTC in both standard and Summer time (if
* the time zones observe Summer time)
* the same Summer time start and end rules, that is, the two time zones
* must switch from standard time to Summer time, and vice versa, on the
* same day and at the same time
* An example of two equivalent time zones is 'Europe/London' and
* 'Europe/Lisbon', which in London is known as GMT/BST, and in Lisbon as
* @param object $po_date1 the first Date object to compare
* @param object $po_date2 the second Date object to compare
* @return bool true if the time zones are equivalent
* @since Method available since Release 1.5.0
return $po_date1->tz ->isEquivalent ($po_date2->getTZID ());
* Suitable for use in sorting functions.
* @param object $od1 the first Date object to compare
* @param object $od2 the second Date object to compare
* @return int 0 if the dates are equal, -1 if '$od1' is
* before '$od2', 1 if '$od1' is after '$od2'
// If the time zones are equivalent, do nothing:
// Only a time zone with a valid time can be converted:
if ($d2->isTimeValid ()) {
$d2->convertTZByID ($d1->getTZID ());
} else if ($d1->isTimeValid ()) {
$d1->convertTZByID ($d2->getTZID ());
// No comparison can be made without guessing the time:
return PEAR ::raiseError ("Both dates have invalid time",
$hn_hour1 = $d1->getStandardHour ();
if (PEAR ::isError ($hn_hour1))
$hn_hour2 = $d2->getStandardHour ();
if (PEAR ::isError ($hn_hour2))
if ($hn_hour1 < $hn_hour2) return -1;
if ($hn_hour1 > $hn_hour2) return 1;
if ($d1->getStandardMinute () < $d2->getStandardMinute ()) return -1;
if ($d1->getStandardMinute () > $d2->getStandardMinute ()) return 1;
if ($d1->getStandardSecond () < $d2->getStandardSecond ()) return -1;
if ($d1->getStandardSecond () > $d2->getStandardSecond ()) return 1;
if ($d1->getStandardPartSecond () < $d2->getStandardPartSecond ()) return -1;
if ($d1->getStandardPartSecond () > $d2->getStandardPartSecond ()) return 1;
* Test if this date/time is before a certain date/time
* @param object $when the Date object to test against
* @return boolean true if this date is before $when
if (PEAR ::isError ($hn_compare))
* Test if this date/time is after a certain date/time
* @param object $when the Date object to test against
* @return boolean true if this date is after $when
if (PEAR ::isError ($hn_compare))
* Test if this date/time is exactly equal to a certain date/time
* @param object $when the Date object to test against
* @return boolean true if this date is exactly equal to $when
if (PEAR ::isError ($hn_compare))
* Determine if this date is in the future
* @return boolean true if this date is in the future
return $this->after($now);
* Determine if this date is in the past
* @return boolean true if this date is in the past
* Determine if the year in this date is a leap year
* @return boolean true if this year is a leap year
* Returns the no of days (1-366) since 31st December of the previous year
* N.B. this function does not return (and never has returned) the 'Julian
* Date', as described, for example, at:
* http://en.wikipedia.org/wiki/Julian_day
* If you want the day of the year (0-366), use 'getDayOfYear()' instead.
* If you want the true Julian Day, call one of the following:
* <code>format("%E")</code>
* <code>format2("J")</code>
* There currently is no function that calls the Julian Date (as opposed
* to the 'Julian Day'), although the Julian Day is an approximation.
* @return int the Julian date
* @see Date::getDayOfYear()
* @deprecated Method deprecated in Release 1.5.0
* Returns the no of days (1-366) since 31st December of the previous year
* @return int an integer between 1 and 366
* @since Method available since Release 1.5.0
* Gets the day of the week for this date (0 = Sunday)
* @return int the day of the week (0 = Sunday)
* Gets the week of the year for this date
* @return int the week of the year
// {{{ getQuarterOfYear()
* Gets the quarter of the year for this date
* @return int the quarter of the year (1-4)
* Gets number of days in the month for this date
* @return int number of days in this month
* Gets the number of weeks in the month for this date
* @return int number of weeks in this month
* Gets the full name or abbreviated name of this weekday
* @param bool $abbr abbreviate the name
* @param int $length length of abbreviation
* @return string name of this day
* Gets the full name or abbreviated name of this month
* @param boolean $abbr abbreviate the name
* @return string name of this month
* Get a Date object for the day after this one
* The time of the returned Date object is the same as this time.
* @return object Date object representing the next day
* Get a Date object for the day before this one
* The time of the returned Date object is the same as this time.
* @return object Date object representing the previous day
* Get a Date object for the weekday after this one
* The time of the returned Date object is the same as this time.
* @return object Date object representing the next week-day
list ($hs_year, $hs_month, $hs_day) =
$ret->setDayMonthYear ($hs_day, $hs_month, $hs_year);
* Get a Date object for the weekday before this one
* The time of the returned Date object is the same as this time.
* @return object Date object representing the previous week-day
list ($hs_year, $hs_month, $hs_day) =
$ret->setDayMonthYear ($hs_day, $hs_month, $hs_year);
* Returns the year field of the date object
* Returns the month field of the date object
* Returns the day field of the date object
// {{{ _getErrorInvalidTime()
* Returns invalid time PEAR Error
* @since Method available since Release 1.5.0
function _getErrorInvalidTime ()
return PEAR ::raiseError ("Invalid time '" .
"' specified for date '" .
"' and in this timezone",
// {{{ _secondsInDayIsValid()
* If leap seconds are observed, checks if the seconds in the day is valid
* Note that only the local standard time is accessed.
* @since Method available since Release 1.5.0
function _secondsInDayIsValid ()
if ($this->ob_countleapseconds) {
$this->on_standardminute,
$this->on_standardsecond,
$this->on_standardpartsecond);
* Whether the stored time is valid as a local time
* An invalid time is one that lies in the 'skipped hour' at the point
* that the clocks go forward. Note that the stored date (i.e.
* the day/month/year, is always valid).
* The object is able to store an invalid time because a user might
* unwittingly and correctly store a valid time, and then add one day so
* as to put the object in the 'skipped' hour (when the clocks go forward).
* This could be corrected by a conversion to Summer time (by adding one
* hour); however, if the user then added another day, and had no need for
* or interest in the time anyway, the behaviour may be rather unexpected.
* And anyway in this situation, the time originally specified would now,
* two days on, be valid again.
* So this class allows an invalid time like this so long as the user does
* not in any way make use of or request the time while it is in this
* semi-invalid state, in order to allow for for the fact that he might be
* only interested in the date, and not the time, and in order not to behave
* in an unexpected way, especially without throwing an exception to tell
* @since Method available since Release 1.5.0
return !$this->ob_invalidtime;
* Returns the hour field of the date object
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
* Returns the minute field of the date object
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
* Returns the second field of the date object
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
// {{{ getSecondsPastMidnight()
* Returns the no of seconds since midnight (0-86400) as float
* @return float float which is at least 0 and less than 86400
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
* Returns the part-second field of the date object
* @return float the part-second
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
return $this->partsecond;
* Returns the year field of the local standard time
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
return $this->on_standardyear;
// {{{ getStandardMonth()
* Returns the month field of the local standard time
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
return $this->on_standardmonth;
* Returns the day field of the local standard time
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
return $this->on_standardday;
* Returns the hour field of the local standard time
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
return $this->on_standardhour;
// {{{ getStandardMinute()
* Returns the minute field of the local standard time
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
return $this->on_standardminute;
// {{{ getStandardSecond()
* Returns the second field of the local standard time
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
return $this->on_standardsecond;
// {{{ getStandardSecondsPastMidnight()
* Returns the no of seconds since midnight (0-86400) of the
* local standard time as float
* @return float float which is at least 0 and less than 86400
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
$this->on_standardminute,
$this->on_standardsecond) +
$this->on_standardpartsecond;
// {{{ getStandardPartSecond()
* Returns the part-second field of the local standard time
* @return float the part-second
* @since Method available since Release 1.5.0
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime ();
return $this->on_standardpartsecond;
* Add a time zone offset to the passed date/time
* @param int $pn_offset the offset to add in milliseconds
* @param int $pn_day the day
* @param int $pn_month the month
* @param int $pn_year the year
* @param int $pn_hour the hour
* @param int $pn_minute the minute
* @param int $pn_second the second
* @param float $pn_partsecond the part-second
* @return array array of year, month, day, hour, minute, second,
* @since Method available since Release 1.5.0
function _addOffset ($pn_offset,
return array ((int) $pn_year,
if ($pn_offset % 3600000 == 0 ) {
$hn_minute = (int) $pn_minute;
$hn_second = (int) $pn_second;
$hn_partsecond = (float) $pn_partsecond;
} else if ($pn_offset % 60000 == 0 ) {
$hn_second = (int) $pn_second;
$hn_partsecond = (float) $pn_partsecond;
$pn_second + $pn_partsecond,
false ); // N.B. do not count
$hn_second = intval($hn_secondraw);
$hn_partsecond = $hn_secondraw - $hn_second;
$hn_second = $hn_secondraw;
* Sets local time (Summer-time-adjusted) and then calculates local
* @param int $pn_day the day
* @param int $pn_month the month
* @param int $pn_year the year
* @param int $pn_hour the hour
* @param int $pn_minute the minute
* @param int $pn_second the second
* @param float $pn_partsecond the part-second
* @param bool $pb_repeatedhourdefault whether to assume Summer time if a
* repeated hour is specified (defaults
* @param bool $pb_correctinvalidtime whether to correct, by adding the
* local Summer time offset, the
* specified time if it falls in the
* skipped hour (defaults to
* DATE_CORRECTINVALIDTIME_DEFAULT)
* @see Date::setStandardTime()
* @since Method available since Release 1.5.0
$pb_repeatedhourdefault = false ,
$pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT )
$pn_minute, $pn_second) + $pn_partsecond),
$pb_repeatedhourdefault);
if (PEAR ::isError ($hb_insummertime)) {
} else if ($pb_correctinvalidtime) {
// Store passed time as local standard time:
$this->on_standardday = $pn_day;
$this->on_standardmonth = $pn_month;
$this->on_standardyear = $pn_year;
$this->on_standardhour = $pn_hour;
$this->on_standardminute = $pn_minute;
$this->on_standardsecond = $pn_second;
$this->on_standardpartsecond = $pn_partsecond;
// Add Summer time offset to passed time:
$this->ob_invalidtime = !$this->_secondsInDayIsValid ();
// Hedge bets - if the user adds/subtracts a day, then the time
// will be uncorrupted, and if the user does
// addition/subtraction with the time, or requests the time,
// then return an error at that point:
$this->month = $pn_month;
$this->minute = $pn_minute;
$this->second = $pn_second;
$this->partsecond = $pn_partsecond;
$this->ob_invalidtime = true;
// Passed time is valid as local time:
$this->month = $pn_month;
$this->minute = $pn_minute;
$this->second = $pn_second;
$this->partsecond = $pn_partsecond;
$this->ob_invalidtime = !$this->_secondsInDayIsValid ();
// Calculate local standard time:
list ($this->on_standardyear,
$this->on_standardminute,
$this->on_standardsecond,
$this->on_standardpartsecond) =
// Time is already local standard time:
$this->on_standardday = $pn_day;
$this->on_standardmonth = $pn_month;
$this->on_standardyear = $pn_year;
$this->on_standardhour = $pn_hour;
$this->on_standardminute = $pn_minute;
$this->on_standardsecond = $pn_second;
$this->on_standardpartsecond = $pn_partsecond;
* Sets local standard time and then calculates local time (i.e.
* @param int $pn_day the day
* @param int $pn_month the month
* @param int $pn_year the year
* @param int $pn_hour the hour
* @param int $pn_minute the minute
* @param int $pn_second the second
* @param float $pn_partsecond the part-second
* @see Date::setLocalTime()
* @since Method available since Release 1.5.0
$this->on_standardday = $pn_day;
$this->on_standardmonth = $pn_month;
$this->on_standardyear = $pn_year;
$this->on_standardhour = $pn_hour;
$this->on_standardminute = $pn_minute;
$this->on_standardsecond = $pn_second;
$this->on_standardpartsecond = $pn_partsecond;
$this->ob_invalidtime = !$this->_secondsInDayIsValid ();
$pn_second) + $pn_partsecond))) {
// Time is already local time:
$this->month = $pn_month;
$this->minute = $pn_minute;
$this->second = $pn_second;
$this->partsecond = $pn_partsecond;
* Sets the year field of the date object
* If specified year forms an invalid date, then PEAR error will be
* returned, unless the validation is over-ridden using the second
* @param bool $pb_validate whether to check that the new date is valid
* @see Date::setDayMonthYear(), Date::setDateTime()
function setYear($y, $pb_validate = DATE_VALIDATE_DATE_BY_DEFAULT )
return PEAR ::raiseError ("'" .
"' is invalid calendar date",
* Sets the month field of the date object
* If specified year forms an invalid date, then PEAR error will be
* returned, unless the validation is over-ridden using the second
* @param int $m the month
* @param bool $pb_validate whether to check that the new date is valid
* @see Date::setDayMonthYear(), Date::setDateTime()
function setMonth($m, $pb_validate = DATE_VALIDATE_DATE_BY_DEFAULT )
return PEAR ::raiseError ("'" .
"' is invalid calendar date",
* Sets the day field of the date object
* If specified year forms an invalid date, then PEAR error will be
* returned, unless the validation is over-ridden using the second
* @param bool $pb_validate whether to check that the new date is valid
* @see Date::setDayMonthYear(), Date::setDateTime()
function setDay($d, $pb_validate = DATE_VALIDATE_DATE_BY_DEFAULT )
return PEAR ::raiseError ("'" .
"' is invalid calendar date",
* Sets the day, month and year fields of the date object
* If specified year forms an invalid date, then PEAR error will be
* returned. Note that setting each of these fields separately
* may unintentionally return a PEAR error if a transitory date is
* invalid between setting these fields.
* @param int $m the month
* @see Date::setDateTime()
* @since Method available since Release 1.5.0
return PEAR ::raiseError ("'" .
"' is invalid calendar date",
* Sets the hour field of the date object
* Expects an hour in 24-hour format.
* @param bool $pb_repeatedhourdefault whether to assume Summer time if a
* repeated hour is specified (defaults
* @see Date::setHourMinuteSecond(), Date::setDateTime()
function setHour($h, $pb_repeatedhourdefault = false )
return PEAR ::raiseError (" Invalid hour value '$h'" );
$this->partsecond == 0.0 ?
$this->second + $this->partsecond,
$pb_repeatedhourdefault);
* Sets the minute field of the date object
* @param int $m the minute
* @param bool $pb_repeatedhourdefault whether to assume Summer time if a
* repeated hour is specified (defaults
* @see Date::setHourMinuteSecond(), Date::setDateTime()
function setMinute($m, $pb_repeatedhourdefault = false )
return PEAR ::raiseError (" Invalid minute value '$m'" );
$this->partsecond == 0.0 ?
$this->second + $this->partsecond,
$pb_repeatedhourdefault);
* Sets the second field of the date object
* @param mixed $s the second as integer or float
* @param bool $pb_repeatedhourdefault whether to assume Summer time if a
* repeated hour is specified
* @see Date::setHourMinuteSecond(), Date::setDateTime()
function setSecond($s, $pb_repeatedhourdefault = false )
if ($s > 60 || // Leap seconds possible
return PEAR ::raiseError (" Invalid second value '$s'" );
$pb_repeatedhourdefault);
* Sets the part-second field of the date object
* @param float $pn_ps the part-second
* @param bool $pb_repeatedhourdefault whether to assume Summer time if a
* repeated hour is specified (defaults
* @see Date::setHourMinuteSecond(), Date::setDateTime()
* @since Method available since Release 1.5.0
if ($pn_ps >= 1 || $pn_ps < 0 ) {
return PEAR ::raiseError (" Invalid part-second value '$pn_ps'" );
$pb_repeatedhourdefault);
// {{{ setHourMinuteSecond()
* Sets the hour, minute, second and part-second fields of the date object
* N.B. if the repeated hour, due to the clocks going back, is specified,
* the default is to assume local standard time.
* @param int $m the minute
* @param mixed $s the second as integer or float
* @param bool $pb_repeatedhourdefault whether to assume Summer time if a
* repeated hour is specified
* @see Date::setDateTime()
* @since Method available since Release 1.5.0
// Split second into integer and part-second:
$hn_partsecond = $s - $hn_second;
$pb_repeatedhourdefault);
* Sets all the fields of the date object (day, month, year, hour, minute
* If specified year forms an invalid date, then PEAR error will be
* returned. Note that setting each of these fields separately
* may unintentionally return a PEAR error if a transitory date is
* invalid between setting these fields.
* N.B. if the repeated hour, due to the clocks going back, is specified,
* the default is to assume local standard time.
* @param int $pn_day the day
* @param int $pn_month the month
* @param int $pn_year the year
* @param int $pn_hour the hour
* @param int $pn_minute the minute
* @param mixed $pm_second the second as integer or float
* @param bool $pb_repeatedhourdefault whether to assume Summer time if a
* repeated hour is specified
* @see Date::setDayMonthYear(), Date::setHourMinuteSecond()
* @since Method available since Release 1.5.0
$pb_repeatedhourdefault = false )
return PEAR ::raiseError ("'" .
"' is invalid calendar date",
// Split second into integer and part-second:
$hn_second = intval($pm_second);
$hn_partsecond = $pm_second - $hn_second;
$hn_second = (int) $pm_second;
$pb_repeatedhourdefault);
* c-hanging-comment-ender-p: nil
Documentation generated on Sun, 23 Mar 2008 20:00:29 -0400 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.
|