Beispiel #1
0
 /**
  * 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
  * '89'.
  *
  * 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>/</code>
  *  <code>,</code>
  *  <code>.</code>
  *  <code>;</code>
  *  <code>:</code>
  *  <code> </code>
  *  <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>A.D.</code> 
  *  <code>AM</code> Meridian indicator with or without full stops
  *  <code>A.M.</code> 
  *  <code>BC</code> BC indicator with or without full stops
  *  <code>B.C.</code> 
  *  <code>BCE</code> BCE indicator with or without full stops
  *  <code>B.C.E.</code> 
  *  <code>CC</code> Century, i.e. the year divided by 100, discarding the
  *                 remainder; 'S' prefixes negative years with a minus sign
  *  <code>SCC</code> 
  *  <code>CE</code> CE indicator with or without full stops
  *  <code>C.E.</code> 
  *  <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>SIYYY</code> 
  *  <code>IYY</code> Last 3, 2, or 1 digit(s) of ISO year
  *  <code>IY</code> 
  *  <code>I</code> 
  *  <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>P.M.</code> 
  *  <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
  *                 with leading spaces.
  *  <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
  *                  time, e.g. 'BST'.
  *                  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
  *                  not 'Plus One'.
  *                  'TZH:TZM' will produce, for example, '+05:30'.  (Also
  *                  see 'TZM' format code)
  *  <code>STZH</code> 
  *  <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
  *                  'TZH' format element)
  *  <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 'TZR').
  *  <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>STZS</code>
  *  <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
  *                  each time zone.
  *  <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
  *                 contains 1st January
  *  <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
  *                 days)
  *  <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
  *                   HUNDRED TWENTY-THREE'
  *  <code>SYEAR</code> 
  *  <code>YYYY</code> 4-digit year; 'S' prefixes negative years with a minus
  *                   sign
  *  <code>SYYYY</code> 
  *  <code>YYY</code> Last 3, 2, or 1 digit(s) of year
  *  <code>YY</code> 
  *  <code>Y</code> 
  *  <code>Y,YYY</code> Year with thousands-separator in this position; five
  *                    possible separators
  *  <code>Y.YYY</code> 
  *  <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
  *                    particular)
  *  <code>Y'YYY</code> 
  *  <code>Y YYY</code> 
  *
  * In addition the following codes can be used in combination with other
  * codes;
  *  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'
  *                   in any order)
  *  <code>THSP</code> 
  *
  * 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
  * @access   public
  * @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', $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, $hs_thousandsep);
                     if (Pear::isError($hs_year)) {
                         return $hs_year;
                     }
                     $ret .= $hs_year;
                     $i += $hn_codelen + strlen($hs_numberformat);
                 }
                 break;
             default:
                 $ret .= $hs_char;
                 ++$i;
                 break;
         }
     }
     return $ret;
 }