* @version $Id: sfDateFormat.class.php 4340 2007-06-23 06:47:05Z fabien $ * @package symfony * @subpackage i18n */ /** * Gets the encoding utilities */ require_once(dirname(__FILE__).'/util.php'); /** * sfDateFormat class. * * The sfDateFormat class allows you to format dates and times with * predefined styles in a locale-sensitive manner. Formatting times * with the sfDateFormat class is similar to formatting dates. * * Formatting dates with the sfDateFormat class is a two-step process. * First, you create a formatter with the getDateInstance method. * Second, you invoke the format method, which returns a string containing * the formatted date. * * DateTime values are formatted using standard or custom patterns stored * in the properties of a DateTimeFormatInfo. * * @author Xiang Wei Zhuo * @version v1.0, last update on Sat Dec 04 14:10:49 EST 2004 * @package System.I18N.core */ class sfDateFormat { /** * A list of tokens and their function call. * @var array */ protected $tokens = array( 'G'=>'Era', 'y'=>'year', 'M'=>'mon', 'd'=>'mday', 'h'=>'Hour12', 'H'=>'hours', 'm'=>'minutes', 's'=>'seconds', 'E'=>'wday', 'D'=>'yday', 'F'=>'DayInMonth', 'w'=>'WeekInYear', 'W'=>'WeekInMonth', 'a'=>'AMPM', 'k'=>'HourInDay', 'K'=>'HourInAMPM', 'z'=>'TimeZone' ); /** * A list of methods, to be used by the token function calls. * @var array */ protected $methods = array(); /** * The sfDateTimeFormatInfo, containing culture specific patterns and names. * @var sfDateTimeFormatInfo */ protected $formatInfo; /** * Initializes a new sfDateFormat. * * @param mixed either, null, a sfCultureInfo instance, a DateTimeFormatInfo instance, or a locale. * @return sfDateFormat instance */ function __construct($formatInfo = null) { if (is_null($formatInfo)) { $this->formatInfo = sfDateTimeFormatInfo::getInvariantInfo(); } else if ($formatInfo instanceof sfCultureInfo) { $this->formatInfo = $formatInfo->DateTimeFormat; } else if ($formatInfo instanceof sfDateTimeFormatInfo) { $this->formatInfo = $formatInfo; } else { $this->formatInfo = sfDateTimeFormatInfo::getInstance($formatInfo); } $this->methods = get_class_methods($this); } /** * Guesses a date without calling strtotime. * * @author Olivier Verdier * @param mixed the time as integer or string in strtotime format. * @param string the input pattern; default is sql date or timestamp * @return array same array as the getdate function */ public function getDate($time, $pattern = null) { if (is_null($time)) { return null; } // if the type is not a php timestamp $isString = (string) $time !== (string) (int) $time; if ($isString) { if (!$pattern) { if (strlen($time) == 10) { $pattern = 'i'; } else // otherwise, default: { $pattern = 'I'; } } $pattern = $this->getPattern($pattern); $tokens = $this->getTokens($pattern); $pregPattern = ''; $matchNames = array(); foreach ($tokens as $token) { if ($matchName = $this->getFunctionName($token)) { $pregPattern .= '(\d+)'; $matchNames[] = $matchName; } else { $pregPattern .= '[^\d]+'; } } preg_match('@'.$pregPattern.'@', $time, $matches); array_shift($matches); if (count($matchNames) == count($matches)) { $date = array_combine($matchNames, $matches); // guess the date if input with two digits if (strlen($date['year']) == 2) { $date['year'] = date('Y', mktime(0, 0, 0, 1, 1, $date['year'])); } $date = array_map('intval', $date); } } // the last attempt has failed we fall back on the default method if (!isset($date)) { if ($isString) { $numericalTime = @strtotime($time); if ($numericalTime === false) { throw new sfException(sprintf('Impossible to parse date "%s" with format "%s".', $time, $pattern)); } } else { $numericalTime = $time; } $date = @getdate($numericalTime); } // we set default values for the time foreach (array('hours', 'minutes', 'seconds') as $timeDiv) { if (!isset($date[$timeDiv])) { $date[$timeDiv] = 0; } } return $date; } /** * Formats a date according to the pattern. * * @param mixed the time as integer or string in strtotime format. * @return string formatted date time. */ public function format($time, $pattern = 'F', $inputPattern = null, $charset = 'UTF-8') { $date = $this->getDate($time, $inputPattern); if (is_null($pattern)) { $pattern = 'F'; } $pattern = $this->getPattern($pattern); $tokens = $this->getTokens($pattern); for ($i = 0, $max = count($tokens); $i < $max; $i++) { $pattern = $tokens[$i]; if ($pattern{0} == "'" && $pattern{strlen($pattern) - 1} == "'") { $tokens[$i] = str_replace('``````', '\'', preg_replace('/(^\')|(\'$)/', '', $pattern)); } else if ($pattern == '``````') { $tokens[$i] = '\''; } else { $function = ucfirst($this->getFunctionName($pattern)); if ($function != null) { $fName = 'get'.$function; if (in_array($fName, $this->methods)) { $tokens[$i] = $this->$fName($date, $pattern); } else { throw new sfException(sprintf('Function %s not found.', $function)); } } } } return I18N_toEncoding(implode('', $tokens), $charset); } /** * For a particular token, get the corresponding function to call. * * @param string token * @return mixed the function if good token, null otherwise. */ protected function getFunctionName($token) { if (isset($this->tokens[$token{0}])) { return $this->tokens[$token{0}]; } } /** * Gets the pattern from DateTimeFormatInfo or some predefined patterns. * If the $pattern parameter is an array of 2 element, it will assume * that the first element is the date, and second the time * and try to find an appropriate pattern and apply * DateTimeFormatInfo::formatDateTime * See the tutorial documentation for futher details on the patterns. * * @param mixed a pattern. * @return string a pattern. * @see DateTimeFormatInfo::formatDateTime() */ public function getPattern($pattern) { if (is_array($pattern) && count($pattern) == 2) { return $this->formatInfo->formatDateTime($this->getPattern($pattern[0]), $this->getPattern($pattern[1])); } switch ($pattern) { case 'd': return $this->formatInfo->ShortDatePattern; break; case 'D': return $this->formatInfo->LongDatePattern; break; case 'p': return $this->formatInfo->MediumDatePattern; break; case 'P': return $this->formatInfo->FullDatePattern; break; case 't': return $this->formatInfo->ShortTimePattern; break; case 'T': return $this->formatInfo->LongTimePattern; break; case 'q': return $this->formatInfo->MediumTimePattern; break; case 'Q': return $this->formatInfo->FullTimePattern; break; case 'f': return $this->formatInfo->formatDateTime($this->formatInfo->LongDatePattern, $this->formatInfo->ShortTimePattern); break; case 'F': return $this->formatInfo->formatDateTime($this->formatInfo->LongDatePattern, $this->formatInfo->LongTimePattern); break; case 'g': return $this->formatInfo->formatDateTime($this->formatInfo->ShortDatePattern, $this->formatInfo->ShortTimePattern); break; case 'G': return $this->formatInfo->formatDateTime($this->formatInfo->ShortDatePattern, $this->formatInfo->LongTimePattern); break; case 'i': return 'yyyy-MM-dd'; break; case 'I': return 'yyyy-MM-dd HH:mm:ss'; break; case 'M': case 'm': return 'MMMM dd'; break; case 'R': case 'r': return 'EEE, dd MMM yyyy HH:mm:ss'; break; case 's': return 'yyyy-MM-ddTHH:mm:ss'; break; case 'u': return 'yyyy-MM-dd HH:mm:ss z'; break; case 'U': return 'EEEE dd MMMM yyyy HH:mm:ss'; break; case 'Y': case 'y': return 'yyyy MMMM'; break; default : return $pattern; } } /** * Returns an easy to parse input pattern * yy is replaced by yyyy and h by H * * @param string pattern. * @return string input pattern */ public function getInputPattern($pattern) { $pattern = $this->getPattern($pattern); $pattern = strtr($pattern, array('yyyy' => 'Y', 'h'=>'H', 'z'=>'', 'a'=>'')); $pattern = strtr($pattern, array('yy'=>'yyyy', 'Y'=>'yyyy')); return trim($pattern); } /** * Tokenizes the pattern. The tokens are delimited by group of * similar characters, e.g. 'aabb' will form 2 tokens of 'aa' and 'bb'. * Any substrings, starting and ending with a single quote (') * will be treated as a single token. * * @param string pattern. * @return array string tokens in an array. */ protected function getTokens($pattern) { $char = null; $tokens = array(); $token = null; $text = false; for ($i = 0, $max = strlen($pattern); $i < $max; $i++) { if ($char == null || $pattern{$i} == $char || $text) { $token .= $pattern{$i}; } else { $tokens[] = str_replace("''", "'", $token); $token = $pattern{$i}; } if ($pattern{$i} == "'" && $text == false) { $text = true; } else if ($text && $pattern{$i} == "'" && $char == "'") { $text = true; } else if ($text && $char != "'" && $pattern{$i} == "'") { $text = false; } $char = $pattern{$i}; } $tokens[] = $token; return $tokens; } // makes a unix date from our incomplete $date array protected function getUnixDate($date) { return getdate(mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year'])); } /** * Gets the year. * "yy" will return the last two digits of year. * "yyyy" will return the full integer year. * * @param array getdate format. * @param string a pattern. * @return string year */ protected function getYear($date, $pattern = 'yyyy') { $year = $date['year']; switch ($pattern) { case 'yy': return substr($year, 2); case 'yyyy': return $year; default: throw new sfException('The pattern for year is either "yy" or "yyyy".'); } } /** * Gets the month. * "M" will return integer 1 through 12 * "MM" will return the narrow month name, e.g. "J" * "MMM" will return the abrreviated month name, e.g. "Jan" * "MMMM" will return the month name, e.g. "January" * * @param array getdate format. * @param string a pattern. * @return string month name */ protected function getMon($date, $pattern = 'M') { $month = $date['mon']; switch ($pattern) { case 'M': return $month; case 'MM': return str_pad($month, 2, '0', STR_PAD_LEFT); case 'MMM': return $this->formatInfo->AbbreviatedMonthNames[$month - 1]; break; case 'MMMM': return $this->formatInfo->MonthNames[$month - 1]; default: throw new sfException('The pattern for month is "M", "MM", "MMM", or "MMMM".'); } } /** * Gets the day of the week. * "E" will return integer 0 (for Sunday) through 6 (for Saturday). * "EE" will return the narrow day of the week, e.g. "M" * "EEE" will return the abrreviated day of the week, e.g. "Mon" * "EEEE" will return the day of the week, e.g. "Monday" * * @param array getdate format. * @param string a pattern. * @return string day of the week. */ protected function getWday($date, $pattern = 'EEEE') { // if the $date comes from our home-made get date if (!isset($date['wday'])) { $date = $this->getUnixDate($date); } $day = $date['wday']; switch ($pattern) { case 'E': return $day; break; case 'EE': return $this->formatInfo->NarrowDayNames[$day]; case 'EEE': return $this->formatInfo->AbbreviatedDayNames[$day]; break; case 'EEEE': return $this->formatInfo->DayNames[$day]; break; default: throw new sfException('The pattern for day of the week is "E", "EE", "EEE", or "EEEE".'); } } /** * Gets the day of the month. * "d" for non-padding, "dd" will always return 2 characters. * * @param array getdate format. * @param string a pattern. * @return string day of the month */ protected function getMday($date, $pattern = 'd') { $day = $date['mday']; switch ($pattern) { case 'd': return $day; case 'dd': return str_pad($day, 2, '0', STR_PAD_LEFT); case 'dddd': return $this->getWday($date); default: throw new sfException('The pattern for day of the month is "d", "dd" or "dddd".'); } } /** * Gets the era. i.e. in gregorian, year > 0 is AD, else BC. * * @todo How to support multiple Eras?, e.g. Japanese. * @param array getdate format. * @param string a pattern. * @return string era */ protected function getEra($date, $pattern = 'G') { if ($pattern != 'G') { throw new sfException('The pattern for era is "G".'); } return $this->formatInfo->getEra($date['year'] > 0 ? 1 : 0); } /** * Gets the hours in 24 hour format, i.e. [0-23]. * "H" for non-padding, "HH" will always return 2 characters. * * @param array getdate format. * @param string a pattern. * @return string hours in 24 hour format. */ protected function getHours($date, $pattern = 'H') { $hour = $date['hours']; switch ($pattern) { case 'H': return $hour; case 'HH': return str_pad($hour, 2, '0', STR_PAD_LEFT); default: throw new sfException('The pattern for 24 hour format is "H" or "HH".'); } } /** * Get the AM/PM designator, 12 noon is PM, 12 midnight is AM. * * @param array getdate format. * @param string a pattern. * @return string AM or PM designator */ protected function getAMPM($date, $pattern = 'a') { if ($pattern != 'a') { throw new sfException('The pattern for AM/PM marker is "a".'); } return $this->formatInfo->AMPMMarkers[intval($date['hours'] / 12)]; } /** * Gets the hours in 12 hour format. * "h" for non-padding, "hh" will always return 2 characters. * * @param array getdate format. * @param string a pattern. * @return string hours in 12 hour format. */ protected function getHour12($date, $pattern = 'h') { $hour = $date['hours']; $hour = ($hour == 12 | $hour == 0) ? 12 : $hour % 12; switch ($pattern) { case 'h': return $hour; case 'hh': return str_pad($hour, 2, '0', STR_PAD_LEFT); default: throw new sfException('The pattern for 24 hour format is "H" or "HH".'); } } /** * Gets the minutes. * "m" for non-padding, "mm" will always return 2 characters. * * @param array getdate format. * @param string a pattern. * @return string minutes. */ protected function getMinutes($date, $pattern = 'm') { $minutes = $date['minutes']; switch ($pattern) { case 'm': return $minutes; case 'mm': return str_pad($minutes, 2, '0', STR_PAD_LEFT); default: throw new sfException('The pattern for minutes is "m" or "mm".'); } } /** * Gets the seconds. * "s" for non-padding, "ss" will always return 2 characters. * * @param array getdate format. * @param string a pattern. * @return string seconds */ protected function getSeconds($date, $pattern = 's') { $seconds = $date['seconds']; switch ($pattern) { case 's': return $seconds; case 'ss': return str_pad($seconds, 2, '0', STR_PAD_LEFT); default: throw new sfException('The pattern for seconds is "s" or "ss".'); } } /** * Gets the timezone from the server machine. * * @todo How to get the timezone for a different region? * @param array getdate format. * @param string a pattern. * @return string time zone */ protected function getTimeZone($date, $pattern = 'z') { if ($pattern != 'z') { throw new sfException('The pattern for time zone is "z".'); } return @date('T', @mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year'])); } /** * Gets the day in the year, e.g. [1-366] * * @param array getdate format. * @param string a pattern. * @return int hours in AM/PM format. */ protected function getYday($date, $pattern = 'D') { if ($pattern != 'D') { throw new sfException('The pattern for day in year is "D".'); } return $date['yday']; } /** * Gets day in the month. * * @param array getdate format. * @param string a pattern. * @return int day in month */ protected function getDayInMonth($date, $pattern = 'FF') { switch ($pattern) { case 'F': return @date('j', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])); break; case 'FF': return @date('d', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])); break; default: throw new sfException('The pattern for day in month is "F" or "FF".'); } } /** * Gets the week in the year. * * @param array getdate format. * @param string a pattern. * @return int week in year */ protected function getWeekInYear($date, $pattern = 'w') { if ($pattern != 'w') { throw new sfException('The pattern for week in year is "w".'); } return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])); } /** * Gets week in the month. * * @param array getdate format. * @return int week in month */ protected function getWeekInMonth($date, $pattern = 'W') { if ($pattern != 'W') { throw new sfException('The pattern for week in month is "W".'); } return @date('W', @mktime(0, 0, 0, $date['mon'], $date['mday'], $date['year'])) - date('W', mktime(0, 0, 0, $date['mon'], 1, $date['year'])); } /** * Gets the hours [1-24]. * * @param array getdate format. * @param string a pattern. * @return int hours [1-24] */ protected function getHourInDay($date, $pattern = 'k') { if ($pattern != 'k') { throw new sfException('The pattern for hour in day is "k".'); } return $date['hours'] + 1; } /** * Gets the hours in AM/PM format, e.g [1-12] * * @param array getdate format. * @param string a pattern. * @return int hours in AM/PM format. */ protected function getHourInAMPM($date, $pattern = 'K') { if ($pattern != 'K') { throw new sfException('The pattern for hour in AM/PM is "K".'); } return ($date['hours'] + 1) % 12; } }