array( '%A', 'DD', '\w+'), // Name of day, long Monday 'D' => array( '%a', 'D', '\w+'), // Name of day, short Mon 'F' => array( '%B', 'MM', '\w+'), // Name of month, long April 'M' => array( '%b', 'M', '\w+'), // Name of month, short Apr 'j' => array( '%-d', 'd', '\d{1,2}'), // Day without leading zeros 1 'd' => array( '%d', 'dd', '\d{2}'), // Day with leading zeros 01 'n' => array( '%-m', 'm', '\d{1,2}'), // Month without leading zeros 4 'm' => array( '%m', 'mm', '\d{2}'), // Month with leading zeros 04 'y' => array( '%y', 'y', '\d{2}'), // Year 2 character 12 'Y' => array( '%Y', 'yy', '\d{4}'), // Year 4 character 2012 'N' => array( '%u', '', '[1-7]'), // Day of the week (1-7) 1 'w' => array( '%w', '', '[0-6]'), // Zero-based day of week (0-6) 0 'S' => array( '', '', '\w{2}'), // Day ordinal suffix st, nd, rd, th 'z' => array( '%-j', 'o', '\d{1,3}'), // Day of the year (0-365) 123 'W' => array( '%W', '', '\d{1,2}'), // Week # of the year 42 't' => array( '', '', '(28|29|30|31)'), // Number of days in month 28 'o' => array( '%V', '', '\d{1,2}'), // ISO-8601 week number 42 'a' => array( '%P', 'tt', '[aApP][mM]'), // am or pm am 'A' => array( '%p', 'TT', '[aApP][mM]'), // AM or PM AM 'g' => array( '%-I', 'h', '\d{1,2}'), // 12-hour format, no leading zeros 5 'h' => array( '%I', 'hh', '\d{2}'), // 12-hour format, leading zeros 05 'G' => array( '%-H', 'h24', '\d{1,2}'), // 24-hour format, no leading zeros 5 'H' => array( '%H', 'hh24', '\d{2}'), // 24-hour format, leading zeros 05 'i' => array( '%M', 'mm', '[0-5][0-9]'), // Minutes 09 's' => array( '%S', 'ss', '[0-5][0-9]'), // Seconds 59 'e' => array( '', '', '\w+'), // Timezone Identifier UTC, GMT, Atlantic/Azores 'I' => array( '', '', '[01]'), // Daylight savings time? 1=yes, 0=no 'O' => array( '', '', '[-+]\d{4}'), // Difference to Greenwich time/hrs +0200 'P' => array( '', '', '[-+]\d{2}:\d{2}'), // Same as above, but with colon +02:00 'T' => array( '', '', '\w+'), // Timezone abbreviation MST, EST 'Z' => array( '', '', '-?\d+'), // Timezone offset in seconds -43200 through 50400 'c' => array( '', '', '[-+:T\d]{19,25}'), // ISO-8601 date 2004-02-12T15:19:21+00:00 'r' => array( '', '', '\w+, \d+ \w+ \d{4}'), // RFC-2822 date Thu, 21 Dec 2000 16:01:07 +0200 'U' => array( '%s', '@', '\d+'), // Unix timestamp 123344556 ); protected $caches = array(); /** * Return all predefined PHP date() formats for use as dates * * ~~~~~ * // output all date formats * $formats = $datetime->getDateFormats(); * echo "
" . print_r($formats, true) . "
"; * ~~~~~ * * #pw-advanced * * @return array Returns an array of strings containing recognized date formats * */ public function getDateFormats() { return self::$dateFormats; } public static function _getDateFormats() { return self::$dateFormats; } /** * Return all predefined PHP date() formats for use as times * * ~~~~~ * // output all time formats * $formats = $datetime->getTimeFormats(); * echo "
" . print_r($formats, true) . "
"; * ~~~~~ * * #pw-advanced * * @return array Returns an array of strings containing recognized time formats * */ public function getTimeFormats() { return self::$timeFormats; } public static function _getTimeFormats() { return self::$timeFormats; } /** * Given a date/time string and expected format, convert it to a unix timestamp * * @param string $str Date/time string * @param string $format Format of the date/time string in PHP date syntax * @return int Unix timestamp * */ public function stringToTimestamp($str, $format) { if(empty($str)) return ''; // already a timestamp if(ctype_digit(ltrim("$str", '-'))) return (int) $str; $format = trim("$format"); if(!strlen($format)) return strtotime($str); // use PHP 5.3's date parser if its available if(function_exists('date_parse_from_format')) { // PHP 5.3+ $a = date_parse_from_format($format, $str); if(isset($a['warnings']) && count($a['warnings'])) { foreach($a['warnings'] as $warning) { $this->warning($warning . " (value='$str', format='$format')"); } } if($a['year'] && $a['month'] && $a['day']) { return mktime($a['hour'], $a['minute'], $a['second'], $a['month'], $a['day'], $a['year']); } } $regex = '!^' . $this->convertDateFormat($format, 'regex') . '$!'; if(!preg_match($regex, $str, $m)) { // wire('pages')->message("'$format' - '$regex' - '$str'"); // if we can't match it, then just send it to strtotime return strtotime($str); } $s = ''; // month if(isset($m['n'])) $s .= $m['n'] . '/'; else if(isset($m['m'])) $s .= $m['m'] . '/'; else if(isset($m['F'])) $s .= $m['F'] . ' '; else if(isset($m['M'])) $s .= $m['M'] . ' '; else return strtotime($str); // separator character $c = substr($s, -1); // day if(isset($m['j'])) $s .= $m['j'] . $c; else if(isset($m['d'])) $s .= $m['d'] . $c; else $s .= '1' . $c; // year if(isset($m['y'])) $s .= $m['y']; else if(isset($m['Y'])) $s .= $m['Y']; else $s .= date('Y'); $s .= ' '; $useTime = true; // hour if(isset($m['g'])) $s .= $m['g'] . ':'; else if(isset($m['h'])) $s .= $m['h'] . ':'; else if(isset($m['G'])) $s .= $m['G'] . ':'; else if(isset($m['H'])) $s .= $m['H'] . ':'; else $useTime = false; // time if($useTime) { // minute if(isset($m['i'])) { $s .= $m['i']; // second if(isset($m['s'])) $s .= ':' . $m['s']; } // am/pm if(isset($m['a'])) $s .= ' ' . $m['a']; else if(isset($m['A'])) $s .= ' ' . $m['A']; } return strtotime($s); } /** * Format a date with the given PHP date() or PHP strftime() format * * This method will accept either [PHP date](http://php.net/manual/en/function.date.php) * or [PHP strftime](http://php.net/manual/en/function.strftime.php) compatible formats, * determine what kind it is, and return the timestamp formatted with it. * * ~~~~~~ * // Output the current date/time in ISO-8601 like format * echo $datetime->formatDate(time(), 'Y-m-d H:i a'); * ~~~~~~ * * #pw-internal * * @param int $value Unix timestamp of date/time * @param string $format PHP date() or strftime() format string to use for formatting * @return string Formatted date string * */ public function formatDate($value, $format) { if(!$value) return ''; if(!strlen("$format") || $format === 'U' || $format === '%s') return (int) $value; // unix timestamp $relativeStr = ''; if(strpos($format, '!') !== false) { if(preg_match('/([!][relativ]+-?)/', $format, $matches)) { $relativeStr = $this->date(ltrim($matches[1], '!'), $value); $format = str_replace($matches[1], '///', $format); } } if(strpos($format, '%') !== false) { // use strftime() if format string contains a % if(strpos($format, '%-') !== false) { // not all systems support the '%-' option in strftime to trim leading zeros // so we are doing our own implementation here $TRIM0 = true; $format = str_replace('%-', 'TRIM0%', $format); } else { $TRIM0 = false; } $value = $this->strftime($format, $value); if($TRIM0) $value = str_replace(array('TRIM00', 'TRIM0'), '', $value); } else { $value = $this->date($format, $value); } if(strlen($relativeStr)) $value = str_replace('///', $relativeStr, $value); return $value; } /** * Given a PHP date() format, convert it to either 'js', 'strftime' or 'regex' format * * #pw-advanced * * @param string $format PHP date() format * @param string $type New format to convert to: either 'js', 'strftime', or 'regex' * @return string * */ public function convertDateFormat($format, $type) { $newFormat = ''; $lastc = ''; for($n = 0; $n < strlen($format); $n++) { $c = $format[$n]; if($c == '\\') { // begin escaped character $lastc = $c; continue; } if($lastc == '\\') { // literal character, not translated $lastc = $c; $newFormat .= $c; continue; } if(!isset(self::$dateConversion[$c])) { // unknown character if($type == 'regex' && in_array($c, array('.', '[', ']', '(', ')', '*', '+', '?'))) { $c = '\\' . $c; // escape regex chars } $newFormat .= $c; continue; } list($strftime, $js, $regex) = self::$dateConversion[$c]; if($type == 'js') { $newFormat .= $js; } else if($type == 'strftime') { $newFormat .= $strftime; } else if($type == 'regex') { $newFormat .= '\b(?<' . $c . '>' . $regex . ')\b'; // regex captured with name of date() format char } else { $newFormat .= $c; } $lastc = $c; } return $newFormat; } /** * Format a date, using PHP date(), strftime() or other special strings (see arguments). * * This is designed to work the same way as PHP's `date()` but be able to accept any common format * used in ProcessWire. This is helpful for reducing code in places where you might have logic * determining when to use `date()`, `strftime()`, `wireRelativeTimeStr()` or some other date * formatting function. * * ~~~~~~ * // Output the current date/time in relative format * echo $datetime->date('relative'); * ~~~~~~ * * @param string|int $format Use one of the following: * - [PHP date](http://php.net/manual/en/function.date.php) format * - [PHP strftime](http://php.net/manual/en/function.strftime.php) format (detected by presence of a '%' somewhere in it) * - `relative` for a relative date/time string. * - `relative-` for a relative date/time string with no tense. * - `rel` for an abbreviated relative date/time string. * - `rel-` for an abbreviated relative date/time string with no tense. * - `r` for an extra-abbreviated relative date/time string. * - `r-` for an extra-abbreviated relative date/time string with no tense. * - `ts` makes it return a unix timestamp * - blank string makes it use the system date format ($config->dateFormat) * - If given an integer and no second argument specified, it is assumed to be the second ($ts) argument. * @param int|string|null $ts Optionally specify the date/time stamp or strtotime() compatible string. If not specified, current time is used. * @return string|bool Formatted date/time, or boolean false on failure * */ function date($format = '', $ts = null) { if(is_null($ts)) { // ts not specified, or it was specified in $format if(ctype_digit("$format")) { // ts specified in format $ts = (int) $format; $format = ''; } else { // use current timestamp $ts = time(); } } else if(is_string($ts) && ctype_digit("$ts")) { // ts is a digit string, convert to integer $ts = (int) $ts; } else if(is_string($ts)) { // ts is a non-integer string, we assume to be a strtotime() compatible formatted date $ts = strtotime($ts); } if($format == '') $format = $this->wire()->config->dateFormat; if($format == 'relative') { $value = $this->relativeTimeStr($ts); } else if($format == 'relative-') { $value = $this->relativeTimeStr($ts, false, false); } else if($format == 'rel') { $value = $this->relativeTimeStr($ts, true); } else if($format == 'rel-') { $value = $this->relativeTimeStr($ts, true, false); } else if($format == 'r') { $value = $this->relativeTimeStr($ts, 1); } else if($format == 'r-') { $value = $this->relativeTimeStr($ts, 1, false); } else if($format == 'ts') { $value = $ts; } else if(strpos($format, '%') !== false && version_compare(PHP_VERSION, '8.1.0', '<')) { $value = $this->strftime($format, $ts); } else { $value = date($format, $ts); $value = $this->localizeDateText($value, $format); } return $value; } /** * Parse about any English textual datetime description into a Unix timestamp using PHP’s strtotime() * * This function behaves the same as PHP’s version except that it optionally accepts an `$options` array * and lets you specify the return value for empty or zeroed dates like 0000-00-00. If given a zerod date * then it returns null by default (rather than throwing an error as PHP8 does). * * @param string $str Date/time string * @param array|int $options Options to modify behavior, or specify int for the `baseTimestamp` option. * - `emptyReturnValue` (int|null|false): Value to return for empty or zero-only date strings (default=null) * - `baseTimestamp` (int|null): The timestamp which is used as a base for the calculation of relative dates. * @return false|int|null * @see https://www.php.net/manual/en/function.strtotime.php * @since 3.0.178 * */ function strtotime($str, $options = array()) { $defaults = array( 'emptyReturnValue' => null, 'baseTimestamp' => null, ); if(is_int($options)) $defaults['baseTimestamp'] = $options; $options = is_array($options) ? array_merge($defaults, $options) : $defaults; $str = trim($str); if(empty($str)) return $options['emptyReturnValue']; if(strpos($str, '00') === 0) { $test = trim(preg_replace('/[^\d]/', '', $str), '0'); if(!strlen($test)) return $options['emptyReturnValue']; } if($options['baseTimestamp'] === null) return strtotime($str); return strtotime($str, $options['baseTimestamp']) ; } /** * strftime() replacement function that works in PHP 8.1+ (though not locale aware) * * @param string $format * @param null|int|string $timestamp * @return string|false * @since 3.0.197 * */ public function strftime($format, $timestamp = null) { $format = (string) $format; if(is_string($timestamp)) { if(empty($timestamp)) { $timestamp = null; } else if(ctype_digit($timestamp)) { $timestamp = (int) $timestamp; } else { $timestamp = $this->strtotime($timestamp); } } if($timestamp === null) $timestamp = time(); if(version_compare(PHP_VERSION, '8.1.0', '<')) { return strftime($format, $timestamp); } $format = $this->strftimeToDateFormat($format); $value = date($format, $timestamp); $value = $this->localizeDateText($value, $format); return $value; } /** * Convert strftime() format to date() format * * @param string $format * @return string * @since 3.0.197 * */ protected function strftimeToDateFormat($format) { // replacements, in addition to those specified in self::$dateConversion // strftime format => date format $replacements = array( '%e' => 'j', // Day of the month without leading zeros '%j' => 'z', // Day of the year, 3 digits with leading zeros '%U' => '_', // Week number of the given year, starting with the first Sunday as the first week (not implemented) '%h' => 'M', // Abbreviated month name '%C' => '_', // Two digit representation of the century (year divided by 100, truncated to an integer) (not implemented) '%g' => 'y', // Two digit representation of the year going by ISO-8601:1988 standards (see %V) '%G' => 'Y', // 4 digit year '%k' => 'G', // Hour in 24-hour format '%l' => 'g', // Hour in 12-hour format '%r' => 'h:i:s A', // Example: 09:34:17 PM '%R' => 'G:i', // Example: 00:35 for 12:35 AM '%T' => 'G:i:s', // Example: 21:34:17 for 09:34:17 PM '%X' => 'G:i:s', // Preferred time representation based on locale, without the date, Example: 03:59:16 or 15:59:16 '%Z' => 'T', // The time zone abbreviation. Example: EST for Eastern Time '%c' => 'Y-m-d H:i:s', // Preferred date and time stamp based on locale '%D' => 'm/d/y', // Example: 02/05/09 for February 5, 2009 '%F' => 'Y-m-d', // Example: 2009-02-05 for February 5, 2009 '%n' => '\\n', // newline '%t' => '\\t', // tab '%%' => '%', // literal percent ); foreach(self::$dateConversion as $dateFormat => $formats) { $strftimeFormat = $formats[0]; if(empty($strftimeFormat)) continue; if(strpos($format, $strftimeFormat) !== false) $replacements[$strftimeFormat] = $dateFormat; } return strtr($format, $replacements); } /** * Given a unix timestamp (or date string), returns a formatted string indicating the time relative to now * * For example: * * - 2 years ago * - 3 months ago * - 1 day ago * - 30 seconds ago * - Just now * - 1 day from now * - 5 months from now * - 3 years from now * * This method also supports multi-language and will output in the current user's language, so long as the * phrases in /wire/core/WireDateTime.php are translated in the language pack. * * @param int|string $ts Unix timestamp or date string * @param bool|int|array $abbreviate Whether to use abbreviations for shorter strings. * - Specify boolean TRUE for abbreviations (abbreviated where common, not always different from non-abbreviated) * - Specify integer 1 for extra short abbreviations (all terms abbreviated into shortest possible string) * - Specify boolean FALSE or omit for no abbreviations. * - Specify associative array of key=value pairs of terms to use for abbreviations. The possible keys are: * just now, ago, from now, never, second, minute, hour, day, week, month, year, decade, seconds, minutes, * hours, days, weeks, months, years, decades * @param bool $useTense Whether to append a tense like "ago" or "from now". * - May be ok to disable in situations where all times are assumed in future or past. * - In abbreviate=1 (shortest) mode, this removes the leading "+" or "-" from the string. * @return string Formatted relative time string * */ public function ___relativeTimeStr($ts, $abbreviate = false, $useTense = true) { // Originally based upon: if(empty($ts)) { if(is_array($abbreviate) && isset($abbreviate['never'])) return $abbreviate['never']; return $this->_('Never'); } $justNow = $this->_('just now'); $ago = $this->_('ago'); $prependAgo = ''; $fromNow = $this->_('from now'); $prependFromNow = ''; $space = ' '; if($abbreviate === 1) { // extra short abbreviations $justNow = $this->_('now'); $ago = ''; $prependAgo = '-'; $fromNow = ''; $prependFromNow = '+'; $space = ''; } else if($abbreviate === true) { // standard abbreviations $justNow = $this->_('now'); $fromNow = ''; $prependFromNow = $this->_('in') . ' '; } else if(is_array($abbreviate)) { // user substitutions if(isset($abbreviate['just now'])) $justNow = $abbreviate['just now']; if(isset($abbreviate['from now'])) $fromNow = $abbreviate['from now']; if(isset($abbreviate['ago'])) $ago = $abbreviate['ago']; } $lengths = array("60","60","24","7","4.35","12","10"); $now = time(); if(!ctype_digit("$ts")) $ts = strtotime($ts); if(empty($ts)) return ""; // is it future date or past date if($now > $ts) { $difference = $now - $ts; $tense = $ago; $prepend = $prependAgo; } else { $difference = $ts - $now; $tense = $fromNow; $prepend = $prependFromNow; } if(!$useTense) { $prepend = ''; $tense = ''; } for($j = 0; $difference >= $lengths[$j] && $j < count($lengths)-1; $j++) { $difference /= $lengths[$j]; } $difference = round($difference); if(!$difference) return $justNow; $periods = $difference != 1 ? $this->getPeriods($abbreviate, true) : $this->getPeriods($abbreviate, false); $period = $periods[$j]; // return sprintf('%s%d%s%s %s', $prepend, (int) $difference, $space, $period, $tense); // i.e. 2 days ago (d=qty, 2=period, 3=tense) $quantity = $prepend . $difference . $space; $format = $this->_('Q P T'); // Relative time order: Q=Quantity, P=Period, T=Tense (i.e. 2 Days Ago) $format = str_replace(array('Q', 'P', 'T'), array('{Q}', '{P}', '{T}'), $format); $out = str_replace(array('{Q}', '{P}', '{T}'), array(" $quantity", " $period", " $tense"), $format); if($abbreviate === 1) { $out = str_replace(" ", "", $out); // no space when extra-abbreviate is active } else if(strpos($out, ' ') !== false) { $out = preg_replace('/\s\s+/', ' ', $out); } return trim($out); } /** * Render an elapsed time string * * If the `$stop` argument is omitted then it is assumed to be the current time. * The maximum period used is weeks, as months and years are not fixed length periods. * * ~~~~~ * $start = '2023-09-08 08:33:52'; * $stop = '2023-09-09 10:47:23'; * * // Regular: 1 day 2 hours 13 minutes 31 seconds * echo $datetime->elapsedTimeStr($start, $stop); * * // Abbreviated: 1 day 2 hrs 13 mins 31 secs * echo $datetime->elapsedTimeStr($start, $stop, true); * * // Abbreviated with exclusions: 1 day 2 hrs * echo $datetime->elapsedTimeStr($start, $stop, true, [ 'exclude' => 'minutes seconds' ]); * * // Optional 3.0.227+ usage and inclusions: 26 hours 13 minutes * echo $datetime->elapsedTimeStr($start, [ 'stop' => $stop, 'include' => 'hours minutes' ]); * ~~~~~ * * @param int|string $start Starting timestamp or date/time string. * @param int|string|array $stop Ending timestamp or date/time string, omit for “now”, or: * - In 3.0.227+ you may optionally substitute the `$options` array argument here. When doing so, * all remaining arguments are ignored and the `stop` and `abbreviate` (if needed) may be * specified in the given options array, along with any other options. * @param bool|int|array $abbreviate * - Specify boolean FALSE for verbose elapsed time string without abbreviations (default). * - Specify boolean TRUE for abbreviations (abbreviated where common, not always different from non-abbreviated). * - Specify integer `1` for extra short abbreviations (all terms abbreviated into shortest possible string). * - Specify integer `0` for digital elapsed time string like “00:01:12” referring to hours:minutes:seconds. * - Note that when using `0` no options apply except except for `exclude[seconds]` option. * @param array $options Additional options: * - `delimiter` (string): String to separate time periods (default=' '). * - `getArray` (bool): Return an array of integers indexed by period name (rather than a string)? 3.0.227+ * - `exclude` (array|string): Exclude these periods, one or more of: 'seconds', 'minutes', 'hours', 'days', 'weeks' (default=[]) * - `include` (array|string): Include only these periods, one or more of: 'seconds', 'minutes', 'hours', 'days', 'weeks' (default=[]) 3.0.227+ * - Note the exclude and include options should not be used together, and the include option requires 3.0.227+. * @return string|array Returns array only if the `getArray` option is true, otherwise returns a string. * @since 3.0.129 * */ public function elapsedTimeStr($start, $stop = null, $abbreviate = false, array $options = array()) { $defaults = array( 'delimiter' => ' ', 'exclude' => array(), 'include' => array(), 'getArray' => false, ); if(is_array($stop)) { // options specified in $stop argument $options = $stop; $stop = isset($options['stop']) ? $options['stop'] : null; $abbreviate = isset($options['abbreviate']) ? $options['abbreviate'] : false; } $options = array_merge($defaults, $options); $periodNames = array('weeks', 'days', 'hours', 'minutes', 'seconds'); $usePeriods = array(); $negative = false; foreach(array('exclude', 'include') as $key) { if(is_string($options[$key])) { $value = trim($options[$key]); $options[$key] = $value ? explode(' ', $value) : array(); } else if(!is_array($options[$key])) { $options[$key] = array(); } foreach($options[$key] as $k => $v) { if(!in_array($v, $periodNames)) unset($options[$key][$k]); } } $include = count($options['include']) && $abbreviate !== 0 ? $options['include'] : null; $exclude = count($options['exclude']) ? $options['exclude'] : null; foreach($periodNames as $name) { if($include && !in_array($name, $include)) continue; if($exclude && in_array($name, $exclude)) continue; $usePeriods[$name] = $name; } if($stop === null) $stop = time(); if(!ctype_digit("$start")) $start = strtotime($start); if(!ctype_digit("$stop")) $stop = strtotime($stop); if($start > $stop) { list($start, $stop) = array($stop, $start); $negative = true; } $times = array(); $seconds = $stop - $start; if($seconds >= 604800 && $abbreviate !== 0 && isset($usePeriods['weeks'])) { $weeks = floor($seconds / 604800); $seconds = $seconds - ($weeks * 604800); $key = $weeks == 1 ? 'week' : 'weeks'; $times[$key] = $weeks; } else { $times['weeks'] = 0; } if($seconds >= 86400 && $abbreviate !== 0 && isset($usePeriods['days'])) { $days = floor($seconds / 86400); $seconds = $seconds - ($days * 86400); $key = $days == 1 ? 'day' : 'days'; $times[$key] = $days; } else { $times['days'] = 0; } if($seconds >= 3600 && isset($usePeriods['hours'])) { $hours = floor($seconds / 3600); $seconds = $seconds - ($hours * 3600); $key = $hours == 1 ? 'hour' : 'hours'; $times[$key] = $hours; } else { $times['hours'] = 0; $hours = 0; } if($seconds >= 60 && isset($usePeriods['minutes'])) { $minutes = floor($seconds / 60); $seconds = $seconds - ($minutes * 60); $key = $minutes == 1 ? 'minute' : 'minutes'; $times[$key] = $minutes; } else { $times['minutes'] = 0; $minutes = 0; } if(($seconds > 0 || empty($times)) && isset($usePeriods['seconds'])) { $key = $seconds == 1 ? 'second' : 'seconds'; $times[$key] = $seconds; } else { $times['seconds'] = 0; $seconds = 0; } if($abbreviate === 0 && !$options['getArray']) { if(strlen($hours) < 2) $hours = "0$hours"; if(strlen($minutes) < 2) $minutes = "0$minutes"; if(strlen($seconds) < 2) $seconds = "0$seconds"; $value = "$hours:$minutes"; if(isset($usePeriods['seconds'])) $value .= ":$seconds"; } else { $periods = $this->getPeriods($abbreviate); $a = array(); $getArray = array(); foreach($times as $key => $qty) { $pluralKey = rtrim($key, 's') . 's'; if(empty($qty) && ($pluralKey !== 'seconds' || count($a))) continue; if(!isset($usePeriods[$pluralKey])) continue; $sep = $abbreviate === 1 ? '' : ' '; $str = $qty . $sep . $periods[$key]; $a[] = $str; $getArray[$pluralKey] = $qty; $getArray[$pluralKey . 'Text'] = $str; } $value = implode($options['delimiter'], $a); if($negative) $value = "-$value"; if($options['getArray']) { $getArray['negative'] = $negative; $getArray['text'] = $value; $value = $getArray; } } return $value; } /** * Get named time periods * * Returns regular array(s) of periods in this order: * seconds, minutes, hours, days, weeks, months, years decades * * If $plural argument is null (or omitted) it instead returns an array * indexed by period name including both singular and plural periods. * * @param $abbreviate * - Specify 1 to get shortest possible abbreviations * - Specify true to get standard/medium abbreviations * - Specify false to get large/full terms (no abbreviations) * - Specify associative array to get large/full terms and substitute your own * @param null|true|int $plural * - Specify true to get plural, * - Specify false to get singular, * - Specify 1 to get array where [ 0 => [singulars], 1 => [plurals] ] * - Omit (or null) to get all in an indexed array * @return array * */ protected function getPeriods($abbreviate, $plural = null) { static $definitions = array(); if(empty($definitions)) $definitions = array( 'keys-singular' => array( 'second', 'minute', 'hour', 'day', 'week', 'month', 'year', 'decade' ), 'keys-plural' => array( 'seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years', 'decades' ), 'short-singular' => array( $this->_("s"), $this->_("m"), $this->_("hr"), $this->_("d"), $this->_("wk"), $this->_("mon"), $this->_("yr"), $this->_("decade") ), 'short-plural' => array( $this->_("s"), $this->_("m"), $this->_("hr"), $this->_("d"), $this->_("wks"), $this->_("mths"), $this->_("yrs"), $this->_("decades") ), 'medium-singular' => array( $this->_("sec"), $this->_("min"), $this->_("hr"), $this->_("day"), $this->_("week"), $this->_("month"), $this->_("year"), $this->_("decade") ), 'medium-plural' => array( $this->_("secs"), $this->_("mins"), $this->_("hrs"), $this->_("days"), $this->_("weeks"), $this->_("months"), $this->_("years"), $this->_("decades") ), 'large-singular' => array( $this->_("second"), $this->_("minute"), $this->_("hour"), $this->_("day"), $this->_("week"), $this->_("month"), $this->_("year"), $this->_("decade") ), 'large-plural' => array( $this->_("seconds"), $this->_("minutes"), $this->_("hours"), $this->_("days"), $this->_("weeks"), $this->_("months"), $this->_("years"), $this->_("decades") ), ); if($abbreviate === 1) { // extra short abbreviations $periodsPlural = $definitions['short-plural']; $periodsSingular = $definitions['short-singular']; } else if($abbreviate === true) { // standard abbreviations $periodsPlural = $definitions['medium-plural']; $periodsSingular = $definitions['medium-singular']; } else { // no abbreviations $periodsPlural = $definitions['large-plural']; $periodsSingular = $definitions['large-singular']; // merge in any user-supplied abbreviations if(is_array($abbreviate)) { foreach($definitions['keys-plural'] as $key => $term) { if(isset($abbreviate[$term])) $periodsPlural[$key] = $abbreviate[$term]; } foreach($definitions['keys-singular'] as $key => $term) { if(isset($abbreviate[$term])) $periodsSingular[$key] = $abbreviate[$term]; } } } if($plural === true) return $periodsPlural; if($plural === false) return $periodsSingular; // get all indexed by term $periods = array(); foreach($definitions['keys-plural'] as $key => $term) { $periods[$term] = $periodsPlural[$key]; } foreach($definitions['keys-singular'] as $key => $term) { $periods[$term] = $periodsSingular[$key]; } return $periods; } /** * Localize a date's month and day names, when present * * @param string $value Date that is already formated with $format * @param string $format Format that was used to format the date * @return string * */ protected function localizeDateText($value, $format) { if(!$this->wire()->languages) return $value; $findReplace = []; $f = $format; $keys = array(); $ltrKeys = array( 'F' => 'monthNames', 'M' => 'monthAbbrs', 'l' => 'dayNames', 'D' => 'dayAbbrs', 'a' => 'meridiums', 'A' => 'meridiums', ); foreach($ltrKeys as $ltr => $key) { $a = self::$dateConversion[$ltr]; if(strpos($f, $ltr) !== false || strpos($f, $a[0]) !== false || strpos($f, $a[1]) !== false) { $keys[] = $key; } } foreach($keys as $key) { $findReplace = array_merge($findReplace, $this->dateWords($key)); } if(count($findReplace)) { $find = array_keys($findReplace); $replace = array_values($findReplace); if($find != $replace) { $value = str_replace($find, $replace, $value); } } return $value; } /** * Get translated month/day words indexed by English words * * @param string $key One of: monthNames, monthAbbrs, dayNames, dayAbbrs, meridiums * @return array * */ protected function dateWords($key) { $k = $key . $this->wire()->user->language->id; switch($key) { case 'monthNames': if(empty($this->caches[$k])) $this->caches[$k] = array( 'January' => $this->_('January'), 'February' => $this->_('February'), 'March' => $this->_('March'), 'April' => $this->_('April'), 'May' => $this->_('May'), 'June' => $this->_('June'), 'July' => $this->_('July'), 'August' => $this->_('August'), 'September' => $this->_('September'), 'October' => $this->_('October'), 'November' => $this->_('November'), 'December' => $this->_('December'), ); return $this->caches[$k]; case 'monthAbbrs': if(empty($this->caches[$k])) $this->caches[$k] = array( 'Jan' => $this->_x('Jan', 'month-abbr'), 'Feb' => $this->_x('Feb', 'month-abbr'), 'Mar' => $this->_x('Mar', 'month-abbr'), 'Apr' => $this->_x('Apr', 'month-abbr'), 'May' => $this->_x('May', 'month-abbr'), 'Jun' => $this->_x('Jun', 'month-abbr'), 'Jul' => $this->_x('Jul', 'month-abbr'), 'Aug' => $this->_x('Aug', 'month-abbr'), 'Sep' => $this->_x('Sep', 'month-abbr'), 'Oct' => $this->_x('Oct', 'month-abbr'), 'Nov' => $this->_x('Nov', 'month-abbr'), 'Dec' => $this->_x('Dec', 'month-abbr'), ); return $this->caches[$k]; case 'dayNames': if(empty($this->caches[$k])) $this->caches[$k] = array( 'Monday' => $this->_('Monday'), 'Tuesday' => $this->_('Tuesday'), 'Wednesday' => $this->_('Wednesday'), 'Thursday' => $this->_('Thursday'), 'Friday' => $this->_('Friday'), 'Saturday' => $this->_('Saturday'), 'Sunday' => $this->_('Sunday'), ); return $this->caches[$k]; case 'dayAbbrs': if(empty($this->caches[$k])) $this->caches[$k] = array( 'Mon' => $this->_x('Mon', 'day-abbr'), 'Tue' => $this->_x('Tue', 'day-abbr'), 'Wed' => $this->_x('Wed', 'day-abbr'), 'Thu' => $this->_x('Thu', 'day-abbr'), 'Fri' => $this->_x('Fri', 'day-abbr'), 'Sat' => $this->_x('Sat', 'day-abbr'), 'Sun' => $this->_x('Sun', 'day-abbr'), ); return $this->caches[$k]; case 'meridiums': if(empty($this->caches[$k])) $this->caches[$k] = array( 'am' => $this->_x('am', 'ante-meridium-lowercase'), 'pm' => $this->_x('pm', 'post-meridium-lowercase'), 'AM' => $this->_x('AM', 'ante-meridium-uppercase'), 'PM' => $this->_x('PM', 'post-meridium-uppercase'), ); return $this->caches[$k]; } return array(); } }