Permalink
Switch branches/tags
Find file
Fetching contributors…
Cannot retrieve contributors at this time
6171 lines (5607 sloc) 228 KB
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
// {{{ Header
/**
* Generic date handling class for PEAR
*
* Handles time zones and changes from local standard to local Summer
* time (daylight-saving time) through the {@link Date_TimeZone} class.
* Supports several operations from {@link Date_Calc} on Date objects.
*
* PHP versions 4 and 5
*
* LICENSE:
*
* Copyright (c) 1997-2008 Baba Buehler, Pierre-Alain Joye, Firman
* Wandayandi, C.A. Woodcock
* All rights reserved.
*
* 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
* @package Date
* @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
* BSD License
* @version CVS: $Id$
* @link http://pear.php.net/package/Date
*/
// }}}
// {{{ Error constants
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);
// }}}
// {{{ Includes
require_once 'PEAR.php';
/**
* Load Date_TimeZone
*/
require_once 'Date/TimeZone.php';
/**
* Load Date_Calc
*/
require_once 'Date/Calc.php';
/**
* Load Date_Span
*/
require_once 'Date/Span.php';
// }}}
// {{{ General constants
/**
* Whether to capture the micro-time (in microseconds) by default
* in calls to {@link Date::setNow()}. Note that this makes a call to
* {@link http://www.php.net/gettimeofday 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->formatLikeSQL('HH.MI.SS');</code>
*
* or:
*
* <code>$date_object->addSeconds(30);</code>
*
* an error will be returned if the time is invalid. However,
* if the user calls:
*
* <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
* access the time.
*
* 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 any of these
* conditions are satisfied:
*
* <ol>
* <li>the user uses a time zone that does not observe Summer time, e.g. UTC</li>
* <li>the user never accesses the time, that is, he never makes a call to
* {@link Date::getHour()} or {@link Date::formatLikeStrftime()} using
* format code '<b>%H</b>', for example, even if he sets the time to
* something invalid</li>
* <li>the user sets DATE_CORRECTINVALIDTIME_DEFAULT to true</li>
* </ol>
*
* @since Constant available since Release 1.5.0
* @see Date::isValidTime(), DATE_VALIDATE_DATE_BY_DEFAULT
*/
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
* functions:
*
* - {@link Date::setYear()}
* - {@link Date::setMonth()}
* - {@link Date::setDay()}
*
* 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
* left unmodified.
*
* This constant is set to 'false' by default for backwards-compatibility
* reasons, however, you are recommended to set it to 'true'.
*
* Note that {@link Date::setHour()}, {@link Date::setMinute()},
* {@link Date::setSecond()} and {@link Date::setPartSecond()}
* allow an invalid date/time to be set regardless of the value of this
* constant.
*
* @see Date::isValidDate(), Date::isValidTime(), Date::isNull(),
* DATE_CORRECTINVALIDTIME_DEFAULT
* @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
* following functions:
*
* - {@link Date::addSeconds()}
* - {@link Date::subtractSeconds()}
* - {@link Date_Calc::addSeconds()}
* - {@link Date::round()}
* - {@link Date::roundSeconds()}
*
* 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 {@link Date::addSpan()} and
* {@link 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);
/**
* Method to call when user invokes {@link Date::format()}
*
* @since Constant available since Release 1.5.1
*/
define('DATE_FORMAT_METHOD', 'formatLikeStrftime');
// }}}
// {{{ Output format constants (used in {@link Date::getDate()})
/**
* "YYYY-MM-DD HH:MM:SS"
*/
define('DATE_FORMAT_ISO', 1);
/**
* "YYYYMMDDTHHMMSS(Z|(+/-)HHMM)?"
*/
define('DATE_FORMAT_ISO_BASIC', 2);
/**
* "YYYY-MM-DDTHH:MM:SS(Z|(+/-)HH:MM)?"
*/
define('DATE_FORMAT_ISO_EXTENDED', 3);
/**
* "YYYY-MM-DDTHH:MM:SS(.S*)?(Z|(+/-)HH:MM)?"
*/
define('DATE_FORMAT_ISO_EXTENDED_MICROTIME', 6);
/**
* "YYYYMMDDHHMMSS"
*/
define('DATE_FORMAT_TIMESTAMP', 4);
/**
* long int, seconds since the unix epoch
*/
define('DATE_FORMAT_UNIXTIME', 5);
// }}}
// {{{ Class: Date
/**
* 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
* @package Date
* @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
* BSD License
* @version Release: 1.5.0a1
* @link http://pear.php.net/package/Date
*/
class Date
{
// {{{ Properties
/**
* The year
*
* @var int
* @access private
* @since Property available since Release 1.0
*/
var $year;
/**
* The month
*
* @var int
* @access private
* @since Property available since Release 1.0
*/
var $month;
/**
* The day
*
* @var int
* @access private
* @since Property available since Release 1.0
*/
var $day;
/**
* The hour
*
* @var int
* @access private
* @since Property available since Release 1.0
*/
var $hour;
/**
* The minute
*
* @var int
* @access private
* @since Property available since Release 1.0
*/
var $minute;
/**
* The second
*
* @var int
* @access private
* @since Property available since Release 1.0
*/
var $second;
/**
* The parts of a second
*
* @var float
* @access private
* @since Property available since Release 1.4.3
*/
var $partsecond;
/**
* The year in local standard time
*
* @var int
* @access private
* @since Property available since Release 1.5.0
*/
var $on_standardyear;
/**
* The month in local standard time
*
* @var int
* @access private
* @since Property available since Release 1.5.0
*/
var $on_standardmonth;
/**
* The day in local standard time
*
* @var int
* @access private
* @since Property available since Release 1.5.0
*/
var $on_standardday;
/**
* The hour in local standard time
*
* @var int
* @access private
* @since Property available since Release 1.5.0
*/
var $on_standardhour;
/**
* The minute in local standard time
*
* @var int
* @access private
* @since Property available since Release 1.5.0
*/
var $on_standardminute;
/**
* The second in local standard time
*
* @var int
* @access private
* @since Property available since Release 1.5.0
*/
var $on_standardsecond;
/**
* The part-second in local standard time
*
* @var float
* @access private
* @since Property available since Release 1.5.0
*/
var $on_standardpartsecond;
/**
* Whether the object should accept and count leap seconds
*
* @var bool
* @access private
* @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
* the clocks go forward)
*
* @var bool
* @access private
* @see Date::isValidTime()
* @since Property available since Release 1.5.0
*/
var $ob_invalidtime = null;
/**
* Date_TimeZone object for this date
*
* @var object Date_TimeZone object
* @access private
* @since Property available since Release 1.0
*/
var $tz;
/**
* Defines the default weekday abbreviation length
*
* Formerly used by {@link Date::formatLikeStrftime()}, but now
* redundant - the abbreviation for the current locale of the machine
* is used.
*
* @var int
* @access private
* @since Property available since Release 1.4.4
*/
var $getWeekdayAbbrnameLength = 3;
// }}}
// {{{ Constructor
/**
* Constructor
*
* 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
* is used.
*
* If a date is passed and an exception is returned by {@link Date::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
* Date::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;
* or, a Unix time stamp
* @param bool $pb_countleapseconds whether to count leap seconds
* (defaults to
* {@link DATE_COUNT_LEAP_SECONDS})
*
* @return void
* @access public
* @see Date::setDate()
*/
function Date($date = null,
$pb_countleapseconds = DATE_COUNT_LEAP_SECONDS)
{
$this->ob_countleapseconds = $pb_countleapseconds;
if (is_a($date, 'Date')) {
$this->copy($date);
} else {
if (!is_null($date)) {
// 'setDate()' expects a time zone to be already set:
//
$this->_setTZToDefault();
$this->setDate($date);
} else {
$this->setNow();
}
}
}
// }}}
// {{{ copy()
/**
* Copy values from another Date object
*
* Makes this Date a copy of another Date object. This is a
* PHP4-compatible implementation of {@link Date::__clone()} in PHP5.
*
* @param object $date Date object to copy
*
* @return void
* @access public
*/
function copy($date)
{
$this->year = $date->year;
$this->month = $date->month;
$this->day = $date->day;
$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->tz = new Date_TimeZone($date->getTZID());
$this->getWeekdayAbbrnameLength = $date->getWeekdayAbbrnameLength;
}
// }}}
// {{{ __clone()
/**
* Copy values from another Date object
*
* Makes this Date a copy of another Date object. For PHP5
* only.
*
* @return void
* @access public
* @see Date::copy()
*/
function __clone()
{
// This line of code would be preferable, but will only
// compile in PHP5:
//
// $this->tz = clone $this->tz;
$this->tz = new Date_TimeZone($this->getTZID());
}
// }}}
// {{{ isNull()
/**
* Returns whether the object is null (i.e. no date has been set)
*
* If the object is set to an invalid date, then this function will
* still return 'false'. To check whether the date is valid use
* either {@link Date::isValidDate()} (to check the day/month/year
* part of the object only) or {@link Date::isValidTime()} (to check
* the time, in addition to the day/month/year part).
*
* @return bool
* @access public
* @see Date::setDate(), Date::isValidDate(), Date::isValidTime()
* @since Method available since Release 1.5.0
*/
function isNull()
{
return is_null($this->year);
}
// }}}
// {{{ isValidDate()
/**
* Returns whether the date (i.e. day/month/year) is valid
*
* It is not possible to set the object to an invalid date using
* {@link Date::setDate()}, but it is possible to do so using the
* following functions:
*
* - {@link Date::setYear()}
* - {@link Date::setMonth()}
* - {@link Date::setDay()}
*
* However you can prevent this possibility (by default) by setting
* {@link DATE_VALIDATE_DATE_BY_DEFAULT} to 'true', in which case
* these three functions will return an error if they specify an
* invalid date, and the object will be unmodified.
*
* Note that this function only checks the day/month/year part of
* the object. Even if this is valid, it is still possible for the
* time to be invalid (see {@link DATE_CORRECTINVALIDTIME_DEFAULT}).
* To check the time as well, use {@link Date::isValidTime()}.
*
* @return bool
* @access public
* @see Date::setDate(), Date::isNull(), Date::isValidTime()
* @since Method available since Release 1.5.0
*/
function isValidDate()
{
return
!Date::isNull() &&
Date_Calc::isValidDate($this->year, $this->month, $this->day);
}
// }}}
// {{{ setDate()
/**
* Sets the date/time of the object based on the input date and format
*
* Accepts a string in three possible formats, and in this order of
* precedence:
*
* - ISO 8601 date (see {@link http://en.wikipedia.org/wiki/ISO_8601})
* - Time-Stamp (i.e. 'YYYYMMDDHHMMSS')
* - Unix time-stamp (see {@link http://en.wikipedia.org/wiki/Unix_time})
*
* Note that if you want to pass a Unix time-stamp then you need to set
* the $format parameter to {@link DATE_FORMAT_UNIXTIME}, or else use the
* method {@link Date::setFromTime()}.
*
* The input string should be a date/time representation in one of the
* following general formats:
*
* - <b><date>T<time><time-zone></b>
* - <b><date> <time><time-zone></b> (non-ISO-standard)
* - <b><date><time><time-zone></b> (non-ISO-standard)
* - <b><date>T<time></b> i.e. without optional <time-zone> representation
* - <b><date> <time></b>
* - <b><date><time></b>
* - <b><date></b> i.e. without optional <time> representation
*
* that is, the representation must be comprised of a <b><date></b> part,
* with an optional <b><time></b> part, which itself may include an optional
* <time-zone> part, each of which may consist of any one of the permitted
* formats detailed below. The <b><date></b> and <b><time</b> representations
* should be divided with the time designator <b>T</b> according to the ISO 8601
* standard, although this method also permits representations divided by a
* space, or by no delimiter at all.
*
* The <b><date></b> representation should be in one of the following formats:
*
* - <b>Calendar date</b>: <b>YYYY-MM-DD</b> (extended format) or
* <b>YYYYMMDD</b> (basic format), where [YYYY]
* indicates the four-digit year (0000-9999), [MM]
* indicates the month (01-12) and [DD] indicates the
* day of the month [01-31]
* - <b>ISO week date</b>: <b>YYYY-Www-D</b> (extended format) or
* <b>YYYYWwwD</b> (basic format), where [YYYY]
* indicates the ISO year (slightly different from the
* calendar year (see below)), [Www] indicates the ISO
* week no prefixed by the letter 'W' (W01-W53) and
* [D] indicates the ISO week-day (1-7), beginning on
* Monday and ending on Sunday. (Also see
* {@link http://en.wikipedia.org/wiki/ISO_week_date}.)
* - <b>Ordinal date</b>: <b>YYYY-DDD</b> (extended format) or
* <b>YYYYDDD</b> (basic format), where [YYYY]
* indicates the four-digit year (0000-9999) and [DDD]
* indicates the day of the year (001-366)
*
* The <b><time></b> representation should be in one of the following formats:
*
* - <b>hh:mm:ss</b> (extended format) or <b>hhmmss</b> (basic format)
* - <b>hh:mm</b> (extended format) or <b>hhmm</b> (basic format)
* - <b>hh</b> (extended format) or <b>hh</b> (basic format)
*
* where [hh] represents the hour (00-24), [mm] represents the minute (00-59)
* and [ss] represents the second (00-60)
*
* Format parameter should be one of the specified DATE_FORMAT_* constants:
*
* - <b>{@link DATE_FORMAT_ISO}</b> - 'YYYY-MM-DD HH:MI:SS'
* - <b>{@link DATE_FORMAT_ISO_BASIC}</b> - 'YYYYMMDDTHHMMSS(Z|(+/-)HHMM)?'
* - <b>{@link DATE_FORMAT_ISO_EXTENDED}</b> - 'YYYY-MM-DDTHH:MM:SS(Z|(+/-)HH:MM)?'
* - <b>{@link DATE_FORMAT_ISO_EXTENDED_MICROTIME}</b> - 'YYYY-MM-DDTHH:MM:SS(.S*)?(Z|(+/-)HH:MM)?'
* - <b>{@link DATE_FORMAT_TIMESTAMP}</b> - 'YYYYMMDDHHMMSS'
* - <b>{@link DATE_FORMAT_UNIXTIME}</b> - long integer of the no of seconds since
* the Unix Epoch
* (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 (for
* which use
* {@link DATE_FORMAT_UNIXTIME}).
* (Defaults to
* {@link DATE_FORMAT_ISO}.)
* @param bool $pb_repeatedhourdefault value to return if repeated
* hour is specified (defaults
* to false)
*
* @return void
* @access public
* @see Date::isNull(), Date::isValidDate(), Date::isValidTime(),
* Date::setFromTime()
*/
function setDate($date,
$format = DATE_FORMAT_ISO,
$pb_repeatedhourdefault = false)
{
if ($format == DATE_FORMAT_UNIXTIME) {
if (is_numeric($date)) {
// Assume Unix time-stamp:
//
$this->setFromTime((int) $date);
} else {
return PEAR::raiseError("'$date' not valid Unix time-stamp");
}
} else if (preg_match('/^([0-9]{4,4})-?(' .
'(0[1-9]|1[0-2])-?(0[1-9]|[12][0-9]|3[01])|' . // [mm]-[dd]
'W(0[1-9]|[1-4][0-9]|5[0-3])-?([1-7])|' . // ISO week date
'(0(0[1-9]|[1-9][0-9])|[12][0-9]{2,2}|3([0-5][0-9]|6[1-6]))' . // [ddd]
')([T\s]?' .
'([01][0-9]|2[0-3])(:?' . // [hh]
'([0-5][0-9])(:?' . // [mm]
'([0-5][0-9]|60)([,.][0-9]+)?)?)?' . // [ss]
'(Z|[+\-][0-9]{2,2}(:?[0-5][0-9])?)?)?$/i', // offset
$date, $regs)
) {
if (substr($regs[2], 0, 1) == "W") {
// ISO week date (YYYY-Www-D)
//
$hs_date = Date_Calc::isoWeekToDate($regs[6],
$regs[5],
$regs[1],
"%Y %m %d");
if (PEAR::isError($hs_date)) {
return $hs_date;
}
list($hs_year, $hs_month, $hs_day) = explode(" ", $hs_date);
} else if (strlen($regs[2]) == 3) {
// ISO ordinal date (YYYY-DDD)
//
$hn_jd = Date_Calc::firstDayOfYear($regs[1]) + $regs[2] - 1;
list($hs_year, $hs_month, $hs_day) =
explode(" ", Date_Calc::daysToDate($hn_jd, "%Y %m %d"));
} else {
// ISO calendar date (YYYY-MM-DD)
//
// 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.
//
$hs_year = $regs[1];
$hs_month = $regs[3];
$hs_day = $regs[4];
if (!Date_Calc::isValidDate($hs_day, $hs_month, $hs_year)) {
return PEAR::raiseError("'" .
Date_Calc::dateFormat($hs_year,
$hs_month,
$hs_day,
"%Y-%m-%d") .
"' is invalid calendar date",
DATE_ERROR_INVALIDDATE);
}
}
if (isset($regs[17])) {
if ($regs[17] == "Z") {
$this->tz = new Date_TimeZone("UTC");
} else {
$this->tz = new Date_TimeZone("UTC" . $regs[17]);
}
}
$this->setLocalTime($hs_day,
$hs_month,
$hs_year,
isset($regs[11]) && $regs[11] != "" ?
$regs[11] : 0,
isset($regs[13]) && $regs[13] != "" ?
$regs[13] : 0,
isset($regs[15]) && $regs[15] != "" ?
$regs[15] : 0,
isset($regs[16]) && $regs[16] != "" ?
$regs[16] : 0.0,
$pb_repeatedhourdefault);
} else {
return PEAR::raiseError("Date '$date' not in ISO 8601 format",
DATE_ERROR_INVALIDDATEFORMAT);
}
}
// }}}
// {{{ setNow()
/**
* Sets to local current time and time zone
*
* @param bool $pb_setmicrotime whether to set micro-time (defaults to the
* value of the constant
* {@link DATE_CAPTURE_MICROTIME_BY_DEFAULT})
*
* @return void
* @access public
* @since Method available since Release 1.5.0
*/
function setNow($pb_setmicrotime = DATE_CAPTURE_MICROTIME_BY_DEFAULT)
{
$this->_setTZToDefault();
if ($pb_setmicrotime) {
$ha_unixtime = gettimeofday();
} else {
$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"]) :
""));
}
// }}}
// {{{ round()
/**
* Rounds the date according to the specified precision (defaults
* to nearest day)
*
* The precision parameter must be one of the following constants:
*
* - <b>{@link DATE_PRECISION_YEAR}</b>
* - <b>{@link DATE_PRECISION_MONTH}</b>
* - <b>{@link DATE_PRECISION_DAY}</b> (default)
* - <b>{@link DATE_PRECISION_HOUR}</b>
* - <b>{@link DATE_PRECISION_10MINUTES}</b>
* - <b>{@link DATE_PRECISION_MINUTE}</b>
* - <b>{@link DATE_PRECISION_10SECONDS}</b>
* - <b>{@link DATE_PRECISION_SECOND}</b>
*
* 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.
* For example:
*
* - <b>(DATE_PRECISION_YEAR - 1)</b> - rounds the date to the nearest 10 years
* - <b>(DATE_PRECISION_YEAR - 3)</b> - rounds the date to the nearest 1000
* years
* - <b>(DATE_PRECISION_SECOND + 1)</b> - rounds the date to 1 decimal
* point of a second
* - <b>(DATE_PRECISION_SECOND + 3)</b> - rounds the date to 3 decimal
* points of a second
* - <b>(DATE_PRECISION_SECOND - 1)</b> - rounds the date to the nearest 10
* seconds (thus it is equivalent to
* <b>DATE_PRECISION_10SECONDS</b>)
*
* @param int $pn_precision a 'DATE_PRECISION_*' constant (defaults to
* {@link DATE_PRECISION_DAY})
* @param bool $pb_correctinvalidtime whether to correct, by adding the
* local Summer time offset, the rounded
* time if it falls in the skipped hour
* (defaults to
* {@link DATE_CORRECTINVALIDTIME_DEFAULT})
*
* @return void
* @access public
* @since Method available since Release 1.5.0
*/
function round($pn_precision = DATE_PRECISION_DAY,
$pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT)
{
if ($pn_precision <= DATE_PRECISION_DAY) {
list($hn_year,
$hn_month,
$hn_day,
$hn_hour,
$hn_minute,
$hn_secondraw) =
Date_Calc::round($pn_precision,
$this->day,
$this->month,
$this->year,
$this->hour,
$this->minute,
$this->partsecond == 0.0 ?
$this->second :
$this->second + $this->partsecond,
$this->ob_countleapseconds);
if (is_float($hn_secondraw)) {
$hn_second = intval($hn_secondraw);
$hn_partsecond = $hn_secondraw - $hn_second;
} else {
$hn_second = $hn_secondraw;
$hn_partsecond = 0.0;
}
$this->setLocalTime($hn_day,
$hn_month,
$hn_year,
$hn_hour,
$hn_minute,
$hn_second,
$hn_partsecond,
true, // This is unlikely anyway, but the
// day starts with the repeated hour
// the first time around
$pb_correctinvalidtime);
return;
}
// ($pn_precision >= DATE_PRECISION_HOUR)
//
if ($this->tz->getDSTSavings() % 3600000 == 0 ||
($this->tz->getDSTSavings() % 60000 == 0 &&
$pn_precision >= DATE_PRECISION_MINUTE)
) {
list($hn_year,
$hn_month,
$hn_day,
$hn_hour,
$hn_minute,
$hn_secondraw) =
Date_Calc::round($pn_precision,
$this->on_standardday,
$this->on_standardmonth,
$this->on_standardyear,
$this->on_standardhour,
$this->on_standardminute,
$this->on_standardpartsecond == 0.0 ?
$this->on_standardsecond :
$this->on_standardsecond +
$this->on_standardpartsecond,
$this->ob_countleapseconds);
if (is_float($hn_secondraw)) {
$hn_second = intval($hn_secondraw);
$hn_partsecond = $hn_secondraw - $hn_second;
} else {
$hn_second = $hn_secondraw;
$hn_partsecond = 0.0;
}
$this->setStandardTime($hn_day,
$hn_month,
$hn_year,
$hn_hour,
$hn_minute,
$hn_second,
$hn_partsecond);
return;
}
// 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)
//
list($hn_year,
$hn_month,
$hn_day,
$hn_hour,
$hn_minute,
$hn_secondraw) =
Date_Calc::round($pn_precision,
$this->day,
$this->month,
$this->year,
$this->hour,
$this->minute,
$this->partsecond == 0.0 ?
$this->second :
$this->second + $this->partsecond,
$this->ob_countleapseconds);
if (is_float($hn_secondraw)) {
$hn_second = intval($hn_secondraw);
$hn_partsecond = $hn_secondraw - $hn_second;
} else {
$hn_second = $hn_secondraw;
$hn_partsecond = 0.0;
}
$this->setLocalTime($hn_day,
$hn_month,
$hn_year,
$hn_hour,
$hn_minute,
$hn_second,
$hn_partsecond,
false, // This will be right half the time
$pb_correctinvalidtime); // This will be right
// some of the time
// (depends on Summer
// time offset)
}
// }}}
// {{{ roundSeconds()
/**
* Rounds seconds up or down to the nearest specified unit
*
* N.B. this function is equivalent to calling:
*
* <code>$date_object->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
* (defaults to
* {@link DATE_CORRECTINVALIDTIME_DEFAULT})
*
* @return void
* @access public
* @since Method available since Release 1.5.0
*/
function roundSeconds($pn_precision = 0,
$pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT)
{
$this->round(DATE_PRECISION_SECOND + $pn_precision,
$pb_correctinvalidtime);
}
// }}}
// {{{ trunc()
/**
* 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:
*
* - {@link DATE_PRECISION_YEAR}
* - {@link DATE_PRECISION_MONTH}
* - {@link DATE_PRECISION_DAY} (default)
* - {@link DATE_PRECISION_HOUR}
* - {@link DATE_PRECISION_10MINUTES}
* - {@link DATE_PRECISION_MINUTE}
* - {@link DATE_PRECISION_10SECONDS}
* - {@link DATE_PRECISION_SECOND}
*
* 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.
* For example:
*
* - <b>DATE_PRECISION_YEAR</b> - truncates the month, day and time
* part of the year
* - <b>(DATE_PRECISION_YEAR - 1)</b> - truncates the unit part of the
* year, e.g. 1987 becomes 1980
* - <b>(DATE_PRECISION_YEAR - 3)</b> - truncates the hundreds part of the
* year, e.g. 1987 becomes 1000
* - <b>(DATE_PRECISION_SECOND + 1)</b> - truncates the part of the second
* less than 0.1 of a second, e.g.
* 3.26301 becomes 3.2 seconds
* - <b>(DATE_PRECISION_SECOND + 3)</b> - truncates the part of the second
* less than 0.001 of a second, e.g.
* 3.26301 becomes 3.263 seconds
* - <b>(DATE_PRECISION_SECOND - 1)</b> - truncates the unit part of the
* seconds (thus it is equivalent to
* <b>DATE_PRECISION_10SECONDS</b>)
*
* @param int $pn_precision a 'DATE_PRECISION_*' constant (defaults
* to {@link DATE_PRECISION_DAY})
* @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
* {@link DATE_CORRECTINVALIDTIME_DEFAULT})
*
* @return void
* @access public
* @since Method available since Release 1.5.0
*/
function trunc($pn_precision = DATE_PRECISION_DAY,
$pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT)
{
if ($pn_precision <= DATE_PRECISION_DAY) {
if ($pn_precision <= DATE_PRECISION_YEAR) {
$hn_month = 0;
$hn_day = 0;
$hn_hour = 0;
$hn_minute = 0;
$hn_second = 0;
$hn_partsecond = 0.0;
$hn_invprecision = DATE_PRECISION_YEAR - $pn_precision;
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)
} else {
$hn_year = $this->year;
}
} else if ($pn_precision == DATE_PRECISION_MONTH) {
$hn_year = $this->year;
$hn_month = $this->month;
$hn_day = 0;
$hn_hour = 0;
$hn_minute = 0;
$hn_second = 0;
$hn_partsecond = 0.0;
} else if ($pn_precision == DATE_PRECISION_DAY) {
$hn_year = $this->year;
$hn_month = $this->month;
$hn_day = $this->day;
$hn_hour = 0;
$hn_minute = 0;
$hn_second = 0;
$hn_partsecond = 0.0;
}
$this->setLocalTime($hn_day,
$hn_month,
$hn_year,
$hn_hour,
$hn_minute,
$hn_second,
$hn_partsecond,
true, // This is unlikely anyway, but the
// day starts with the repeated
// hour the first time around
$pb_correctinvalidtime);
return;
}
// Precision is at least equal to DATE_PRECISION_HOUR
//
if ($pn_precision == DATE_PRECISION_HOUR) {
$this->addSeconds($this->partsecond == 0.0 ?
-$this->second :
-$this->second - $this->partsecond);
//
// (leap seconds irrelevant)
$this->addMinutes(-$this->minute);
} else if ($pn_precision <= DATE_PRECISION_MINUTE) {
if ($pn_precision == DATE_PRECISION_10MINUTES) {
$this->addMinutes(-$this->minute % 10);
}
$this->addSeconds($this->partsecond == 0.0 ?
-$this->second :
-$this->second - $this->partsecond);
//
// (leap seconds irrelevant)
} else if ($pn_precision == DATE_PRECISION_10SECONDS) {
$this->addSeconds($this->partsecond == 0.0 ?
-$this->second % 10 :
(-$this->second % 10) - $this->partsecond);
//
// (leap seconds irrelevant)
} else {
// Assume Summer time offset cannot be composed of part-seconds:
//
$hn_precision = $pn_precision - DATE_PRECISION_SECOND;
$hn_partsecond = intval($this->on_standardpartsecond *
pow(10, $hn_precision)) /
pow(10, $hn_precision);
$this->setStandardTime($this->on_standardday,
$this->on_standardmonth,
$this->on_standardyear,
$this->on_standardhour,
$this->on_standardminute,
$this->on_standardsecond,
$hn_partsecond);
}
}
// }}}
// {{{ truncSeconds()
/**
* Truncates seconds according to the specified precision
*
* N.B. this function is equivalent to calling:
*
* <code>
* $date_object->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
* {@link DATE_CORRECTINVALIDTIME_DEFAULT})
*
* @return void
* @access public
* @since Method available since Release 1.5.0
*/
function truncSeconds($pn_precision = 0,
$pb_correctinvalidtime = DATE_CORRECTINVALIDTIME_DEFAULT)
{
$this->trunc(DATE_PRECISION_SECOND + $pn_precision,
$pb_correctinvalidtime);
}
// }}}
// {{{ getDate()
/**
* Gets a string (or other) representation of this date
*
* Returns a date in the format specified by the DATE_FORMAT_* constants,
* which should be one of the following:
*
* - {@link DATE_FORMAT_ISO} (default)
* - {@link DATE_FORMAT_ISO_BASIC}
* - {@link DATE_FORMAT_ISO_EXTENDED}
* - {@link DATE_FORMAT_ISO_EXTENDED_MICROTIME}
* - {@link DATE_FORMAT_TIMESTAMP}
* - {@link DATE_FORMAT_UNIXTIME}
*
* @param int $format format constant (DATE_FORMAT_*) of the output date
*
* @return string the date in the requested format (defaults to
* {@link DATE_FORMAT_ISO})
* @access public
*/
function getDate($format = DATE_FORMAT_ISO)
{
$ret;
switch ($format) {
case DATE_FORMAT_ISO:
$ret = $this->formatLikeStrftime("%Y-%m-%d %T");
break;
case DATE_FORMAT_ISO_BASIC:
$format = "%Y%m%dT%H%M%S";
if ($this->getTZID() == 'UTC') {
$format .= "Z";
}
$ret = $this->formatLikeStrftime($format);
break;
case DATE_FORMAT_ISO_EXTENDED:
$format = "%Y-%m-%dT%H:%M:%S";
if ($this->getTZID() == 'UTC') {
$format .= "Z";
}
$ret = $this->formatLikeStrftime($format);
break;
case DATE_FORMAT_ISO_EXTENDED_MICROTIME:
$format = "%Y-%m-%dT%H:%M:%s";
if ($this->getTZID() == 'UTC') {
$format .= "Z";
}
$ret = $this->formatLikeStrftime($format);
break;
case DATE_FORMAT_TIMESTAMP:
$ret = $this->formatLikeStrftime("%Y%m%d%H%M%S");
break;
case DATE_FORMAT_UNIXTIME:
$ret = $this->getTime();
if (!PEAR::isError($ret)) {
$ret = (string) $ret;
}
break;
}
return $ret;
}
// }}}
// {{{ format()
/**
* Formats the date according to the specified formatting code string
*
* This function is an alias for the method specified by the constant
* {@link DATE_FORMAT_METHOD} (which defaults to 'formatLikeStrftime'
* for backwards-compatibility).
*
* @return string date/time in given format
* @access public
* @see Date::formatLikeStrftime(), Date::formatLikeDate(),
* Date::formatLikeSQL()
*/
function format()
{
$ha_args = func_get_args();
return call_user_func_array(array(&$this, DATE_FORMAT_METHOD),
$ha_args);
}
// }}}
// {{{ formatLikeStrftime()
/**
* Formats the date according to the specified formatting code string,
* based on {@link http://www.php.net/strftime strftime()}
*
* Formats the date in the given format, much like
* strftime(). Most strftime() options are supported.
*
*
* Formatting options:
*
* - <b>%a</b> - abbreviated weekday name (Sun, Mon, Tue)
* - <b>%A</b> - full weekday name (Sunday, Monday, Tuesday)
* - <b>%b</b> - abbreviated month name (Jan, Feb, Mar)
* - <b>%B</b> - full month name (January, February, March)
* - <b>%C</b> - century number (the year divided by 100 and truncated
* to an integer, range 00 to 99)
* - <b>%d</b> - day of month (range 00 to 31)
* - <b>%D</b> - equivalent to '<b>%m/%d/%y</b>'
* - <b>%e</b> - day of month without leading noughts (range 0 to 31)
* - <b>%E</b> - {@link http://en.wikipedia.org/wiki/Julian_day Julian day} -
* no of days since Monday, 24th November, 4714 B.C. (in
* the proleptic Gregorian calendar)
* - <b>%g</b> - like '<b>%G</b>', but without the century
* - <b>%G</b> - the 4-digit year corresponding to the ISO week
* number (see '<b>%V</b>'). This has the same
* format and value as '<b>%Y</b>', except that if
* the ISO week number belongs to the previous or
* next year, that year is used instead.
* - <b>%h</b> - hour as decimal number without leading noughts (0
* to 23)
* - <b>%H</b> - hour as decimal number (00 to 23)
* - <b>%i</b> - hour as decimal number on 12-hour clock without
* leading noughts (1 to 12)
* - <b>%I</b> - hour as decimal number on 12-hour clock (01 to 12)
* - <b>%j</b> - day of year (range 001 to 366)
* - <b>%m</b> - month as decimal number (range 01 to 12)
* - <b>%M</b> - minute as a decimal number (00 to 59)
* - <b>%n</b> - newline character ("\n")
* - <b>%o</b> - raw timezone offset expressed as '+/-HH:MM'
* - <b>%O</b> - dst-corrected timezone offset expressed as '+/-HH:MM'
* - <b>%p</b> - either 'am' or 'pm' depending on the time
* - <b>%P</b> - either 'AM' or 'PM' depending on the time
* - <b>%r</b> - time in am/pm notation; equivalent to
* '<b>%I:%M:%S %p</b>'
* - <b>%R</b> - time in 24-hour notation; equivalent to
* '<b>%H:%M</b>'
* - <b>%s</b> - seconds including the micro-time (the decimal
* representation less than one second to six
* decimal places
* - <b>%S</b> - seconds as a decimal number (00 to 59)
* - <b>%t</b> - tab character ("\t")
* - <b>%T</b> - current time; equivalent to '<b>%H:%M:%S</b>'
* - <b>%u</b> - day of week as decimal (1 to 7; where 1 = Monday)
* - <b>%U</b> - 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)
* (00 to 53)
* - <b>%V</b> - the {@link http://en.wikipedia.org/wiki/ISO_week_date 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 '<b>%G</b>' or '<b>%g</b>' for the
* year component that corresponds to the week number
* for the specified timestamp.)
* - <b>%w</b> - day of week as decimal (0 to 6; where 0 = Sunday)
* - <b>%W</b> - 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)
* (00 to 53)
* - <b>%y</b> - year as decimal (range 00 to 99)
* - <b>%Y</b> - year as decimal including century (range 0000 to
* 9999)
* - <b>%Z</b> - 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'.
* - <b>%%</b> - literal '%'
*
*
* The following codes render a different output to that of
* {@link http://www.php.net/strftime strftime()}:
*
* - <b>%e</b> - in 'strftime()' a single digit is preceded by a space
* - <b>%h</b> - in 'strftime()' is equivalent to '<b>%b</b>'
* - <b>%U</b> - '<b>%U</b>' and '<b>%W</b>' 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.
* - <b>%W</b>
*
* @param string $format the format string for returned date/time
*
* @return string date/time in given format
* @access public
* @see Date::format(), Date::formatLikeDate(), Date::formatLikeSQL()
* @since Method available since Release 1.5.1
*/
function formatLikeStrftime($format)
{
$output = "";
$hn_isoyear = null;
$hn_isoweek = null;
$hn_isoday = null;
for ($strpos = 0; $strpos < strlen($format); $strpos++) {
$char = substr($format, $strpos, 1);
if ($char == "%") {
$nextchar = substr($format, $strpos + 1, 1);
switch ($nextchar) {
case "a":
$output .= Date_Calc::getWeekdayAbbrname($this->day,
$this->month, $this->year,
$this->getWeekdayAbbrnameLength);
break;
case "A":
$output .= Date_Calc::getWeekdayFullname($this->day,
$this->month, $this->year);
break;
case "b":
$output .= Date_Calc::getMonthAbbrname($this->month);
break;
case "B":
$output .= Date_Calc::getMonthFullname($this->month);
break;
case "C":
$output .= sprintf("%02d", intval($this->year / 100));
break;
case "d":
$output .= sprintf("%02d", $this->day);
break;
case "D":
$output .= sprintf("%02d/%02d/%02d", $this->month,
$this->day, $this->year);
break;
case "e":
$output .= $this->day;
break;
case "E":
$output .= Date_Calc::dateToDays($this->day, $this->month,
$this->year);
break;
case "g":
if (is_null($hn_isoyear))
list($hn_isoyear, $hn_isoweek, $hn_isoday) =
Date_Calc::isoWeekDate($this->day,
$this->month,
$this->year);
$output .= sprintf("%02d", $hn_isoyear % 100);
break;
case "G":
if (is_null($hn_isoyear))
list($hn_isoyear, $hn_isoweek, $hn_isoday) =
Date_Calc::isoWeekDate($this->day,
$this->month,
$this->year);
$output .= sprintf("%04d", $hn_isoyear);
break;
case 'h':
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$output .= sprintf("%d", $this->hour);
break;
case "H":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$output .= sprintf("%02d", $this->hour);
break;
case "i":
case "I":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$hour = $this->hour + 1 > 12 ?
$this->hour - 12 :
$this->hour;
$output .= $hour == 0 ?
12 :
($nextchar == "i" ?
$hour :
sprintf('%02d', $hour));
break;
case "j":
$output .= sprintf("%03d",
Date_Calc::dayOfYear($this->day,
$this->month,
$this->year));
break;
case "m":
$output .= sprintf("%02d", $this->month);
break;
case "M":
$output .= sprintf("%02d", $this->minute);
break;
case "n":
$output .= "\n";
break;
case "O":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$offms = $this->getTZOffset();
$direction = $offms >= 0 ? "+" : "-";
$offmins = abs($offms) / 1000 / 60;
$hours = $offmins / 60;
$minutes = $offmins % 60;
$output .= sprintf("%s%02d:%02d", $direction, $hours, $minutes);
break;
case "o":
$offms = $this->tz->getRawOffset($this);
$direction = $offms >= 0 ? "+" : "-";
$offmins = abs($offms) / 1000 / 60;
$hours = $offmins / 60;
$minutes = $offmins % 60;
$output .= sprintf("%s%02d:%02d", $direction, $hours, $minutes);
break;
case "p":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$output .= $this->hour >= 12 ? "pm" : "am";
break;
case "P":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$output .= $this->hour >= 12 ? "PM" : "AM";
break;
case "r":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$hour = $this->hour + 1 > 12 ?
$this->hour - 12 :
$this->hour;
$output .= sprintf("%02d:%02d:%02d %s",
$hour == 0 ? 12 : $hour,
$this->minute,
$this->second,
$this->hour >= 12 ? "PM" : "AM");
break;
case "R":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$output .= sprintf("%02d:%02d", $this->hour, $this->minute);
break;
case "s":
$output .= str_replace(',',
'.',
sprintf("%09f",
(float)((float) $this->second +
$this->partsecond)));
break;
case "S":
$output .= sprintf("%02d", $this->second);
break;
case "t":
$output .= "\t";
break;
case "T":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$output .= sprintf("%02d:%02d:%02d",
$this->hour,
$this->minute,
$this->second);
break;
case "u":
$hn_dayofweek = $this->getDayOfWeek();
$output .= $hn_dayofweek == 0 ? 7 : $hn_dayofweek;
break;
case "U":
$ha_week = Date_Calc::weekOfYear7th($this->day,
$this->month,
$this->year,
0);
$output .= sprintf("%02d", $ha_week[1]);
break;
case "V":
if (is_null($hn_isoyear))
list($hn_isoyear, $hn_isoweek, $hn_isoday) =
Date_Calc::isoWeekDate($this->day,
$this->month,
$this->year);
$output .= $hn_isoweek;
break;
case "w":
$output .= $this->getDayOfWeek();
break;
case "W":
$ha_week = Date_Calc::weekOfYear7th($this->day,
$this->month,
$this->year,
1);
$output .= sprintf("%02d", $ha_week[1]);
break;
case 'y':
$output .= sprintf('%0' .
($this->year < 0 ? '3' : '2') .
'd',
$this->year % 100);
break;
case "Y":
$output .= sprintf('%0' .
($this->year < 0 ? '5' : '4') .
'd',
$this->year);
break;
case "Z":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$output .= $this->getTZShortName();
break;
case "%":
$output .= "%";
break;
default:
$output .= $char.$nextchar;
}
$strpos++;
} else {
$output .= $char;
}
}
return $output;
}
// }}}
// {{{ _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
* capitalized
*
* @return string
* @access private
* @since Method available since Release 1.5.0
*/
function _getOrdinalSuffix($pn_num, $pb_uppercase = true)
{
switch (($pn_numabs = abs($pn_num)) % 100) {
case 11:
case 12:
case 13:
$hs_suffix = "th";
break;
default:
switch ($pn_numabs % 10) {
case 1:
$hs_suffix = "st";
break;
case 2:
$hs_suffix = "nd";
break;
case 3:
$hs_suffix = "rd";
break;
default:
$hs_suffix = "th";
}
}
return $pb_uppercase ? strtoupper($hs_suffix) : $hs_suffix;
}
// }}}
// {{{ _spellNumber()
/**
* Converts a number to its word representation
*
* Private helper function, particularly for {@link Date::formatLikeSQL()}.
* N.B. The second argument is the 'SP' code which can be specified in the
* format string for 'formatLikeSQL()' and is interpreted as follows:
*
* - <b>SP</b> - returns upper-case spelling, e.g. 'FOUR HUNDRED'
* - <b>Sp</b> - returns spelling with first character of each word
* capitalized, e.g. 'Four Hundred'
* - <b>sp</b> - 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
* be ordinal
* @param string $ps_capitalization string for specifying capitalization
* options
* @param string $ps_locale language name abbreviation used for
* formatting numbers as spelled-out words
*
* @return string
* @access private
* @since Method available since Release 1.5.0
*/
function _spellNumber($pn_num,
$pb_ordinal = false,
$ps_capitalization = "SP",
$ps_locale = "en_GB")
{
include_once "Numbers/Words.php";
$hs_words = Numbers_Words::toWords($pn_num, $ps_locale);
if (Pear::isError($hs_words)) {
return $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) {
$hs_words .= "th";
} else {
switch ($pn_numabs % 10) {
case 1:
$hs_words = substr($hs_words, 0, -3) . "first";
break;
case 2:
$hs_words = substr($hs_words, 0, -3) . "second";
break;
case 3:
$hs_words = substr($hs_words, 0, -3) . "ird";
break;
case 5:
$hs_words = substr($hs_words, 0, -2) . "fth";
break;
default:
switch (substr($hs_words, -1)) {
case "e":
$hs_words = substr($hs_words, 0, -1) . "th";
break;
case "t":
$hs_words .= "h";
break;
case "y":
$hs_words = substr($hs_words, 0, -1) . "ieth";
break;
default:
$hs_words .= "th";
}
}
}
}
if (($hs_char = substr($ps_capitalization, 0, 1)) ==
strtolower($hs_char)) {
$hb_upper = false;
$hs_words = strtolower($hs_words);
} else if (($hs_char = substr($ps_capitalization, 1, 1)) ==
strtolower($hs_char)) {
$hb_upper = false;
$hs_words = ucwords($hs_words);
} else {
$hb_upper = true;
$hs_words = strtoupper($hs_words);
}
return $hs_words;
}
// }}}
// {{{ _formatNumber()
/**
* Formats a number according to the specified format string
*
* Private helper function, for {@link Date::formatLikeSQL()}, which
* interprets the codes '<b>SP</b>' and '<b>TH</b>' and the combination
* of the two as follows:
*
* - <b>TH</b> - Ordinal number
* - <b>SP</b> - Spelled cardinal number
* - <b>SPTH</b> - Spelled ordinal number (combination of '<b>SP</b>'
* and '<b>TH</b>' in any order)
* - <b>THSP</b>
*
* Code '<b>SP</b>' can have the following three variations (which
* can also be used in combination with '<b>TH</b>'):
*
* - <b>SP</b> - returns upper-case spelling, e.g. 'FOUR HUNDRED'
* - <b>Sp</b> - returns spelling with first character of each word
* capitalized, e.g. 'Four Hundred'
* - <b>sp</b> - returns lower-case spelling, e.g. 'four hundred'
*
* Code '<b>TH</b>' can have the following two variations (although in
* combination with code '<b>SP</b>', the case specification of
* '<b>SP</b>' takes precedence):
*
* - <b>TH</b> - returns upper-case ordinal suffix, e.g. 400TH
* - <b>th</b> - 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 '<b>SP</b>'
* and '<b>TH</b>'. 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 '<b>SP</b>' preceding code '<b>TH</b>' (i.e. '<b>SPTH</b>',
* '<b>Spth</b>' or '<b>spth</b>').
*
* @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
* as numeral)
* @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
* formatting
* @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
*
* @return string
* @access private
* @since Method available since Release 1.5.0
*/
function _formatNumber($pn_num,
&$ps_format,
$pn_numofdigits,
$pb_nopad = false,
$pb_nosign = false,
$ps_locale = "en_GB",
$ps_thousandsep = null,
$pn_padtype = STR_PAD_LEFT)
{
$hs_code1 = substr($ps_format, 0, 2);
$hs_code2 = substr($ps_format, 2, 2);
$hs_sp = null;
$hs_th = null;
if (strtoupper($hs_code1) == "SP") {
$hs_sp = $hs_code1;
if (strtoupper($hs_code2) == "TH") {
$hs_th = $hs_code2;
}
} else if (strtoupper($hs_code1) == "TH") {
$hs_th = $hs_code1;
if (strtoupper($hs_code2) == "SP") {
$hs_sp = $hs_code2;
}
}
$hn_absnum = abs($pn_num);
if ($pn_numofdigits > 0 && strlen($hn_absnum) > $pn_numofdigits) {
$hn_absnum = intval(substr($hn_absnum, -$pn_numofdigits));
}
$hs_num = $hn_absnum;
if (!is_null($hs_sp)) {
// Spell out number:
//
$ps_format = $hs_sp .
(is_null($hs_th) ? "" : ($hs_sp == "SP" ? "TH" : "th"));
return $this->_spellNumber(!$pb_nosign && $pn_num < 0 ?
$hn_absnum * -1 :
$hn_absnum,
!is_null($hs_th),
$hs_sp,
$ps_locale);
} else {
// Display number as Arabic numeral:
//
if (!$pb_nopad) {
$hs_num = str_pad($hs_num, $pn_numofdigits, "0", $pn_padtype);
}
if (!is_null($ps_thousandsep)) {
for ($i = strlen($hs_num) - 3; $i > 0; $i -= 3) {
$hs_num = substr($hs_num, 0, $i) .
$ps_thousandsep .
substr($hs_num, $i);
}
}
if (!$pb_nosign) {
if ($pn_num < 0)
$hs_num = "-" . $hs_num;
else if (!$pb_nopad)
$hs_num = " " . $hs_num;
}
if (!is_null($hs_th)) {
$ps_format = $hs_th;
return $hs_num .
$this->_getOrdinalSuffix($pn_num,
substr($hs_th, 0, 1) == "T");
} else {
$ps_format = "";
return $hs_num;
}
}
}
// }}}
// {{{ formatLikeSQL()
/**
* Formats the date according to the specified formatting code string,
* based on SQL date-formatting codes
*
* Most codes reproduce the no of digits equal to the length of the
* code, for example, '<b>YYY</b>' 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
* '<b>NPYYY</b>', which will return '89'.
*
* For negative values, the sign will be discarded, unless the
* '<b>S</b>' 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:
*
* - <b>YYYY</b> - returns '2007'
* - <b>SYYYY</b> - returns ' 2007'
* - <b>NPSYYYY</b> - returns '2007'
*
* The no-padding modifier '<b>NP</b>' can be used with numeric codes
* to suppress leading (or trailing in the case of code '<b>F</b>')
* noughts, and with character-returning codes such as '<b>DAY</b>'
* 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:
*
* - <b>Day</b> - returns 'Monday ' because the maximum length of
* this code is 'Wednesday';
* - <b>NPDay</b> - 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 '<b>MONTH</b>', will
* set the capitalization according to the code, so for example:
*
* - <b>MONTH</b> - returns upper-case spelling, e.g. 'JANUARY'
* - <b>Month</b> - returns spelling with first character of each word
* capitalized, e.g. 'January'
* - <b>month</b> - returns lower-case spelling, e.g. 'january'
*
* Where it makes sense, numeric codes can be combined with a following
* '<b>SP</b>' code which spells out the number, or with a '<b>TH</b>'
* code, which renders the code as an ordinal ('<b>TH</b>' only works
* in English), for example, for 31st December:
*
* - <b>DD</b> - returns '31'
* - <b>DDTH</b> - returns '31ST'
* - <b>DDth</b> - returns '31st'
* - <b>DDSP</b> - returns 'THIRTY-ONE'
* - <b>DDSp</b> - returns 'Thirty-one'
* - <b>DDsp</b> - returns 'thirty-one'
* - <b>DDSPTH</b> - returns 'THIRTY-FIRST'
* - <b>DDSpth</b> - returns 'Thirty-first'
* - <b>DDspth</b> - returns 'thirty-first'
*
*
* All formatting options:
*
* - <b>-</b> (All punctuation and white-space is reproduced unchanged)
* - <b>/</b>
* - <b>,</b>
* - <b>.</b>
* - <b>;</b>
* - <b>:</b>
* - <b>"text"</b> - Quoted text is reproduced unchanged (escape using
* '\')
* - <b>AD</b> - AD indicator with or without full stops
* - <b>A.D.</b>
* - <b>AM</b> - Meridian indicator with or without full stops
* - <b>A.M.</b>
* - <b>BC</b> - BC indicator with or without full stops
* - <b>B.C.</b>
* - <b>BCE</b> - BCE indicator with or without full stops
* - <b>B.C.E.</b>
* - <b>CC</b> - Century, i.e. the year divided by 100, discarding the
* remainder; '<b>S</b>' prefixes negative years with a
* minus sign
* - <b>SCC</b>
* - <b>CE</b> - CE indicator with or without full stops
* - <b>C.E.</b>
* - <b>D</b> - Day of week (0-6), where 0 represents Sunday
* - <b>DAY</b> - Name of day, padded with blanks to display width of the
* widest name of day in the locale of the machine
* - <b>DD</b> - Day of month (1-31)
* - <b>DDD</b> - Day of year (1-366)
* - <b>DY</b> - Abbreviated name of day
* - <b>FFF</b> - Fractional seconds; no radix character is printed. The
* no of '<b>F</b>'s determines the no of digits of the
* part-second to return; e.g. 'HH:MI:SS.FF'
* - <b>F[integer]</b> - The integer after '<b>F</b>' specifies the
* number of digits of the part-second to return.
* This is an alternative to using several
* '<b>F</b>'s in sequence, and '<b>F3</b>' is thus
* equivalent to using '<b>FFF</b>'.
* - <b>HH</b> - Hour of day (0-23)
* - <b>HH12</b> - Hour of day (1-12)
* - <b>HH24</b> - Hour of day (0-23)
* - <b>ID</b> - Day of week (1-7) based on the ISO 8601 standard (see
* '<b>IW</b>')
* - <b>IW</b> - Week of year (1-52 or 1-53) based on the
* {@link http://en.wikipedia.org/wiki/ISO_week_date ISO 8601 standard}
* - <b>IYYY</b> - 4-digit year based on the ISO 8601 standard (see
* '<b>IW</b>'); '<b>S</b>' prefixes negative years with a
* minus sign
* - <b>SIYYY</b>
* - <b>IYY</b> - Last 3, 2, or 1 digit(s) of ISO year
* - <b>IY</b>
* - <b>I</b>
* - <b>J</b> - {@link http://en.wikipedia.org/wiki/Julian_day Julian day} -
* the number of days since Monday, 24th November, 4714 B.C.
* (proleptic Gregorian calendar)
* - <b>MI</b> - Minute (0-59)
* - <b>MM</b> - Month (01-12; January = 01)
* - <b>MON</b> - Abbreviated name of month
* - <b>MONTH</b> - Name of month, padded with blanks to display width of
* the widest name of month in the date language used for
* - <b>PM</b> - Meridian indicator with or without full stops
* - <b>P.M.</b>
* - <b>Q</b> - Quarter of year (1, 2, 3, 4; January - March = 1)
* - <b>RM</b> - Roman numeral month (I-XII; January = I); N.B. padded
* with leading spaces.
* - <b>SS</b> - Second (0-59)
* - <b>SSSSS</b> - Seconds past midnight (0-86399)
* - <b>TZC</b> - 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'.
* N.B. this is not a unique identifier - for this purpose
* use the time zone region (code '<b>TZR</b>').
* - <b>TZH</b> - Time zone hour; '<b>S</b>' 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 '<b>NP</b>'). Also note that if you
* combine with the '<b>SP</b>' code, the sign will not be
* spelled out. (I.e. '<b>STZHSp</b>' will produce '+One',
* for example, and not 'Plus One'.
* '<b>TZH:TZM</b>' will produce, for example, '+05:30'.
* (Also see '<b>TZM</b>' format code)
* - <b>STZH</b>
* - <b>TZI</b> - Whether or not the date is in Summer time (daylight
* saving time). Returns '1' if Summer time, else '0'.
* - <b>TZM</b> - Time zone minute, without any +/- sign. (Also see
* '<b>TZH</b>' format element)
* - <b>TZN</b> - 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 '<b>TZR</b>').
* - <b>TZO</b> - Time zone offset in ISO 8601 form - that is, 'Z' if
* UTC, else [+/-][hh]:[mm] (which would be equivalent
* to '<b>STZH:TZM</b>'). Note that this result is right
* padded.
* with spaces by default, (i.e. if 'Z').
* - <b>TZS</b> - Time zone offset in seconds; '<b>S</b>' prefixes
* negative sign with minus sign '-' if negative, and no
* sign if positive (i.e. -43200 to 50400).
* - <b>STZS</b>
* - <b>TZR</b> - Time zone region, that is, the name or ID of the time
* zone e.g. 'Europe/London'. This value is unique for
* each time zone.
* - <b>U</b> - Seconds since the Unix Epoch -
* January 1 1970 00:00:00 GMT
* - <b>W</b> - 'Absolute' week of month (1-5), counting week 1 as
* 1st-7th of the year, regardless of the day
* - <b>W1</b> - Week of year (1-54), counting week 1 as the week that
* contains 1st January
* - <b>W4</b> - Week of year (1-53), counting week 1 as the week that
* contains 4th January (i.e. first week with at least 4
* days)
* - <b>W7</b> - Week of year (1-53), counting week 1 as the week that
* contains 7th January (i.e. first full week)
* - <b>WW</b> - 'Absolute' week of year (1-53), counting week 1 as
* 1st-7th of the year, regardless of the day
* - <b>YEAR</b> - Year, spelled out; '<b>S</b>' prefixes negative
* years with 'MINUS'; N.B. '<b>YEAR</b>' differs from
* '<b>YYYYSP</b>' in that the first will render 1923,
* for example, as 'NINETEEN TWENTY-THREE, and the
* second as 'ONE THOUSAND NINE HUNDRED TWENTY-THREE'
* - <b>SYEAR</b>
* - <b>YYYY</b> - 4-digit year; '<b>S</b>' prefixes negative years
* with a minus sign
* - <b>SYYYY</b>
* - <b>YYY</b> - Last 3, 2, or 1 digit(s) of year
* - <b>YY</b>
* - <b>Y</b>
* - <b>Y,YYY</b> - Year with thousands-separator in this position; five
* possible separators
* - <b>Y.YYY</b>
* - <b>Y·YYY</b> - N.B. space-dot (mid-dot, interpunct) is valid only in
* ISO 8859-1 (so take care when using UTF-8 in
* particular)
* - <b>Y'YYY</b>
* - <b>Y YYY</b>
*
* In addition the following codes can be used in combination with other
* codes;
* Codes that modify the next code in the format string:
*
* - <b>NP</b> - '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 '<b>MM</b>'):
*
* - <b>TH</b> - Ordinal number
* - <b>SP</b> - Spelled cardinal number
* - <b>SPTH</b> - Spelled ordinal number (combination of '<b>SP</b>'
* and '<b>TH</b>' in any order)
* - <b>THSP</b>
*
* Code '<b>SP</b>' can have the following three variations (which can
* also be used in combination with '<b>TH</b>'):
*
* - <b>SP</b> - returns upper-case spelling, e.g. 'FOUR HUNDRED'
* - <b>Sp</b> - returns spelling with first character of each word
* capitalized, e.g. 'Four Hundred'
* - <b>sp</b> - returns lower-case spelling, e.g. 'four hundred'
*
* Code '<b>TH</b>' can have the following two variations (although in
* combination with code '<b>SP</b>', the case specification of
* '<b>SP</b>' takes precedence):
*
* - <b>TH</b> - returns upper-case ordinal suffix, e.g. 400TH
* - <b>th</b> - 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
* @access public
* @see Date::format(), Date::formatLikeStrftime(), Date::formatLikeDate()
* @since Method available since Release 1.5.0
*/
function formatLikeSQL($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',
$ps_format)) {
return PEAR::raiseError("Invalid date format '$ps_format'",
DATE_ERROR_INVALIDFORMATSTRING);
}
$ret = "";
$i = 0;
$hb_nopadflag = false;
$hb_showsignflag = false;
$hn_weekdaypad = null;
$hn_monthpad = null;
$hn_isoyear = null;
$hn_isoweek = null;
$hn_isoday = null;
$hn_tzoffset = null;
while ($i < strlen($ps_format)) {
$hb_lower = false;
if ($hb_nopadflag) {
$hb_nopad = true;
} else {
$hb_nopad = false;
}
if ($hb_showsignflag) {
$hb_nosign = false;
} else {
$hb_nosign = true;
}
$hb_nopadflag = false;
$hb_showsignflag = false;
switch ($hs_char = substr($ps_format, $i, 1)) {
case "-":
case "/":
case ",":
case ".":
case ";":
case ":":
case " ":
$ret .= $hs_char;
$i += 1;
break;
case "\"":
preg_match('/(([^"\\\\]|\\\\\\\\|\\\\")*)"/',
$ps_format,
$ha_matches,
PREG_OFFSET_CAPTURE,
$i + 1);
$ret .= str_replace(array('\\\\', '\\"'),
array('\\', '"'),
$ha_matches[1][0]);
$i += strlen($ha_matches[0][0]) + 1;
break;
case "a":
$hb_lower = true;
case "A":
if (strtoupper(substr($ps_format, $i, 4)) == "A.D.") {
$ret .= $this->year >= 0 ?
($hb_lower ? "a.d." : "A.D.") :
($hb_lower ? "b.c." : "B.C.");
$i += 4;
} else if (strtoupper(substr($ps_format, $i, 2)) == "AD") {
$ret .= $this->year >= 0 ?
($hb_lower ? "ad" : "AD") :
($hb_lower ? "bc" : "BC");
$i += 2;
} else {
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
if (strtoupper(substr($ps_format, $i, 4)) == "A.M.") {
$ret .= $this->hour < 12 ?
($hb_lower ? "a.m." : "A.M.") :
($hb_lower ? "p.m." : "P.M.");
$i += 4;
} else if (strtoupper(substr($ps_format, $i, 2)) == "AM") {
$ret .= $this->hour < 12 ?
($hb_lower ? "am" : "AM") :
($hb_lower ? "pm" : "PM");
$i += 2;
}
}
break;
case "b":
$hb_lower = true;
case "B":
// Check for 'B.C.E.' first:
//
if (strtoupper(substr($ps_format, $i, 6)) == "B.C.E.") {
if ($this->year >= 0) {
$hs_era = $hb_lower ? "c.e." : "C.E.";
$ret .= $hb_nopad ?
$hs_era :
str_pad($hs_era, 6, " ", STR_PAD_RIGHT);
} else {
$ret .= $hb_lower ? "b.c.e." : "B.C.E.";
}
$i += 6;
} else if (strtoupper(substr($ps_format, $i, 3)) == "BCE") {
if ($this->year >= 0) {
$hs_era = $hb_lower ? "ce" : "CE";
$ret .= $hb_nopad ?
$hs_era :
str_pad($hs_era, 3, " ", STR_PAD_RIGHT);
} else {
$ret .= $hb_lower ? "bce" : "BCE";
}
$i += 3;
} else if (strtoupper(substr($ps_format, $i, 4)) == "B.C.") {
$ret .= $this->year >= 0 ?
($hb_lower ? "a.d." : "A.D.") :
($hb_lower ? "b.c." : "B.C.");
$i += 4;
} else if (strtoupper(substr($ps_format, $i, 2)) == "BC") {
$ret .= $this->year >= 0 ?
($hb_lower ? "ad" : "AD") :
($hb_lower ? "bc" : "BC");
$i += 2;
}
break;
case "c":
$hb_lower = true;
case "C":
if (strtoupper(substr($ps_format, $i, 4)) == "C.E.") {
if ($this->year >= 0) {
$hs_era = $hb_lower ? "c.e." : "C.E.";
$ret .= $hb_nopad ?
$hs_era :
str_pad($hs_era, 6, " ", STR_PAD_RIGHT);
} else {
$ret .= $hb_lower ? "b.c.e." : "B.C.E.";
}
$i += 4;
} else if (strtoupper(substr($ps_format, $i, 2)) == "CE") {
if ($this->year >= 0) {
$hs_era = $hb_lower ? "ce" : "CE";
$ret .= $hb_nopad ?
$hs_era :
str_pad($hs_era, 3, " ", STR_PAD_RIGHT);
} else {
$ret .= $hb_lower ? "bce" : "BCE";
}
$i += 2;
} else {
// Code C(CCC...):
//
$hn_codelen = 1;
while (strtoupper(substr($ps_format,
$i + $hn_codelen,
1)) == "C")
++$hn_codelen;
// Check next code is not 'CE' or 'C.E.'
//
if ($hn_codelen > 1 &&
(strtoupper(substr($ps_format,
$i + $hn_codelen - 1,
4)) == "C.E." ||
strtoupper(substr($ps_format,
$i + $hn_codelen - 1,
2)) == "CE"
))
--$hn_codelen;
$hn_century = intval($this->year / 100);
$hs_numberformat = substr($ps_format, $i + $hn_codelen, 4);
$hs_century = $this->_formatNumber($hn_century,
$hs_numberformat,
$hn_codelen,
$hb_nopad,
$hb_nosign,
$ps_locale);
if (Pear::isError($hs_century))
return $hs_century;
$ret .= $hs_century;
$i += $hn_codelen + strlen($hs_numberformat);
}
break;
case "d":
$hb_lower = true;
case "D":
if (strtoupper(substr($ps_format, $i, 3)) == "DAY") {
$hs_day = Date_Calc::getWeekdayFullname($this->day,
$this->month,
$this->year);
if (!$hb_nopad) {
if (is_null($hn_weekdaypad)) {
// Set week-day padding variable:
//
$hn_weekdaypad = 0;
foreach (Date_Calc::getWeekDays() as $hs_weekday)
$hn_weekdaypad = max($hn_weekdaypad,
strlen($hs_weekday));
}
$hs_day = str_pad($hs_day,
$hn_weekdaypad,
" ",
STR_PAD_RIGHT);
}
$ret .= $hb_lower ?
strtolower($hs_day) :
(substr($ps_format, $i + 1, 1) == "A" ?
strtoupper($hs_day) :
$hs_day);
$i += 3;
} else if (strtoupper(substr($ps_format, $i, 2)) == "DY") {
$hs_day = Date_Calc::getWeekdayAbbrname($this->day,
$this->month,
$this->year);
$ret .= $hb_lower ?
strtolower($hs_day) :
(substr($ps_format, $i + 1, 1) == "Y" ?
strtoupper($hs_day) :
$hs_day);
$i += 2;
} else if (strtoupper(substr($ps_format, $i, 3)) == "DDD" &&
strtoupper(substr($ps_format, $i + 2, 3)) != "DAY" &&
strtoupper(substr($ps_format, $i + 2, 2)) != "DY"
) {
$hn_day = Date_Calc::dayOfYear($this->day,
$this->month,
$this->year);
$hs_numberformat = substr($ps_format, $i + 3, 4);
$hs_day = $this->_formatNumber($hn_day,
$hs_numberformat,
3,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_day))
return $hs_day;
$ret .= $hs_day;
$i += 3 + strlen($hs_numberformat);
} else if (strtoupper(substr($ps_format, $i, 2)) == "DD" &&
strtoupper(substr($ps_format, $i + 1, 3)) != "DAY" &&
strtoupper(substr($ps_format, $i + 1, 2)) != "DY"
) {
$hs_numberformat = substr($ps_format, $i + 2, 4);
$hs_day = $this->_formatNumber($this->day,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_day))
return $hs_day;
$ret .= $hs_day;
$i += 2 + strlen($hs_numberformat);
} else {
// Code 'D':
//
$hn_day = Date_Calc::dayOfWeek($this->day,
$this->month,
$this->year);
$hs_numberformat = substr($ps_format, $i + 1, 4);
$hs_day = $this->_formatNumber($hn_day,
$hs_numberformat,
1,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_day))
return $hs_day;
$ret .= $hs_day;
$i += 1 + strlen($hs_numberformat);
}
break;
case "f":
case "F":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$hn_codelen = 1;
if (is_numeric(substr($ps_format, $i + $hn_codelen, 1))) {
++$hn_codelen;
while (is_numeric(substr($ps_format, $i + $hn_codelen, 1)))
++$hn_codelen;
$hn_partsecdigits = substr($ps_format, $i + 1, $hn_codelen - 1);
} else {
while (strtoupper(substr($ps_format,
$i + $hn_codelen,
1)) == "F")
++$hn_codelen;
// Check next code is not F[numeric]:
//
if ($hn_codelen > 1 &&
is_numeric(substr($ps_format, $i + $hn_codelen, 1)))
--$hn_codelen;
$hn_partsecdigits = $hn_codelen;
}
$hs_partsec = (string) $this->partsecond;
if (preg_match('/^([0-9]+)(\.([0-9]+))?E-([0-9]+)$/i',
$hs_partsec,
$ha_matches)) {
$hs_partsec =
str_repeat("0", $ha_matches[4] - strlen($ha_matches[1])) .
$ha_matches[1] .
$ha_matches[3];
} else {
$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:
//
if ($hb_nopad) {
$hs_partsec = rtrim($hs_partsec, "0");
if ($hs_partsec == "")
$hs_partsec = "0";
} else {
$hs_partsec = str_pad($hs_partsec,
$hn_partsecdigits,
"0",
STR_PAD_RIGHT);
}
$ret .= $hs_partsec;
$i += $hn_codelen;
break;
case "h":
case "H":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
if (strtoupper(substr($ps_format, $i, 4)) == "HH12") {
$hn_hour = $this->hour % 12;
if ($hn_hour == 0)
$hn_hour = 12;
$hn_codelen = 4;
} else {
// Code 'HH' or 'HH24':
//
$hn_hour = $this->hour;
$hn_codelen = strtoupper(substr($ps_format,
$i,
4)) == "HH24" ? 4 : 2;
}
$hs_numberformat = substr($ps_format, $i + $hn_codelen, 4);
$hs_hour = $this->_formatNumber($hn_hour,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_hour))
return $hs_hour;
$ret .= $hs_hour;
$i += $hn_codelen + strlen($hs_numberformat);
break;
case "i":
case "I":
if (is_null($hn_isoyear))
list($hn_isoyear, $hn_isoweek, $hn_isoday) =
Date_Calc::isoWeekDate($this->day,
$this->month,
$this->year);
if (strtoupper(substr($ps_format, $i, 2)) == "ID" &&
strtoupper(substr($ps_format, $i + 1, 3)) != "DAY"
) {
$hs_numberformat = substr($ps_format, $i + 2, 4);
$hs_isoday = $this->_formatNumber($hn_isoday,
$hs_numberformat,
1,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_isoday))
return $hs_isoday;
$ret .= $hs_isoday;
$i += 2 + strlen($hs_numberformat);
} else if (strtoupper(substr($ps_format, $i, 2)) == "IW") {
$hs_numberformat = substr($ps_format, $i + 2, 4);
$hs_isoweek = $this->_formatNumber($hn_isoweek,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_isoweek))
return $hs_isoweek;
$ret .= $hs_isoweek;
$i += 2 + strlen($hs_numberformat);
} else {
// Code I(YYY...):
//
$hn_codelen = 1;
while (strtoupper(substr($ps_format,
$i + $hn_codelen,
1)) == "Y")
++$hn_codelen;
$hs_numberformat = substr($ps_format, $i + $hn_codelen, 4);
$hs_isoyear = $this->_formatNumber($hn_isoyear,
$hs_numberformat,
$hn_codelen,
$hb_nopad,
$hb_nosign,
$ps_locale);
if (Pear::isError($hs_isoyear))
return $hs_isoyear;
$ret .= $hs_isoyear;
$i += $hn_codelen + strlen($hs_numberformat);
}
break;
case "j":
case "J":
$hn_jd = Date_Calc::dateToDays($this->day,
$this->month,
$this->year);
$hs_numberformat = substr($ps_format, $i + 1, 4);
// Allow sign if negative; allow all digits (specify nought);
// suppress padding:
//
$hs_jd = $this->_formatNumber($hn_jd,
$hs_numberformat,
0,
true,
false,
$ps_locale);
if (Pear::isError($hs_jd))
return $hs_jd;
$ret .= $hs_jd;
$i += 1 + strlen($hs_numberformat);
break;
case "m":
$hb_lower = true;
case "M":
if (strtoupper(substr($ps_format, $i, 2)) == "MI") {
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$hs_numberformat = substr($ps_format, $i + 2, 4);
$hs_minute = $this->_formatNumber($this->minute,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_minute))
return $hs_minute;
$ret .= $hs_minute;
$i += 2 + strlen($hs_numberformat);
} else if (strtoupper(substr($ps_format, $i, 2)) == "MM") {
$hs_numberformat = substr($ps_format, $i + 2, 4);
$hs_month = $this->_formatNumber($this->month,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_month))
return $hs_month;
$ret .= $hs_month;
$i += 2 + strlen($hs_numberformat);
} else if (strtoupper(substr($ps_format, $i, 5)) == "MONTH") {
$hs_month = Date_Calc::getMonthFullname($this->month);
if (!$hb_nopad) {
if (is_null($hn_monthpad)) {
// Set month padding variable:
//
$hn_monthpad = 0;
foreach (Date_Calc::getMonthNames() as $hs_monthofyear)
$hn_monthpad = max($hn_monthpad,
strlen($hs_monthofyear));
}
$hs_month = str_pad($hs_month,
$hn_monthpad,
" ",
STR_PAD_RIGHT);
}
$ret .= $hb_lower ?
strtolower($hs_month) :
(substr($ps_format, $i + 1, 1) == "O" ?
strtoupper($hs_month) :
$hs_month);
$i += 5;
} else if (strtoupper(substr($ps_format, $i, 3)) == "MON") {
$hs_month = Date_Calc::getMonthAbbrname($this->month);
$ret .= $hb_lower ?
strtolower($hs_month) :
(substr($ps_format, $i + 1, 1) == "O" ?
strtoupper($hs_month) :
$hs_month);
$i += 3;
}
break;
case "n":
case "N":
// No-Padding rule 'NP' applies to the next code (either trailing
// spaces or leading/trailing noughts):
//
$hb_nopadflag = true;
$i += 2;
break;
case "p":
$hb_lower = true;
case "P":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
if (strtoupper(substr($ps_format, $i, 4)) == "P.M.") {
$ret .= $this->hour < 12 ?
($hb_lower ? "a.m." : "A.M.") :
($hb_lower ? "p.m." : "P.M.");
$i += 4;
} else if (strtoupper(substr($ps_format, $i, 2)) == "PM") {
$ret .= $this->hour < 12 ?
($hb_lower ? "am" : "AM") :
($hb_lower ? "pm" : "PM");
$i += 2;
}
break;
case "q":
case "Q":
// 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:
//
$hn_quarter = Date_Calc::quarterOfYear($this->day,
$this->month,
$this->year);
$hs_numberformat = substr($ps_format, $i + 1, 4);
$hs_quarter = $this->_formatNumber($hn_quarter,
$hs_numberformat,
1,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_quarter))
return $hs_quarter;
$ret .= $hs_quarter;
$i += 1 + strlen($hs_numberformat);
break;
case "r":
$hb_lower = true;
case "R":
// Code 'RM':
//
switch ($this->month) {
case 1:
$hs_monthroman = "i";
break;
case 2:
$hs_monthroman = "ii";
break;
case 3:
$hs_monthroman = "iii";
break;
case 4:
$hs_monthroman = "iv";
break;
case 5:
$hs_monthroman = "v";
break;
case 6:
$hs_monthroman = "vi";
break;
case 7:
$hs_monthroman = "vii";
break;
case 8:
$hs_monthroman = "viii";
break;
case 9:
$hs_monthroman = "ix";
break;
case 10:
$hs_monthroman = "x";
break;
case 11:
$hs_monthroman = "xi";
break;
case 12:
$hs_monthroman = "xii";
break;
}
$hs_monthroman = $hb_lower ?
$hs_monthroman :
strtoupper($hs_monthroman);
$ret .= $hb_nopad ?
$hs_monthroman :
str_pad($hs_monthroman, 4, " ", STR_PAD_LEFT);
$i += 2;
break;
case "s":
case "S":
// Check for 'SSSSS' before 'SS':
//
if (strtoupper(substr($ps_format, $i, 5)) == "SSSSS") {
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$hs_numberformat = substr($ps_format, $i + 5, 4);
$hn_second = Date_Calc::secondsPastMidnight($this->hour,
$this->minute,
$this->second);
$hs_second = $this->_formatNumber($hn_second,
$hs_numberformat,
5,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_second))
return $hs_second;
$ret .= $hs_second;
$i += 5 + strlen($hs_numberformat);
} else if (strtoupper(substr($ps_format, $i, 2)) == "SS") {
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$hs_numberformat = substr($ps_format, $i + 2, 4);
$hs_second = $this->_formatNumber($this->second,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_second))
return $hs_second;
$ret .= $hs_second;
$i += 2 + strlen($hs_numberformat);
} else {
// One of the following codes:
// 'SC(CCC...)'
// 'SY(YYY...)'
// 'SIY(YYY...)'
// 'STZH'
// 'STZS'
// 'SYEAR'
//
$hb_showsignflag = true;
if ($hb_nopad)
$hb_nopadflag = true;
++$i;
}
break;
case "t":
case "T":
// Code TZ[...]:
//
if (strtoupper(substr($ps_format, $i, 3)) == "TZR") {
// This time-zone-related code can be called when the time is
// invalid, but the others should return an error:
//
$ret .= $this->getTZID();
$i += 3;
} else {
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
if (strtoupper(substr($ps_format, $i, 3)) == "TZC") {
$ret .= $this->getTZShortName();
$i += 3;
} else if (strtoupper(substr($ps_format, $i, 3)) == "TZH") {
if (is_null($hn_tzoffset))
$hn_tzoffset = $this->getTZOffset();
$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,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_tzh))
return $hs_tzh;
// Display sign, even if positive:
//
$ret .= ($hb_nosign ? "" : ($hn_tzh >= 0 ? '+' : '-')) .
$hs_tzh;
$i += 3 + strlen($hs_numberformat);
} else if (strtoupper(substr($ps_format, $i, 3)) == "TZI") {
$ret .= ($this->inDaylightTime() ? '1' : '0');
$i += 3;
} else if (strtoupper(substr($ps_format, $i, 3)) == "TZM") {
if (is_null($hn_tzoffset))
$hn_tzoffset = $this->getTZOffset();
$hs_numberformat = substr($ps_format, $i + 3, 4);
$hn_tzm = intval(($hn_tzoffset % 3600000) / 60000);
// Suppress sign:
//
$hs_tzm = $this->_formatNumber($hn_tzm,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_tzm))
return $hs_tzm;
$ret .= $hs_tzm;
$i += 3 + strlen($hs_numberformat);
} else if (strtoupper(substr($ps_format, $i, 3)) == "TZN") {
$ret .= $this->getTZLongName();
$i += 3;
} else if (strtoupper(substr($ps_format, $i, 3)) == "TZO") {
if (is_null($hn_tzoffset))
$hn_tzoffset = $this->getTZOffset();
$hn_tzh = intval(abs($hn_tzoffset) / 3600000);
$hn_tzm = intval((abs($hn_tzoffset) % 3600000) / 60000);
if ($hn_tzoffset == 0) {
$ret .= $hb_nopad ? "Z" : "Z ";
} else {
// Display sign, even if positive:
//
$ret .= ($hn_tzoffset >= 0 ? '+' : '-') .
sprintf("%02d", $hn_tzh) .
":" .
sprintf("%02d", $hn_tzm);
}
$i += 3;
} else if (strtoupper(substr($ps_format, $i, 3)) == "TZS") {
if (is_null($hn_tzoffset))
$hn_tzoffset = $this->getTZOffset();
$hs_numberformat = substr($ps_format, $i + 3, 4);
$hn_tzs = intval($hn_tzoffset / 1000);
$hs_tzs = $this->_formatNumber($hn_tzs,
$hs_numberformat,
5,
$hb_nopad,
$hb_nosign,
$ps_locale);
if (Pear::isError($hs_tzs))
return $hs_tzs;
$ret .= $hs_tzs;
$i += 3 + strlen($hs_numberformat);
}
}
break;
case "u":
case "U":
if ($this->ob_invalidtime)
return $this->_getErrorInvalidTime();
$hn_unixtime = $this->getTime();
$hs_numberformat = substr($ps_format, $i + 1, 4);
// Allow sign if negative; allow all digits (specify nought);
// suppress padding:
//
$hs_unixtime = $this->_formatNumber($hn_unixtime,
$hs_numberformat,
0,
true,
false,
$ps_locale);
if (Pear::isError($hs_unixtime))
return $hs_unixtime;
$ret .= $hs_unixtime;
$i += 1 + strlen($hs_numberformat);
break;
case "w":
case "W":
// Check for 'WW' before 'W':
//
if (strtoupper(substr($ps_format, $i, 2)) == "WW") {
$hn_week = Date_Calc::weekOfYearAbsolute($this->day,
$this->month,
$this->year);
$hs_numberformat = substr($ps_format, $i + 2, 4);
$hs_week = $this->_formatNumber($hn_week,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_week))
return $hs_week;
$ret .= $hs_week;
$i += 2 + strlen($hs_numberformat);
} else if (strtoupper(substr($ps_format, $i, 2)) == "W1") {
$hn_week = Date_Calc::weekOfYear1st($this->day,
$this->month,
$this->year);
$hs_numberformat = substr($ps_format, $i + 2, 4);
$hs_week = $this->_formatNumber($hn_week,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_week))
return $hs_week;
$ret .= $hs_week;
$i += 2 + strlen($hs_numberformat);
} else if (strtoupper(substr($ps_format, $i, 2)) == "W4") {
$ha_week = Date_Calc::weekOfYear4th($this->day,
$this->month,
$this->year);
$hn_week = $ha_week[1];
$hs_numberformat = substr($ps_format, $i + 2, 4);
$hs_week = $this->_formatNumber($hn_week,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_week))
return $hs_week;
$ret .= $hs_week;
$i += 2 + strlen($hs_numberformat);
} else if (strtoupper(substr($ps_format, $i, 2)) == "W7") {
$ha_week = Date_Calc::weekOfYear7th($this->day,
$this->month,
$this->year);
$hn_week = $ha_week[1];
$hs_numberformat = substr($ps_format, $i + 2, 4);
$hs_week = $this->_formatNumber($hn_week,
$hs_numberformat,
2,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_week))
return $hs_week;
$ret .= $hs_week;
$i += 2 + strlen($hs_numberformat);
} else {
// Code 'W':
//
$hn_week = Date_Calc::weekOfMonthAbsolute($this->day,
$this->month,
$this->year);
$hs_numberformat = substr($ps_format, $i + 1, 4);
$hs_week = $this->_formatNumber($hn_week,
$hs_numberformat,
1,
$hb_nopad,
true,
$ps_locale);
if (Pear::isError($hs_week))
return $hs_week;
$ret .= $hs_week;
$i += 1 + strlen($hs_numberformat);
}
break;
case "y":
case "Y":
// Check for 'YEAR' first:
//
if (strtoupper(substr($ps_format, $i, 4)) == "YEAR") {
switch (substr($ps_format, $i, 2)) {
case "YE":
$hs_spformat = "SP";
break;
case "Ye":
$hs_spformat = "Sp";
break;
default:
$hs_spformat = "sp";
}
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,
$hs_numberformat,
0,
true,
$hb_nosign,
$ps_locale);
if (Pear::isError($hs_year))
return $hs_year;
$ret .= $hs_year;
} else {
// 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,
$hs_numberformat,
0,
true,
$hb_nosign,
$ps_locale);
if (Pear::isError($hs_century))
return $hs_century;
$ret .= $hs_century . " ";
$hs_numberformat = $hs_spformat;
// Discard sign; padding irrelevant:
//
$hs_year = $this->_formatNumber($this->year,
$hs_numberformat,
2,
false,
true,
$ps_locale);
if (Pear::isError($hs_year))
return $hs_year;
$ret .= $hs_year;
}
$i += 4;
} else {
// Code Y(YYY...):
//
$hn_codelen = 1;
while (strtoupper(substr($ps_format,
$i + $hn_codelen,
1)) == "Y")
++$hn_codelen;
$hs_thousandsep = null;
$hn_thousandseps = 0;
if ($hn_codelen <= 3) {
while (preg_match('/([,.·\' ])YYY/i',
substr($ps_format,
$i + $hn_codelen,
4),
$ha_matches)) {
$hn_codelen += 4;
$hs_thousandsep = $ha_matches[1];
++$hn_thousandseps;
}
}
// Check next code is not 'YEAR'
//
if ($hn_codelen > 1 &&
strtoupper(substr($ps_format,
$i + $hn_codelen - 1,
4)) == "YEAR")
--$hn_codelen;
$hs_numberformat = substr($ps_format, $i + $hn_codelen, 4);
$hs_year = $this->_formatNumber($this->year,
$hs_numberformat,
$hn_codelen -
$hn_thousandseps,
$hb_nopad,
$hb_nosign,
$ps_locale,<