Файл: library/XenForo/Locale.php
Строк: 663
<?php
/**
* Helper methods to generate locale sensitive output.
*
* @package XenForo_Core
*/
class XenForo_Locale
{
/**
* Default language to use for locale-specific output (if not overridden).
*
* @var array
*/
protected static $_language = array();
/**
* Default time zone to use for locale-specific output (if not overridden)
*
* @var DateTimeZone|null
*/
protected static $_timeZone = null;
/**
* A cached DateTime object. This will be set only if setTimestamp exists on it
* (PHP 5.3 and newer). This serves as an optimization to avoid object creation
* and date parsing overhead.
*
* @var DateTime|null
*/
protected static $_dateObj = null;
protected static $_dayStartTimestamps = null;
/**
* Translate a numeric day of the week to representation that will be used in phrases.
*
* @var array
*/
protected static $_dowTranslation = array(
0 => 'sunday',
1 => 'monday',
2 => 'tuesday',
3 => 'wednesday',
4 => 'thursday',
5 => 'friday',
6 => 'saturday'
);
/**
* Private constructor. Use this class statically.
*/
private function __construct()
{
}
/**
* Set the default language information and time zone (optionally).
*
* @param array $language
* @param string|DateTimeZone|null $timeZoneString String time zone (eg, Europe/London)
*/
public static function setDefaultLanguage(array $language, $timeZoneString = null)
{
self::$_language = $language;
if ($timeZoneString)
{
self::setDefaultTimeZone($timeZoneString);
}
}
/**
* Sets the default time zone.
*
* @param string|DateTimeZone $timeZoneString String time zone (eg, Europe/London);
*/
public static function setDefaultTimeZone($timeZoneString)
{
if (!$timeZoneString)
{
return;
}
if ($timeZoneString instanceof DateTimeZone)
{
self::$_timeZone = $timeZoneString;
}
else
{
try
{
self::$_timeZone = new DateTimeZone($timeZoneString);
}
catch (Exception $e) { return; }
}
if (method_exists('DateTime', 'setTimestamp'))
{
self::$_dateObj = new DateTime('', self::$_timeZone);
}
}
/**
* Gets the default time zone.
*
* @return DateTimeZone|null
*/
public static function getDefaultTimeZone()
{
return self::$_timeZone;
}
/**
* Gets the current timezone offset from UTC in seconds
*
* @return integer
*/
public static function getTimeZoneOffset()
{
return self::_getDateObject()->getOffset();
}
/**
* Gets a date object that fits the requirements (correct timestamp and time zone).
*
* @param integer|DateTime|null $timestamp Unix timestamp or a DateTime object that's already configured
* @param string|DateTimeZone|null $timeZoneString String time zone. If null, uses default (and can use date object optimization if available)
*
* @return DateTime
*/
protected static function _getDateObject($timestamp = null, $timeZoneString = null)
{
if ($timestamp instanceof DateTime)
{
return $timestamp;
}
else if ($timestamp === null)
{
$timestamp = XenForo_Application::$time;
}
if ($timeZoneString)
{
if ($timeZoneString instanceof DateTimeZone)
{
$timeZone = $timeZoneString;
}
else
{
$timeZone = new DateTimeZone($timeZoneString);
}
}
else
{
if (!self::$_timeZone)
{
self::setDefaultTimeZone('UTC');
}
if (self::$_dateObj)
{
self::$_dateObj->setTimestamp($timestamp);
return self::$_dateObj;
}
$timeZone = self::$_timeZone;
}
$dt = new DateTime('@' . $timestamp);
$dt->setTimezone($timeZone);
return $dt;
}
/**
* Gets the formatted date/time using the given format. String-based
* identifiers (months, days of week) need to be passed in.
*
* @param DateTime $date DateTime object, with correct time set
* @param string $format Format to display as; supports a subset of the formats from the built-in date() function
* @param array $phrases List of phrases that will be used to replace string-based identifiers
*
* @return string Formatted date
*/
public static function getFormattedDateInternal(DateTime $date, $format, array $phrases)
{
$dateParts = explode('|', $date->format('j|w|W|n|Y|G|i|s|S'));
list($dayOfMonth, $dayOfWeek, $weekOfYear, $month, $year, $hour, $minute, $second, $ordinalSuffix) = $dateParts;
$output = '';
$formatters = str_split($format);
$formatterCount = count($formatters);
for ($i = 0; $i < $formatterCount; $i++)
{
$identifier = $formatters[$i];
switch ($identifier)
{
// day of month
case 'd': $output .= sprintf('%02d', $dayOfMonth); continue;
case 'j': $output .= $dayOfMonth; continue;
// day of week
case 'D': $output .= $phrases['day_' . self::$_dowTranslation[$dayOfWeek] . '_short']; continue;
case 'l': $output .= $phrases['day_' . self::$_dowTranslation[$dayOfWeek]]; continue;
// week
case 'W': $output .= $weekOfYear; continue;
// month
case 'm': $output .= sprintf('%02d', $month); continue;
case 'n': $output .= $month; continue;
case 'F': $output .= $phrases['month_' . $month]; continue;
case 'M': $output .= $phrases['month_' . $month . '_short']; continue;
// year
case 'Y': $output .= $year; continue;
case 'y': $output .= substr($year, 2); continue;
// am/pm
case 'a': $output .= $phrases[($hour >= 12 ? 'time_pm_lower' : 'time_am_lower')]; continue;
case 'A': $output .= $phrases[($hour >= 12 ? 'time_pm_upper' : 'time_am_upper')]; continue;
// hour
case 'H': $output .= sprintf('%02d', $hour); continue;
case 'h': $output .= sprintf('%02d', $hour % 12 ? $hour % 12 : 12); continue;
case 'G': $output .= $hour; continue;
case 'g': $output .= ($hour % 12 ? $hour % 12 : 12); continue;
// minute
case 'i': $output .= $minute; continue;
// second
case 's': $output .= $second; continue;
// ordinal
case 'S': $output .= $ordinalSuffix; continue;
case '\':
$i++;
if ($i < $formatterCount)
{
$output .= $formatters[$i];
}
continue;
// anything else is printed
default: $output .= $identifier;
}
}
return $output;
}
/**
* Gets a date formatted in the requested format.
*
* @param integer|DateTime $timestamp Unix timestamp or a DateTime object that's already configured
* @param string $format Format to display as; compatible with sub-set of date() options
* @param array|null $language Language (if overriding default)
* @param string|DateTimeZone|null $timeZoneString Time zone user is in (if overriding default)
*
* @return string
*/
public static function getFormattedDate($timestamp, $format, array $language = null, $timeZoneString = null)
{
if (!$language)
{
$language = self::$_language;
}
$date = self::_getDateObject($timestamp, $timeZoneString);
if (!$language)
{
return $date->format($format);
}
else
{
return self::getFormattedDateInternal($date, $format, $language['phrase_cache']);
}
}
/**
* Formats the given timestamp as a date.
*
* @param integer|DateTime $timestamp Unix timestamp or a DateTime object that's already configured
* @param string $format Format that maps to a known type. Uses default if specified. (Currently ignored.)
* @param array|null $language Info about language to override default
* @param string|DateTimeZone|null $timeZoneString String time zone to override default
*
* @return string
*/
public static function date($timestamp, $format = null, array $language = null, $timeZoneString = null)
{
if ($timestamp === null || $timestamp === false)
{
return '';
}
if (!$language)
{
$language = self::$_language;
}
$date = self::_getDateObject($timestamp, $timeZoneString);
$rtlPrefix = self::getRtlDateTimeMarker($language);
switch ($format)
{
case 'year':
$dateFormat = 'Y';
break;
case 'monthDay':
$dateFormat = 'F j';
break;
case 'picker':
$dateFormat = 'Y-m-d';
$rtlPrefix = '';
break;
case 'absolute':
case 'relative':
case '':
$dateFormat = ($language ? $language['date_format'] : 'M j, Y');
break;
default:
$dateFormat = $format;
$rtlPrefix = '';
}
if (!$language)
{
return $rtlPrefix . $date->format($dateFormat);
}
else
{
if (!$format || $format == 'relative')
{
$relativeDate = self::getRelativeDate(
$date, $language['phrase_cache']
);
if ($relativeDate !== false)
{
return $rtlPrefix . $relativeDate;
}
}
return $rtlPrefix . self::getFormattedDateInternal($date, $dateFormat, $language['phrase_cache']);
}
}
/**
* Formats the given timestamp as a time.
*
* @param integer|DateTime $timestamp Unix timestamp or a DateTime object that's already configured
* @param string $format Format that maps to a known type. Uses default if specified. (Currently ignored.)
* @param array|null $language Info about language to override default
* @param string|DateTimeZone|null $timeZoneString String time zone to override default
*
* @return string
*/
public static function time($timestamp, $format = null, array $language = null, $timeZoneString = null)
{
if ($timestamp === null || $timestamp === false)
{
return '';
}
if (!$language)
{
$language = self::$_language;
}
$date = self::_getDateObject($timestamp, $timeZoneString);
$rtlPrefix = self::getRtlDateTimeMarker($language);
if (!$format)
{
$format = 'absolute';
}
switch ($format)
{
case 'absolute':
$timeFormat = ($language ? $language['time_format'] : 'g:i A');
break;
default:
$timeFormat = $format;
$rtlPrefix = '';
}
if (!$language)
{
return $rtlPrefix . $date->format($timeFormat);
}
else
{
return $rtlPrefix . self::getFormattedDateInternal($date, $timeFormat, $language['phrase_cache']);
}
}
/**
* Formats the given timestamp as a date and a time.
*
* @param integer|DateTime $timestamp Unix timestamp or a DateTime object that's already configured
* @param string $format Format that maps to a known type. Uses default if specified.
* @param array|null $language Info about language to override default
* @param string|DateTimeZone|null $timeZoneString String time zone to override default
*
* @return string|array If format 'separate' is specified, returns [dateString, date, time]
*/
public static function dateTime($timestamp, $format = null, array $language = null, $timeZoneString = null)
{
if (!$language)
{
$language = self::$_language;
}
$date = self::_getDateObject($timestamp, $timeZoneString);
$rtlPrefix = self::getRtlDateTimeMarker($language);
if (!$language)
{
return $rtlPrefix . $date->format('M j, Y g:i A');
}
else
{
if (!$format || $format == 'relative')
{
$relativeDate = self::getRelativeDateTime(
$date, $language['time_format'], $language['phrase_cache']
);
if ($relativeDate !== false)
{
return $rtlPrefix . $relativeDate;
}
else
{
return $rtlPrefix . self::getFormattedDateInternal($date, $language['date_format'], $language['phrase_cache']);
}
}
switch ($format)
{
case 'absolute':
case 'separate':
default:
$dateTimeFormat = $language['date_format'] . '|' . $language['time_format'];
$formatPhrase = 'date_x_at_time_y';
}
$parts = explode('|', self::getFormattedDateInternal($date, $dateTimeFormat, $language['phrase_cache']));
$dateFind = array(
'{date}' => $parts[0],
'{time}' => $parts[1]
);
$dateString = strtr($language['phrase_cache'][$formatPhrase], $dateFind);
if ($format == 'separate')
{
$dayStarts = self::getDayStartTimestamps();
return array(
'string' => $dateString,
'date' => $parts[0],
'time' => $parts[1],
'relative' => ($timestamp > $dayStarts['week'])
);
}
else
{
return $rtlPrefix . $dateString;
}
}
}
/**
* Returns a string representing the given date and time as a relative period before now, in certain circumstances
*
* @param DateTime $date
* @param string $timeFormat
* @param array $phrases
*
* @return string|false
*/
public static function getRelativeDateTime(DateTime $date, $timeFormat, array $phrases)
{
$timestamp = $date->format('U');
$interval = XenForo_Application::$time - $timestamp;
if ($interval < 0)
{
//TODO: handle future dates - Tomorrow, later today...
return false;
}
if ($interval <= 60)
{
return $phrases['a_moment_ago'];
}
if ($interval < 120)
{
return $phrases['one_minute_ago'];
}
if ($interval < 3600)
{
return str_replace(
'{minutes}', floor($interval / 60), $phrases['x_minutes_ago']
);
}
$dayStartTimestamps = self::getDayStartTimestamps();
if ($timestamp >= $dayStartTimestamps['today'])
{
return str_replace(
'{time}', self::getFormattedDateInternal($date, $timeFormat, $phrases), $phrases['today_at_x']
);
}
if ($timestamp >= $dayStartTimestamps['yesterday'])
{
return str_replace(
'{time}', self::getFormattedDateInternal($date, $timeFormat, $phrases), $phrases['yesterday_at_x']
);
}
if ($timestamp >= $dayStartTimestamps['week'])
{
$dateReplace = explode('|', self::getFormattedDateInternal($date, 'l|' . $timeFormat, $phrases));
$dateFind = array(
'{day}' => $dateReplace[0],
'{time}' => $dateReplace[1]
);
return strtr($phrases['day_x_at_time_y'], $dateFind);
}
return false;
}
/**
* Returns a string representing the given date as today, yesterday, dayname (within this past week)
*
* @param DateTime $date
* @param string $timeFormat
* @param array $phrases
*
* @return string|false
*/
public static function getRelativeDate(DateTime $date, array $phrases)
{
$timestamp = $date->format('U');
if ($timestamp > XenForo_Application::$time)
{
// TODO: date in the future... Tomorrow, Later today
return false;
}
$dayStartTimestamps = self::getDayStartTimestamps();
if ($timestamp >= $dayStartTimestamps['today'])
{
return $phrases['today'];
}
if ($timestamp >= $dayStartTimestamps['yesterday'])
{
return $phrases['yesterday'];
}
if ($timestamp >= $dayStartTimestamps['week'])
{
return self::getFormattedDateInternal($date, 'l', $phrases);
}
return false;
}
/**
* Fetches timestamps for the start of today, yesterday or a week ago
*
* @return array [now => long, today => long, todayDow => long, yesterday => long, week => long]
*/
public static function getDayStartTimestamps()
{
if (!self::$_dayStartTimestamps)
{
$date = new DateTime('@' . XenForo_Application::$time);
$date->setTimezone(self::$_timeZone ? self::$_timeZone : new DateTimeZone('UTC'));
$date->setTime(0, 0, 0);
list($todayStamp, $todayDow) = explode('|', $date->format('U|w'));
$date->modify('-1 day');
$yesterdayStamp = $date->format('U');
$date->modify('-5 days');
$weekStamp = $date->format('U');
self::$_dayStartTimestamps = array(
'now' => XenForo_Application::$time,
'today' => $todayStamp,
'todayDow' => $todayDow,
'yesterday' => $yesterdayStamp,
'week' => $weekStamp
);
}
return self::$_dayStartTimestamps;
}
public static function getRtlDateTimeMarker(array $language = null)
{
if (!$language)
{
$language = self::$_language;
}
if ($language && !empty($language['text_direction']) && $language['text_direction'] == 'RTL')
{
return "xE2x80x8F"; // right to left marker
}
else
{
return '';
}
}
/**
* Formats the given number for a language/locale. Also used for file size formatting.
*
* @param float|integer $number Number to format
* @param integer|string $precision Number of places to show after decimal point or word "size" for file size
* @param array|null $language Language to override default
*
* @return string Formatted number
*/
public static function numberFormat($number, $precision = 0, array $language = null)
{
if (!$language)
{
$language = self::$_language;
}
if (!$language)
{
$decimalSep = '.';
$thousandsSep = ',';
}
else
{
$decimalSep = $language['decimal_point'];
$thousandsSep = $language['thousands_separator'];
}
if ($precision === 'size')
{
if ($number >= 1099511627776) // 1 TB
{
$number = number_format($number / 1099511627776, 1, $decimalSep, $thousandsSep);
$unit = ' TB';
$phrase = 'x_tb';
}
else if ($number >= 1073741824) // 1 GB
{
$number = number_format($number / 1073741824, 1, $decimalSep, $thousandsSep);
$unit = ' GB';
$phrase = 'x_gb';
}
else if ($number >= 1048576) // 1 MB
{
$number = number_format($number / 1048576, 1, $decimalSep, $thousandsSep);
$unit = ' MB';
$phrase = 'x_mb';
}
else if ($number >= 1024) // 1 KB
{
$number = number_format($number / 1024, 1, $decimalSep, $thousandsSep);
$unit = ' KB';
$phrase = 'x_kb';
}
else
{
$number = number_format($number, 1, $decimalSep, $thousandsSep);
$unit = ' bytes';
$phrase = 'x_bytes';
}
// return $number, not $number.0 when the decimal is 0.
if (substr($number, -2) == $decimalSep . '0')
{
$number = substr($number, 0, -2);
}
if (!$language || !isset($language['phrase_cache'][$phrase]))
{
return $number . $unit;
}
else
{
return str_replace('{size}', $number, $language['phrase_cache'][$phrase]);
}
}
else
{
return number_format($number, $precision, $decimalSep, $thousandsSep);
}
}
}