From 48eeb12cb3f84a6ef38490684061e113a4dfd00c Mon Sep 17 00:00:00 2001 From: therselman Date: Sun, 6 Aug 2017 00:40:19 +0200 Subject: [PATCH] --- src/Date.php | 1241 ++++++++++-------------------------------- src/DateIterator.php | 218 ++++++++ src/DatePeriod.php | 47 +- src/DateTime.php | 2 + src/Str.php | 1 + 5 files changed, 532 insertions(+), 977 deletions(-) create mode 100644 src/DateIterator.php diff --git a/src/Date.php b/src/Date.php index e332331..cece4f6 100644 --- a/src/Date.php +++ b/src/Date.php @@ -4,7 +4,6 @@ namespace Twister; use \DateTime; // http://php.net/manual/en/class.datetime.php -use Countable; // http://php.net/manual/en/class.countable.php Classes implementing Countable can be used with the count() function. use ArrayAccess; // http://php.net/manual/en/class.arrayaccess.php Interface to provide accessing objects as arrays. use IteratorAggregate; // http://php.net/manual/en/class.iteratoraggregate.php Interface to create an external Iterator. @@ -27,10 +26,12 @@ use OverflowException; // http://php.net/manual/en/class.overflowexception.php use UnderflowException; // http://php.net/manual/en/class.underflowexception.php Exception thrown when performing an invalid operation on an empty container, such as removing an element. */ -class Date implements Countable, IteratorAggregate, ArrayAccess +class Date implements IteratorAggregate, ArrayAccess { protected $str = null; + public static $utc = null; + /** * * @@ -42,20 +43,29 @@ class Date implements Countable, IteratorAggregate, ArrayAccess * @throws \InvalidArgumentException if an array or object without a * __toString method is passed as the first argument */ - public function __construct($date = 'now') + public function __construct($date) { + // I will need a FAST constructor if I want to return new Date objects from functions like 'add', 'sub', 'next' etc. + $this->str = $date; + return; + if (is_string($date)) { if (strlen($date) === 10) // 1234-67-90 { - if (preg_match('/\d\d\d\d-\d\d-\d\d/', $date) === 1) + if (preg_match('/\d\d\d\d-\d\d-\d\d/', $date) === 1) // '~^([1-9]\d\d\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$~' { - + $this->str = $date; + return; } + throw new InvalidArgumentException('Date string must have the following format: "YYYY-MM-DD"'); } + throw new InvalidArgumentException('Date string must have 10 characters'); } - if ( ! is_string($date)) { - if (is_array($date)) { + else + { + if (is_array($date)) + { if (count($date) === 3) { if (isset($date['year']) && isset($date['month']) && isset($date['day'])) @@ -72,12 +82,66 @@ class Date implements Countable, IteratorAggregate, ArrayAccess { throw new InvalidArgumentException('Invalid date array `' . print_r($date, true) . '` passed to Twister\Date, array must have 3 members!'); } - } elseif (is_object($str) && !method_exists($str, '__toString')) { - throw new InvalidArgumentException('Passed object must have a __toString method'); + } + else if ($date instanceof DateTime) + { + + } + else if (is_object($date)) + { + if ( ! method_exists($date, '__toString')) + throw new InvalidArgumentException('Passed object must have a __toString method'); + $this->str = (string) $date; } } + } - $this->str = (string) $str; + /** + * Wrapper around \DateTime->add() + * + * @link http://php.net/manual/en/datetime.add.php + * @link http://php.net/manual/en/class.dateinterval.php + * @link http://php.net/manual/en/dateinterval.construct.php + * @link https://en.wikipedia.org/wiki/Iso8601#Durations + * + * Simple examples: + * Two days is P2D. + * Two seconds is PT2S. + * Six years and five minutes is P6YT5M. + * + * Formats are based on the » ISO 8601 duration specification. + * + * @param string $interval_spec The character encoding + * @return new Twister\Date isntance or false on failure + */ + public function add($interval_spec = 'P1D') + { + $this->str = (new \DateTime($this->str, new \DateTimeZone('UTC')))->add(new \DateInterval($interval_spec))->format('Y-m-d'); + return $this; + } + + /** + * Wrapper around \DateTime->sub() + * + * @link http://php.net/manual/en/datetime.sub.php + * @link http://php.net/manual/en/class.dateinterval.php + * @link http://php.net/manual/en/dateinterval.construct.php + * @link https://en.wikipedia.org/wiki/Iso8601#Durations + * + * Simple examples: + * Two days is P2D. + * Two seconds is PT2S. + * Six years and five minutes is P6YT5M. + * + * Formats are based on the » ISO 8601 duration specification. + * + * @param string $interval_spec The character encoding + * @return new Twister\Date isntance or false on failure + */ + public function sub($interval_spec = 'P1D') + { + $this->str = (new \DateTime($this->str, new \DateTimeZone('UTC')))->add(new \DateInterval($interval_spec))->format('Y-m-d'); + return $this; } /** @@ -164,18 +228,25 @@ class Date implements Countable, IteratorAggregate, ArrayAccess } } } + // fallthrough case 1: $date = $params[0]; if (is_string($date)) { - if (preg_match('~^([1-9]\d\d\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$~', $date, $matches) === 1) + if (preg_match('~^([1-9]\d\d\d)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$~', $date, $matches) === 1) { return checkdate($matches[2], $matches[3], $matches[1]) ? $date : false; // checkdate(month, day, year) } + else if (preg_match('~([1-9]\d\d\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])~', $date, $matches) === 1) // removed the ~^ ... $~ first & last matches! Also added more delimeters + { + $date = $matches[1] . '-' . $matches[2] . '-' . $matches[3]; + return checkdate($matches[2], $matches[3], $matches[1]) ? $date : false; // checkdate(month, day, year) + } else { // ... can we match other date formats? // http://php.net/manual/en/function.strtotime.php + return false; } } else if (is_array($date)) @@ -189,6 +260,7 @@ class Date implements Countable, IteratorAggregate, ArrayAccess { if ($day >= 1000 && $year <= 12 && $month <= 31) { + // We switch the year, month, day to month, day, year ... ie. US format $year = $date[2]; $month = $date[0]; $day = $date[1]; @@ -283,14 +355,10 @@ class Date implements Countable, IteratorAggregate, ArrayAccess $day = (int) $day; if ($year < 1000 || $month < 1 || $day < 1 || $day > 31 || $month > 12 || $year > 9999) - { return false; - } if (checkdate($month, $day, $year)) - { - return $year . '-' . ($month < 10 ? '0' : null) . $month . '-' . ($day < 10 ? '0' : null) . $day; - } + return str_pad($year, 4, '0', STR_PAD_LEFT) . str_pad($month, 3, '-0', STR_PAD_LEFT) . str_pad($day , 3, '-0', STR_PAD_LEFT); return false; } @@ -316,16 +384,6 @@ class Date implements Countable, IteratorAggregate, ArrayAccess trigger_error('Function ' . __METHOD__ . ' not implemented yet'); } - /** - * Returns the timestamp - * - * @return int The unix timestamp ... or number of days ??? - */ - public function count() - { - return strlen($this->str); - } - /** * Convert any date format into valid MySQL date format 'YYYY-MM-DD' * eg. YYYY.MM.DD -> YYYY-MM-DD @@ -340,41 +398,58 @@ class Date implements Countable, IteratorAggregate, ArrayAccess /** * * @param string $delimiter - * @return + * @return string */ public function delimit($delimiter) { - $str = \preg_replace('~([^A-Z\b])([A-Z])~', '\1-\2', \trim($this->str)); - $str = \preg_replace('~[-_\s]+~', $delimiter, \strtolower($str)); - - return new static($str); + return implode($delimiter, explode('-', $this->str)); } /** - * Returns true if the string contains a date in the format 'YYYY-MM-DD' - * Alternative patterns: - * '/^\d{4}-\d{2}-\d{2} [0-2][0-3]:[0-5][0-9]:[0-5][0-9]$/' - * '/^\d\d\d\d-\d\d-\d\d$/' - * '/^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/' * * @return string[]|null Returns an array with 'year', 'month' and 'day' * from a matching date in the format 'YYYY-MM-DD', or null on failure */ - public function getDate($pattern = null) + public function format($format = 'Y-m-d', $tz = 'UTC') { - $pattern = $pattern ?? '/^(?P[12]\d\d\d)-(?P0[1-9]|1[0-2])-(?P0[1-9]|[1-2][0-9]|3[0-1])$/'; - return preg_match($pattern, $this->str, $matches) === false ? null : $matches; + return (new \DateTime($this->str, new \DateTimeZone($tz)))->format($format); } /** - * Gets a hash code of the internal string. + * PHP version of the MySQL FROM_DAYS() function + * + * @link https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_from-days + * + * @return string[]|null Returns an array with 'year', 'month' and 'day' + * from a matching date in the format 'YYYY-MM-DD', or null on failure + */ + public static function from_days($days) + { + trigger_error('Function ' . __METHOD__ . ' not implemented yet'); + } + + /** + * PHP version of the MySQL FROM_DAYS() function + * + * @link https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_from-days + * + * @return string[]|null Returns an array with 'year', 'month' and 'day' + * from a matching date in the format 'YYYY-MM-DD', or null on failure + */ + public static function from_unixtime($time) + { + trigger_error('Function ' . __METHOD__ . ' not implemented yet'); + } + + /** + * Gets a hash code of the internal date. * * @param string|null $algo Algorithm name supported by the hash() library, defaults to 'md5' * @return string */ public function hash($algo = 'md5', $raw_output = false) { - return new static(hash($algo, $this->str, $raw_output)); + return hash($algo, $this->str, $raw_output); } /** @@ -385,20 +460,16 @@ class Date implements Countable, IteratorAggregate, ArrayAccess */ public function getHash($algo = 'md5', $raw_output = false) { - return new static(hash($algo, $this->str, $raw_output)); + return hash($algo, $this->str, $raw_output); } /** * - * @return Human readable string format, eg. 24 October 1977 + * @return Human readable date format, eg. 24 October 1977 */ public function humanize() { trigger_error('Function ' . __METHOD__ . ' not implemented yet'); - - return static::create( - str_replace(['_id', '_'], ['', ' '], $this->str), - $this->encoding)->trim()->upperCaseFirst(); } /** @@ -410,25 +481,6 @@ class Date implements Countable, IteratorAggregate, ArrayAccess return $this->str === null || $this->str === '0000-00-00'; } - /** - * Returns true if the string contains a date in the format 'YYYY-MM-DD' AND is a valid Gregorian date - * - * All date patterns MUST have 3x (..) - * - * Alternative patterns: - * '/^(\d\d\d\d)-(\d\d)-(\d\d)$/' - * '/^(\d{4})-(\d{2})-(\d{2}) [0-2][0-3]:[0-5][0-9]:[0-5][0-9]$/' - * '/^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/' - * - * @link http://php.net/manual/en/function.checkdate.php - * - * @return bool Whether or not $str contains a matching date in the format 'YYYY-MM-DD' - */ - public static function isDate($pattern = '~^([1-9]\d\d\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$~') - { - return preg_match($pattern, $this->str, $matches) === 1 && checkdate($matches[2], $matches[3], $matches[1]); // checkdate(month, day, year) - } - /** * Gets an MD5 hash code of the internal date. Return result can be raw binary or hex by default * @@ -505,7 +557,7 @@ class Date implements Countable, IteratorAggregate, ArrayAccess case 1: return $value; case 2: return $value; } - throw new Exception(''); + throw new Exception('TODO'); } /** @@ -520,6 +572,34 @@ class Date implements Countable, IteratorAggregate, ArrayAccess throw new Exception('TODO'); } + /** + * PHP equivalent of the LAST_DAY() MySQL function + * + * `Takes a date or datetime value and returns the corresponding value for the last day of the month. Returns NULL if the argument is invalid.` + * + * @link https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_last-day + * + * @return int + */ + public function last_day() + { + trigger_error('Function ' . __METHOD__ . ' not implemented yet'); + } + + /** + * PHP equivalent of the QUARTER() MySQL function + * + * `Returns the quarter of the year for date, in the range 1 to 4.` + * + * @link https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_quarter + * + * @return int + */ + public function quarter() + { + return (int) (($this->month / 4) + 1); + } + /** * Gets a SHA1 (160-bit) hash code of the internal string. Return result can be raw binary or hex by default * @@ -564,924 +644,189 @@ class Date implements Countable, IteratorAggregate, ArrayAccess return hash('sha512', $this->str, $raw_output); } - /* - * A str_shuffle() wrapper function. + /** * - * @return static Object with a shuffled $str + * @return array */ - public function shuffle() + public function toArray() { - return new static(str_shuffle($this->str)); + return explode('-', $this->str); } /** - * Converts the string into an URL slug. This includes replacing non-ASCII - * characters with their closest ASCII equivalents, removing remaining - * non-ASCII and non-alphanumeric characters, and replacing whitespace with - * $replacement. The replacement defaults to a single dash, and the string - * is also converted to lowercase. The language of the source string can - * also be supplied for language-specific transliteration. * - * @param string $replacement The string used to replace whitespace - * @param string $language Language of the source string - * @return static Object whose $str has been converted to an URL slug + * @return int */ - public function slugify($replacement = '-', $language = 'en') - { - trigger_error('Function not implemented yet'); - - $stringy = $this->toAscii($language); - - $stringy->str = str_replace('@', $replacement, $stringy); - $quotedReplacement = preg_quote($replacement); - $pattern = "/[^a-zA-Z\d\s-_$quotedReplacement]/u"; - $stringy->str = preg_replace($pattern, '', $stringy); - - return $stringy->toLowerCase()->delimit($replacement) - ->removeLeft($replacement)->removeRight($replacement); - } - - /** - * Returns true if the string begins with $substring, false otherwise. - * By default, the comparison is case-sensitive, - * but can be made insensitive by setting $caseSensitive to false. - * - * @param string $substring The substring to look for - * @param bool $caseSensitive Whether or not to enforce case-sensitivity - * @return bool Whether or not $str starts with $substring - */ - public function startsWith($substring, $caseSensitive = true) - { - return ($caseSensitive ? strpos($this->str, $substring) : stripos($this->str, $substring)) === 0; - } - - /** - * Returns true if the string begins with any of $substrings, false - * otherwise. By default the comparison is case-sensitive, but can be made - * insensitive by setting $caseSensitive to false. - * - * @param string[] $substrings Substrings to look for - * @param bool $caseSensitive Whether or not to enforce - * case-sensitivity - * @return bool Whether or not $str starts with $substring - */ - public function startsWithAny($substrings, $caseSensitive = true) - { - trigger_error('Function not implemented yet'); - - if (empty($substrings)) { - return false; - } - - foreach ($substrings as $substring) { - if ($this->startsWith($substring, $caseSensitive)) { - return true; - } - } - - return false; - } - - /** - * Returns the substring beginning at $start, and up to, but not including - * the index specified by $end. If $end is omitted, the function extracts - * the remaining string. If $end is negative, it is computed from the end - * of the string. - * - * @param int $start Initial index from which to begin extraction - * @param int $end Optional index at which to end extraction - * @return static Object with its $str being the extracted substring - */ - public function slice($start, $end = null) - { - trigger_error('Function not implemented yet'); - - if ($end === null) { - $length = $this->length(); - } elseif ($end >= 0 && $end <= $start) { - return static::create('', $this->encoding); - } elseif ($end < 0) { - $length = $this->length() + $end - $start; - } else { - $length = $end - $start; - } - - return $this->substr($start, $length); - } - - /** - * Splits the string with the provided regular expression, returning an - * array of Stringy objects. An optional integer $limit will truncate the - * results. - * - * @param string $pattern The regex with which to split the string - * @param int $limit Optional maximum number of results to return - * @return static[] An array of Stringy objects - */ - public function split($pattern, $limit = null) - { - trigger_error('Function not implemented yet'); - - if ($limit === 0) { - return []; - } - - // mb_split errors when supplied an empty pattern in < PHP 5.4.13 - // and HHVM < 3.8 - if ($pattern === '') { - return [static::create($this->str, $this->encoding)]; - } - - $regexEncoding = $this->regexEncoding(); - $this->regexEncoding($this->encoding); - - // mb_split returns the remaining unsplit string in the last index when - // supplying a limit - $limit = ($limit > 0) ? $limit += 1 : -1; - - static $functionExists; - if ($functionExists === null) { - $functionExists = function_exists('\mb_split'); - } - - if ($functionExists) { - $array = \mb_split($pattern, $this->str, $limit); - } else if ($this->supportsEncoding()) { - $array = \preg_split("/$pattern/", $this->str, $limit); - } - - $this->regexEncoding($regexEncoding); - - if ($limit > 0 && count($array) === $limit) { - array_pop($array); - } - - for ($i = 0; $i < count($array); $i++) { - $array[$i] = static::create($array[$i], $this->encoding); - } - - return $array; - } - - /** - * Strip all whitespace characters. This includes tabs and newline - * characters, as well as multibyte whitespace such as the thin space - * and ideographic space. - * - * @return static Object with whitespace stripped - */ - public function stripWhitespace() - { - trigger_error('Function not implemented yet'); - - return $this->regexReplace('[[:space:]]+', ''); - } - - /** - * Returns the length of the string. strlen() wrapper. - * - * @return int The number of characters in $str given the encoding - */ - public function strlen() - { - return strlen($this->str); - } - - /** - * Returns the substring beginning at $start with the specified $length. - * It differs from the mb_substr() function in that providing a $length of - * null will return the rest of the string, rather than an empty string. - * - * @param int $start Position of the first character to use - * @param int $length Maximum number of characters used - * @return static Object with its $str being the substring - */ - public function substr($start, $length = null) - { - return new static($length === null ? substr($this->str, $start) : substr($this->str, $start, $length)); - } - - /** - * Surrounds $str with the given substring. - * - * @param string $substring The substring to add to both sides - * @return static Object whose $str had the substring both prepended and appended - */ - public function surround($substring) - { - return new static($substring . $this->str . $substring); - } - - /** - * Returns a case swapped version of the string. - * - * @return static Object whose $str has each character's case swapped - */ - public function swapCase() - { - trigger_error('Function not implemented yet'); - - $stringy = static::create($this->str, $this->encoding); - $encoding = $stringy->encoding; - - $stringy->str = preg_replace_callback( - '/[\S]/u', - function ($match) use ($encoding) { - if ($match[0] == \mb_strtoupper($match[0], $encoding)) { - return \mb_strtolower($match[0], $encoding); - } - - return \mb_strtoupper($match[0], $encoding); - }, - $stringy->str - ); - - return $stringy; - } - - /** - * Returns a string with smart quotes, ellipsis characters, and dashes from - * Windows-1252 (commonly used in Word documents) replaced by their ASCII - * equivalents. - * - * @return static Object whose $str has those characters removed - */ - public function tidy() - { - trigger_error('Function not implemented yet'); - - $str = preg_replace([ - '/\x{2026}/u', - '/[\x{201C}\x{201D}]/u', - '/[\x{2018}\x{2019}]/u', - '/[\x{2013}\x{2014}]/u', - ], [ - '...', - '"', - "'", - '-', - ], $this->str); - - return static::create($str, $this->encoding); - } - - /** - * Returns a trimmed string with the first letter of each word capitalized. - * Also accepts an array, $ignore, allowing you to list words not to be capitalized. - * - * @param array $ignore An array of words not to capitalize - * @return static Object with a titleized $str - */ - public function titleize($ignore = null) - { - trigger_error('Function not implemented yet'); - - $stringy = static::create($this->trim(), $this->encoding); - $encoding = $this->encoding; - - $stringy->str = preg_replace_callback( - '/([\S]+)/u', - function ($match) use ($encoding, $ignore) { - if ($ignore && in_array($match[0], $ignore)) { - return $match[0]; - } - - $stringy = new Stringy($match[0], $encoding); - - return (string) $stringy->toLowerCase()->upperCaseFirst(); - }, - $stringy->str - ); - - return $stringy; - } - - /** - * Returns an ASCII version of the string. A set of non-ASCII characters are - * replaced with their closest ASCII counterparts, and the rest are removed - * by default. The language or locale of the source string can be supplied - * for language-specific transliteration in any of the following formats: - * en, en_GB, or en-GB. For example, passing "de" results in "äöü" mapping - * to "aeoeue" rather than "aou" as in other languages. - * - * @param string $language Language of the source string - * @param bool $removeUnsupported Whether or not to remove the - * unsupported characters - * @return static Object whose $str contains only ASCII characters - */ - public function toAscii($language = 'en', $removeUnsupported = true) - { - trigger_error('Function not implemented yet'); - - $str = $this->str; - - $langSpecific = $this->langSpecificCharsArray($language); - if (!empty($langSpecific)) { - $str = str_replace($langSpecific[0], $langSpecific[1], $str); - } - - foreach ($this->charsArray() as $key => $value) { - $str = str_replace($value, $key, $str); - } - - if ($removeUnsupported) { - $str = preg_replace('/[^\x20-\x7E]/u', '', $str); - } - - return static::create($str, $this->encoding); - } - - /** - * Returns a base64 encoded string object. - * - * @return static base64 encoded string object - */ - public function toBase64() - { - return new static(base64_encode($this->str)); - } - - /** - * Alias of toBoolean() - * - * @return bool A boolean value for the string - */ - public function toBool() - { - return $this->toBoolean(); - } - - /** - * Returns a boolean representation of the given logical string value. - * For example, 'true', '1', 'on' and 'yes' will return true. 'false', '0', - * 'off', and 'no' will return false. In all instances, case is ignored. - * For other numeric strings, their sign will determine the return value. - * In addition, blank strings consisting of only whitespace will return false. - * For all other strings, the return value is a result of a boolean cast. - * - * @return bool A boolean value for the string - */ - public function toBoolean() - { - $key = ctype_lower($this->str) ? $this->str : strtolower($this->str); - - if (isset(self::$boolMap[$key])) - return self::$boolMap[$key]; - - if (is_numeric($this->str)) - return intval($this->str) > 0; - - return (bool) \str_replace(\str_split(" \t\n\r\0\x0B"), '', $this->str); - } - - /** - * Converts all characters in the string to lowercase. - * - * @return static Object with all characters of $str being lowercase - */ - public function toLower() - { - return new static(\strtolower($this->str)); - } - - /** - * Converts all characters in the string to lowercase. - * - * @return static Object with all characters of $str being lowercase - */ - public function toLowerCase() - { - return new static(\strtolower($this->str)); - } - - /** - * Converts each tab in the string to some number of spaces, as defined by - * $tabLength. By default, each tab is converted to 4 consecutive spaces. - * - * @param int $tabLength Number of spaces to replace each tab with - * @return static Object whose $str has had tabs switched to spaces - */ - public function toSpaces($tabLength = 4) - { - return new static(\str_replace("\t", \str_repeat(' ', $tabLength), $this->str)); - } - - /** - * Converts each occurrence of some consecutive number of spaces, as - * defined by $tabLength, to a tab. By default, each 4 consecutive spaces - * are converted to a tab. - * - * @param int $tabLength Number of spaces to replace with a tab - * @return static Object whose $str has had spaces switched to tabs - */ - public function toTabs($tabLength = 4) - { - return new static(\str_replace(\str_repeat(' ', $tabLength), "\t", $this->str)); - } - - /** - * Converts the first character of each word in the string to uppercase. - * - * @return static Object with all characters of $str being title-cased - */ - public function toTitleCase() - { - return new static(\ucwords($this->str)); - // return new static(\mb_convert_case($this->str, \MB_CASE_TITLE, 'UTF-8')); - } - - /** - * Converts all characters in the string to uppercase. - * - * @return static Object with all characters of $str being uppercase - */ - public function toUpper() - { - return new static(\ctype_upper($this->str)); - } - - /** - * Converts all characters in the string to uppercase. - * - * @return static Object with all characters of $str being uppercase - */ - public function toUpperCase() - { - return new static(\ctype_upper($this->str)); - } - - /** - * Returns a string with whitespace removed from the start and end of the string. - * Supports the removal of unicode whitespace. - * Accepts an optional string of characters to strip instead of the defaults. - * - * @param string $character_mask Optional string of characters to strip - * @return static Object with a trimmed $str - */ - public function trim(string $character_mask = " \t\n\r\0\x0B") - { - return new static(\trim($this->str, $character_mask)); - } - - /** - * Returns a string with whitespace removed from the start of the string. - * Supports the removal of unicode whitespace. Accepts an optional - * string of characters to strip instead of the defaults. - * - * @param string $chars Optional string of characters to strip - * @return static Object with a trimmed $str - */ - public function trimLeft(string $character_mask = " \t\n\r\0\x0B") - { - return new static(\ltrim($this->str, $character_mask)); - } - - /** - * Returns a string with whitespace removed from the end of the string. - * Supports the removal of unicode whitespace. Accepts an optional - * string of characters to strip instead of the defaults. - * - * @param string $chars Optional string of characters to strip - * @return static Object with a trimmed $str - */ - public function trimRight(string $character_mask = " \t\n\r\0\x0B") - { - return new static(\rtrim($this->str, $character_mask)); - } - - /** - * Truncates the string to a given length. If $substring is provided, and - * truncating occurs, the string is further truncated so that the substring - * may be appended without exceeding the desired length. - * - * @param int $length Desired length of the truncated string - * @param string $substring The substring to append if it can fit - * @return static Object with the resulting $str after truncating - */ - public function truncate($length, $substring = '') + public function toUnixTimestamp() { trigger_error('Function ' . __METHOD__ . ' not implemented yet'); - - $stringy = static::create($this->str, $this->encoding); - if ($length >= $stringy->length()) { - return $stringy; - } - - // Need to further trim the string so we can append the substring - $substringLength = \mb_strlen($substring, $stringy->encoding); - $length = $length - $substringLength; - - $truncated = \mb_substr($stringy->str, 0, $length, $stringy->encoding); - $stringy->str = $truncated . $substring; - - return $stringy; } - /** - * Converts the first character of the supplied string to upper case. - * - * @return static Object with the first character of $str being upper case - */ - public function ucFirst() + function is_leap_year() { - return new static(\ucfirst($this->str)); + return $this->year % 100 == 0 ? $this->year % 400 == 0 : $this->year % 4 == 0; } - /** - * Returns a lowercase and trimmed string separated by underscores. - * Underscores are inserted before uppercase characters (with the exception - * of the first character of the string), and in place of spaces as well as dashes. - * - * @return static Object with an underscored $str - */ - public function underscored() // AKA snake_case + function isLeapYear() { - trigger_error('Function ' . __METHOD__ . ' not implemented yet'); - - return $this->delimit('_'); + return $this->year % 100 == 0 ? $this->year % 400 == 0 : $this->year % 4 == 0; } - /** - * Returns an UpperCamelCase version of the supplied string. It trims - * surrounding spaces, capitalizes letters following digits, spaces, dashes - * and underscores, and removes spaces, dashes, underscores. - * - * @return static Object with $str in UpperCamelCase - */ - public function upperCamelize() - { - trigger_error('Function ' . __METHOD__ . ' not implemented yet'); - - return $this->camelize()->upperCaseFirst(); - } - - /** - * Converts the first character of the supplied string to upper case. - * - * @return static Object with the first character of $str being upper case - */ - public function upperCaseFirst() - { - trigger_error('Function ' . __METHOD__ . ' not implemented yet'); - - $first = \mb_substr($this->str, 0, 1, $this->encoding); - $rest = \mb_substr($this->str, 1, $this->length() - 1, $this->encoding); - - $str = \mb_strtoupper($first, $this->encoding) . $rest; - - return static::create($str, $this->encoding); - } - - /** - * Returns the replacements for the toAscii() method. - * - * @return array An array of replacements. - */ - protected function charsArray() - { - if (isset(self::$charsArray)) - return self::$charsArray; - - return self::$charsArray = [ - '0' => ['°', '₀', '۰', '0'], - '1' => ['¹', '₁', '۱', '1'], - '2' => ['²', '₂', '۲', '2'], - '3' => ['³', '₃', '۳', '3'], - '4' => ['⁴', '₄', '۴', '٤', '4'], - '5' => ['⁵', '₅', '۵', '٥', '5'], - '6' => ['⁶', '₆', '۶', '٦', '6'], - '7' => ['⁷', '₇', '۷', '7'], - '8' => ['⁸', '₈', '۸', '8'], - '9' => ['⁹', '₉', '۹', '9'], - 'a' => ['à', 'á', 'ả', 'ã', 'ạ', 'ă', 'ắ', 'ằ', 'ẳ', 'ẵ', - 'ặ', 'â', 'ấ', 'ầ', 'ẩ', 'ẫ', 'ậ', 'ā', 'ą', 'å', - 'α', 'ά', 'ἀ', 'ἁ', 'ἂ', 'ἃ', 'ἄ', 'ἅ', 'ἆ', 'ἇ', - 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ὰ', 'ά', - 'ᾰ', 'ᾱ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'а', 'أ', 'အ', - 'ာ', 'ါ', 'ǻ', 'ǎ', 'ª', 'ა', 'अ', 'ا', 'a', 'ä'], - 'b' => ['б', 'β', 'ب', 'ဗ', 'ბ', 'b'], - 'c' => ['ç', 'ć', 'č', 'ĉ', 'ċ', 'c'], - 'd' => ['ď', 'ð', 'đ', 'ƌ', 'ȡ', 'ɖ', 'ɗ', 'ᵭ', 'ᶁ', 'ᶑ', - 'д', 'δ', 'د', 'ض', 'ဍ', 'ဒ', 'დ', 'd'], - 'e' => ['é', 'è', 'ẻ', 'ẽ', 'ẹ', 'ê', 'ế', 'ề', 'ể', 'ễ', - 'ệ', 'ë', 'ē', 'ę', 'ě', 'ĕ', 'ė', 'ε', 'έ', 'ἐ', - 'ἑ', 'ἒ', 'ἓ', 'ἔ', 'ἕ', 'ὲ', 'έ', 'е', 'ё', 'э', - 'є', 'ə', 'ဧ', 'ေ', 'ဲ', 'ე', 'ए', 'إ', 'ئ', 'e'], - 'f' => ['ф', 'φ', 'ف', 'ƒ', 'ფ', 'f'], - 'g' => ['ĝ', 'ğ', 'ġ', 'ģ', 'г', 'ґ', 'γ', 'ဂ', 'გ', 'گ', - 'g'], - 'h' => ['ĥ', 'ħ', 'η', 'ή', 'ح', 'ه', 'ဟ', 'ှ', 'ჰ', 'h'], - 'i' => ['í', 'ì', 'ỉ', 'ĩ', 'ị', 'î', 'ï', 'ī', 'ĭ', 'į', - 'ı', 'ι', 'ί', 'ϊ', 'ΐ', 'ἰ', 'ἱ', 'ἲ', 'ἳ', 'ἴ', - 'ἵ', 'ἶ', 'ἷ', 'ὶ', 'ί', 'ῐ', 'ῑ', 'ῒ', 'ΐ', 'ῖ', - 'ῗ', 'і', 'ї', 'и', 'ဣ', 'ိ', 'ီ', 'ည်', 'ǐ', 'ი', - 'इ', 'ی', 'i'], - 'j' => ['ĵ', 'ј', 'Ј', 'ჯ', 'ج', 'j'], - 'k' => ['ķ', 'ĸ', 'к', 'κ', 'Ķ', 'ق', 'ك', 'က', 'კ', 'ქ', - 'ک', 'k'], - 'l' => ['ł', 'ľ', 'ĺ', 'ļ', 'ŀ', 'л', 'λ', 'ل', 'လ', 'ლ', - 'l'], - 'm' => ['м', 'μ', 'م', 'မ', 'მ', 'm'], - 'n' => ['ñ', 'ń', 'ň', 'ņ', 'ʼn', 'ŋ', 'ν', 'н', 'ن', 'န', - 'ნ', 'n'], - 'o' => ['ó', 'ò', 'ỏ', 'õ', 'ọ', 'ô', 'ố', 'ồ', 'ổ', 'ỗ', - 'ộ', 'ơ', 'ớ', 'ờ', 'ở', 'ỡ', 'ợ', 'ø', 'ō', 'ő', - 'ŏ', 'ο', 'ὀ', 'ὁ', 'ὂ', 'ὃ', 'ὄ', 'ὅ', 'ὸ', 'ό', - 'о', 'و', 'θ', 'ို', 'ǒ', 'ǿ', 'º', 'ო', 'ओ', 'o', - 'ö'], - 'p' => ['п', 'π', 'ပ', 'პ', 'پ', 'p'], - 'q' => ['ყ', 'q'], - 'r' => ['ŕ', 'ř', 'ŗ', 'р', 'ρ', 'ر', 'რ', 'r'], - 's' => ['ś', 'š', 'ş', 'с', 'σ', 'ș', 'ς', 'س', 'ص', 'စ', - 'ſ', 'ს', 's'], - 't' => ['ť', 'ţ', 'т', 'τ', 'ț', 'ت', 'ط', 'ဋ', 'တ', 'ŧ', - 'თ', 'ტ', 't'], - 'u' => ['ú', 'ù', 'ủ', 'ũ', 'ụ', 'ư', 'ứ', 'ừ', 'ử', 'ữ', - 'ự', 'û', 'ū', 'ů', 'ű', 'ŭ', 'ų', 'µ', 'у', 'ဉ', - 'ု', 'ူ', 'ǔ', 'ǖ', 'ǘ', 'ǚ', 'ǜ', 'უ', 'उ', 'u', - 'ў', 'ü'], - 'v' => ['в', 'ვ', 'ϐ', 'v'], - 'w' => ['ŵ', 'ω', 'ώ', 'ဝ', 'ွ', 'w'], - 'x' => ['χ', 'ξ', 'x'], - 'y' => ['ý', 'ỳ', 'ỷ', 'ỹ', 'ỵ', 'ÿ', 'ŷ', 'й', 'ы', 'υ', - 'ϋ', 'ύ', 'ΰ', 'ي', 'ယ', 'y'], - 'z' => ['ź', 'ž', 'ż', 'з', 'ζ', 'ز', 'ဇ', 'ზ', 'z'], - 'aa' => ['ع', 'आ', 'آ'], - 'ae' => ['æ', 'ǽ'], - 'ai' => ['ऐ'], - 'ch' => ['ч', 'ჩ', 'ჭ', 'چ'], - 'dj' => ['ђ', 'đ'], - 'dz' => ['џ', 'ძ'], - 'ei' => ['ऍ'], - 'gh' => ['غ', 'ღ'], - 'ii' => ['ई'], - 'ij' => ['ij'], - 'kh' => ['х', 'خ', 'ხ'], - 'lj' => ['љ'], - 'nj' => ['њ'], - 'oe' => ['œ', 'ؤ'], - 'oi' => ['ऑ'], - 'oii' => ['ऒ'], - 'ps' => ['ψ'], - 'sh' => ['ш', 'შ', 'ش'], - 'shch' => ['щ'], - 'ss' => ['ß'], - 'sx' => ['ŝ'], - 'th' => ['þ', 'ϑ', 'ث', 'ذ', 'ظ'], - 'ts' => ['ц', 'ც', 'წ'], - 'uu' => ['ऊ'], - 'ya' => ['я'], - 'yu' => ['ю'], - 'zh' => ['ж', 'ჟ', 'ژ'], - '(c)' => ['©'], - 'A' => ['Á', 'À', 'Ả', 'Ã', 'Ạ', 'Ă', 'Ắ', 'Ằ', 'Ẳ', 'Ẵ', - 'Ặ', 'Â', 'Ấ', 'Ầ', 'Ẩ', 'Ẫ', 'Ậ', 'Å', 'Ā', 'Ą', - 'Α', 'Ά', 'Ἀ', 'Ἁ', 'Ἂ', 'Ἃ', 'Ἄ', 'Ἅ', 'Ἆ', 'Ἇ', - 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'Ᾰ', 'Ᾱ', - 'Ὰ', 'Ά', 'ᾼ', 'А', 'Ǻ', 'Ǎ', 'A', 'Ä'], - 'B' => ['Б', 'Β', 'ब', 'B'], - 'C' => ['Ç','Ć', 'Č', 'Ĉ', 'Ċ', 'C'], - 'D' => ['Ď', 'Ð', 'Đ', 'Ɖ', 'Ɗ', 'Ƌ', 'ᴅ', 'ᴆ', 'Д', 'Δ', - 'D'], - 'E' => ['É', 'È', 'Ẻ', 'Ẽ', 'Ẹ', 'Ê', 'Ế', 'Ề', 'Ể', 'Ễ', - 'Ệ', 'Ë', 'Ē', 'Ę', 'Ě', 'Ĕ', 'Ė', 'Ε', 'Έ', 'Ἐ', - 'Ἑ', 'Ἒ', 'Ἓ', 'Ἔ', 'Ἕ', 'Έ', 'Ὲ', 'Е', 'Ё', 'Э', - 'Є', 'Ə', 'E'], - 'F' => ['Ф', 'Φ', 'F'], - 'G' => ['Ğ', 'Ġ', 'Ģ', 'Г', 'Ґ', 'Γ', 'G'], - 'H' => ['Η', 'Ή', 'Ħ', 'H'], - 'I' => ['Í', 'Ì', 'Ỉ', 'Ĩ', 'Ị', 'Î', 'Ï', 'Ī', 'Ĭ', 'Į', - 'İ', 'Ι', 'Ί', 'Ϊ', 'Ἰ', 'Ἱ', 'Ἳ', 'Ἴ', 'Ἵ', 'Ἶ', - 'Ἷ', 'Ῐ', 'Ῑ', 'Ὶ', 'Ί', 'И', 'І', 'Ї', 'Ǐ', 'ϒ', - 'I'], - 'J' => ['J'], - 'K' => ['К', 'Κ', 'K'], - 'L' => ['Ĺ', 'Ł', 'Л', 'Λ', 'Ļ', 'Ľ', 'Ŀ', 'ल', 'L'], - 'M' => ['М', 'Μ', 'M'], - 'N' => ['Ń', 'Ñ', 'Ň', 'Ņ', 'Ŋ', 'Н', 'Ν', 'N'], - 'O' => ['Ó', 'Ò', 'Ỏ', 'Õ', 'Ọ', 'Ô', 'Ố', 'Ồ', 'Ổ', 'Ỗ', - 'Ộ', 'Ơ', 'Ớ', 'Ờ', 'Ở', 'Ỡ', 'Ợ', 'Ø', 'Ō', 'Ő', - 'Ŏ', 'Ο', 'Ό', 'Ὀ', 'Ὁ', 'Ὂ', 'Ὃ', 'Ὄ', 'Ὅ', 'Ὸ', - 'Ό', 'О', 'Θ', 'Ө', 'Ǒ', 'Ǿ', 'O', 'Ö'], - 'P' => ['П', 'Π', 'P'], - 'Q' => ['Q'], - 'R' => ['Ř', 'Ŕ', 'Р', 'Ρ', 'Ŗ', 'R'], - 'S' => ['Ş', 'Ŝ', 'Ș', 'Š', 'Ś', 'С', 'Σ', 'S'], - 'T' => ['Ť', 'Ţ', 'Ŧ', 'Ț', 'Т', 'Τ', 'T'], - 'U' => ['Ú', 'Ù', 'Ủ', 'Ũ', 'Ụ', 'Ư', 'Ứ', 'Ừ', 'Ử', 'Ữ', - 'Ự', 'Û', 'Ū', 'Ů', 'Ű', 'Ŭ', 'Ų', 'У', 'Ǔ', 'Ǖ', - 'Ǘ', 'Ǚ', 'Ǜ', 'U', 'Ў', 'Ü'], - 'V' => ['В', 'V'], - 'W' => ['Ω', 'Ώ', 'Ŵ', 'W'], - 'X' => ['Χ', 'Ξ', 'X'], - 'Y' => ['Ý', 'Ỳ', 'Ỷ', 'Ỹ', 'Ỵ', 'Ÿ', 'Ῠ', 'Ῡ', 'Ὺ', 'Ύ', - 'Ы', 'Й', 'Υ', 'Ϋ', 'Ŷ', 'Y'], - 'Z' => ['Ź', 'Ž', 'Ż', 'З', 'Ζ', 'Z'], - 'AE' => ['Æ', 'Ǽ'], - 'Ch' => ['Ч'], - 'Dj' => ['Ђ'], - 'Dz' => ['Џ'], - 'Gx' => ['Ĝ'], - 'Hx' => ['Ĥ'], - 'Ij' => ['IJ'], - 'Jx' => ['Ĵ'], - 'Kh' => ['Х'], - 'Lj' => ['Љ'], - 'Nj' => ['Њ'], - 'Oe' => ['Œ'], - 'Ps' => ['Ψ'], - 'Sh' => ['Ш'], - 'Shch' => ['Щ'], - 'Ss' => ['ẞ'], - 'Th' => ['Þ'], - 'Ts' => ['Ц'], - 'Ya' => ['Я'], - 'Yu' => ['Ю'], - 'Zh' => ['Ж'], - ' ' => ["\xC2\xA0", "\xE2\x80\x80", "\xE2\x80\x81", - "\xE2\x80\x82", "\xE2\x80\x83", "\xE2\x80\x84", - "\xE2\x80\x85", "\xE2\x80\x86", "\xE2\x80\x87", - "\xE2\x80\x88", "\xE2\x80\x89", "\xE2\x80\x8A", - "\xE2\x80\xAF", "\xE2\x81\x9F", "\xE3\x80\x80", - "\xEF\xBE\xA0"], - ]; - } - - /** - * Returns language-specific replacements for the toAscii() method. - * For example, German will map 'ä' to 'ae', while other languages - * will simply return 'a'. - * - * @param string $language Language of the source string - * @return array An array of replacements. - */ - protected static function langSpecificCharsArray($language = 'en') - { - trigger_error('Function ' . __METHOD__ . ' not implemented yet'); - - $split = preg_split('/[-_]/', $language); - $language = strtolower($split[0]); - - static $charsArray = []; - if (isset($charsArray[$language])) { - return $charsArray[$language]; - } - - $languageSpecific = [ - 'de' => [ - ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü' ], - ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], - ], - 'bg' => [ - ['х', 'Х', 'щ', 'Щ', 'ъ', 'Ъ', 'ь', 'Ь'], - ['h', 'H', 'sht', 'SHT', 'a', 'А', 'y', 'Y'] - ] - ]; - - if (isset($languageSpecific[$language])) { - $charsArray[$language] = $languageSpecific[$language]; - } else { - $charsArray[$language] = []; - } - - return $charsArray[$language]; - } - - /** - * Adds the specified amount of left and right padding to the given string. - * The default character used is a space. - * - * @param int $left Length of left padding - * @param int $right Length of right padding - * @param string $padStr String used to pad - * @return static String with padding applied - */ - protected function applyPadding($left = 0, $right = 0, $padStr = ' ') - { - trigger_error('Function ' . __METHOD__ . ' not implemented yet'); - - $stringy = static::create($this->str, $this->encoding); - $length = \mb_strlen($padStr, $stringy->encoding); - - $strLength = $stringy->length(); - $paddedLength = $strLength + $left + $right; - - if (!$length || $paddedLength <= $strLength) { - return $stringy; - } - - $leftPadding = \mb_substr(str_repeat($padStr, ceil($left / $length)), 0, $left, $stringy->encoding); - $rightPadding = \mb_substr(str_repeat($padStr, ceil($right / $length)), 0, $right, $stringy->encoding); - - $stringy->str = $leftPadding . $stringy->str . $rightPadding; - - return $stringy; - } - - /** - * Returns true if $str matches the supplied pattern, false otherwise. - * - * @param string $pattern Regex pattern to match against - * @return bool Whether or not $str matches the pattern - */ - protected function matchesPattern($pattern) - { - trigger_error('Function ' . __METHOD__ . ' not implemented yet'); - - $regexEncoding = $this->regexEncoding(); - $this->regexEncoding($this->encoding); - - $match = \mb_ereg_match($pattern, $this->str); - $this->regexEncoding($regexEncoding); - - return $match; - } - - /** - * Alias for mb_ereg_replace with a fallback to preg_replace if the - * mbstring module is not installed. - */ - protected function eregReplace($pattern, $replacement, $string, $option = 'msr') - { - trigger_error('Function ' . __METHOD__ . ' not implemented yet'); - - static $functionExists; - if ($functionExists === null) { - $functionExists = function_exists('\mb_split'); - } - - if ($functionExists) { - return \mb_ereg_replace($pattern, $replacement, $string, $option); - } else if ($this->supportsEncoding()) { - $option = str_replace('r', '', $option); - return \preg_replace("/$pattern/u$option", $replacement, $string); - } - } - - /** - * Alias for mb_regex_encoding which default to a noop if the mbstring - * module is not installed. - */ - protected function regexEncoding() - { - trigger_error('Function ' . __METHOD__ . ' not implemented yet'); - - static $functionExists; - - if ($functionExists === null) { - $functionExists = function_exists('\mb_regex_encoding'); - } - - if ($functionExists) { - $args = func_get_args(); - return call_user_func_array('\mb_regex_encoding', $args); - } - } - - /** - * Returns a string with whitespace removed from the start and end of the string. - * Supports the removal of unicode whitespace. - * Accepts an optional string of characters to strip instead of the defaults. - * - * @param string $character_mask Optional string of characters to strip - * @return static Object with a trimmed $str - */ - public function rtrim(string $character_mask = " \t\n\r\0\x0B") - { - return new static(\rtrim($this->str, $character_mask)); - } - - function __get($name) { switch ($name) { - case 'length': return \strlen($this->str); + case 'year': return substr($this->str, 0, 4); + case 'month': return substr($this->str, 5, 2); + case 'day': return substr($this->str, 8, 2); + case 'days': return ; + + // http://php.net/manual/en/function.date.php + case 'd': return ; // Day of the month, 2 digits with leading zeros eg. 01 to 31 + case 'D': return ; // A textual representation of a day, three letters eg. Mon through Sun + case 'j': return ; // Day of the month without leading zeros eg. 1 to 31 + case 'l': return ; // A full textual representation of the day of the week eg. Sunday through Saturday + case 'N': return ; // ISO-8601 numeric representation of the day of the week (added in PHP 5.1.0 eg. 1 (for Monday) through 7 (for Sunday) + case 'S': return ; // English ordinal suffix for the day of the month, 2 characters eg. st, nd, rd or th. Works well with j + case 'w': return ; // Numeric representation of the day of the week eg. 0 (for Sunday) through 6 (for Saturday) + case 'z': return ; // The day of the year (starting from 0) eg. 0 through 365 + case 'W': return ; // ISO-8601 week number of year, weeks starting on Monday eg. Example: 42 (the 42nd week in the year) + case 'F': return ; // A full textual representation of a month, such as January or March eg. January through December + case 'm': return ; // Numeric representation of a month, with leading zeros eg. 01 through 12 + case 'M': return ; // A short textual representation of a month, three letters eg. Jan through Dec + case 'n': return ; // Numeric representation of a month, without leading zeros eg. 1 through 12 + case 't': return ; // Number of days in the given month eg. 28 through 31 + case 'L': return ; // Whether it's a leap year eg. 1 if it is a leap year, 0 otherwise. + case 'o': return ; // ISO-8601 week-numbering year. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0) eg. Examples: 1999 or 2003 + case 'Y': return ; // A full numeric representation of a year, 4 digits eg. Examples: 1999 or 2003 + case 'y': return ; // A two digit representation of a year eg. Examples: 99 or 03 + + // `time` related formats are missing! + + case 'U': return ; // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) eg. See also time() + + case 'quarter': return $this->month / 4 + 1; default: - if ( ! ctype_lower($name)) - $name = strtolower($name); + if ( ! ctype_upper($name)) + $name = strtoupper($name); + + // MySQL related values: + switch (strtoupper($name)) + { + case 'DAYNAME': // https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_dayname + case 'DAYOFWEEK': + case 'DAYOFMONTH': + case 'DAYOFYEAR': + + //--- Start of alias or mixed-case properties ---// + case 'YEAR': return substr($this->str, 0, 4); + case 'MONTH': return substr($this->str, 5, 2); + case 'DAY': return substr($this->str, 8, 2); + } + + $name = strtolower($name); if (self::$hashAlgos === null) self::$hashAlgos = array_flip(hash_algos()); // set the hash algorithms as keys for faster lookup with isset() instead of in_array()! if (isset(self::$hashAlgos[$name])) // we converted the hash name to lowercase above so we can safely support this: $this->Sha256 return hash($name, $this->str); - - //--- Start of alias and mixed-case properties ---// - - switch ($name) - { - // possible mixed-case variants `normalized` to lowercase. eg. `Scheme` => `scheme` - case 'length': return \strlen($this->str); - } } } + public function getIterator() + { + return new \Twister\DateIterator($this->str); + } + + public function getYear() + { + return substr($this->str, 0, 4); + } + + public function getMonth() + { + return substr($this->str, 5, 2); + } + + public function getDay() + { + return substr($this->str, 8, 2); + } + static function curdate($format = 'Y-m-d') { + //static $curdate = new static(date('Y-m-d')); + //return $curdate; + + // alternative return new static(date($format)); } + /** + * Returns true if the string contains a date in the format 'YYYY-MM-DD' AND is a valid Gregorian date + * + * All date patterns MUST have 3x (..) + * + * @alias isValid() + * @alias validate() + * + * Alternative patterns: + * '/^(\d\d\d\d)-(\d\d)-(\d\d)$/' + * '/^(\d{4})-(\d{2})-(\d{2}) [0-2][0-3]:[0-5][0-9]:[0-5][0-9]$/' + * '/^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/' + * + * @link http://php.net/manual/en/function.checkdate.php + * + * @return bool + */ + public static function isDate($date, $pattern = '~^([1-9]\d\d\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$~') + { + return preg_match($pattern, $date, $matches) === 1 && checkdate($matches[2], $matches[3], $matches[1]); // checkdate(month, day, year) + } + + /** + * Returns true if the string contains a date in the format 'YYYY-MM-DD' AND is a valid Gregorian date + * + * @alias isDate() + * @alias validate() + * + * Alternative patterns: + * '/^(\d\d\d\d)-(\d\d)-(\d\d)$/' + * '/^(\d{4})-(\d{2})-(\d{2}) [0-2][0-3]:[0-5][0-9]:[0-5][0-9]$/' + * '/^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/' + * + * @link http://php.net/manual/en/function.checkdate.php + * + * @return bool + */ + public static function isValid($date, $pattern = '~^([1-9]\d\d\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$~') + { + return preg_match($pattern, $date, $matches) === 1 && checkdate($matches[2], $matches[3], $matches[1]); // checkdate(month, day, year) + } + + /** + * Returns true if the string contains a date in the format 'YYYY-MM-DD' AND is a valid Gregorian date + * + * @alias isDate() + * @alias isValid() + * + * Alternative patterns: + * '/^(\d\d\d\d)-(\d\d)-(\d\d)$/' + * '/^(\d{4})-(\d{2})-(\d{2}) [0-2][0-3]:[0-5][0-9]:[0-5][0-9]$/' + * '/^([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/' + * + * @link http://php.net/manual/en/function.checkdate.php + * + * @return bool + */ + public static function validate($date, $pattern = '~^([1-9]\d\d\d)[- /.](0[1-9]|1[012])[- /.](0[1-9]|[12][0-9]|3[01])$~') + { + return preg_match($pattern, $date, $matches) === 1 && checkdate($matches[2], $matches[3], $matches[1]); // checkdate(month, day, year) + } + } + +\Twister\Date::$utc = new \DateTimeZone('UTC'); diff --git a/src/DateIterator.php b/src/DateIterator.php new file mode 100644 index 0000000..898b8d0 --- /dev/null +++ b/src/DateIterator.php @@ -0,0 +1,218 @@ +start = (is_string($date) ? new \DateTime($date, self::$utc) : $date); + $this->current = clone $this->start; + $this->interval = $interval_spec === null ? self::$p1d : (is_string($interval_spec) ? new \DateInterval($interval_spec) : $interval_spec); + } + + /** + * Returns the $current value as MySQL Date format ('YYYY-MM-DD') + * + * @return string The $current property + */ + public function __toString() + { + return $this->current->format('Y-m-d'); + } + + public function rewind() + { + $this->current = clone $this->start; + } + + public function current() + { + return $this->current->format('Y-m-d'); + } + + public function key() + { + return $this->start->diff($this->current)->format('%r%a'); + } + + public function next() + { + $this->current->add($this->interval); + return $this; + } + + public function prev() + { + $this->current->sub($this->interval); + return $this; + } + + public function valid() + { + return true; // basically allows the DateIterator to be infinite + } + + // Sets the `current` value! + public function toDate($date) + { + $this->current = (is_string($date) ? new \DateTime($date, self::$utc) : $date); + } + + public function getInterval() + { + return $this->interval; + } + + public function setInterval($interval_spec = 'P1D') + { + $this->interval = new \DateInterval($interval_spec); + return $this; + } + + /** + * Wrapper around DateTime::modify() + * + * `Alter the timestamp of a DateTime object by incrementing or decrementing in a format accepted by strtotime().` + * + * @link http://php.net/manual/en/datetime.modify.php + * @link http://php.net/manual/en/function.strtotime.php + * + * $return $this + */ + public function modify($modify = '+1 day') + { + $this->current->modify($modify); + return $this; + } + + /** + * Wrapper around \DateTime->add() + * + * @link http://php.net/manual/en/datetime.add.php + * @link http://php.net/manual/en/class.dateinterval.php + * @link http://php.net/manual/en/dateinterval.construct.php + * @link https://en.wikipedia.org/wiki/Iso8601#Durations + * + * Simple examples: + * Two days is P2D. + * Two seconds is PT2S. + * Six years and five minutes is P6YT5M. + * + * Formats are based on the » ISO 8601 duration specification. + * + * @param string $interval_spec The character encoding + * @return new Twister\Date isntance or false on failure + */ + public function add($interval_spec = 'P1D') + { + $this->str = (new \DateTime($this->str, new \DateTimeZone('UTC')))->add(new \DateInterval($interval_spec))->format('Y-m-d'); + return $this; + } + + /** + * Wrapper around \DateTime->sub() + * + * @link http://php.net/manual/en/datetime.sub.php + * @link http://php.net/manual/en/class.dateinterval.php + * @link http://php.net/manual/en/dateinterval.construct.php + * @link https://en.wikipedia.org/wiki/Iso8601#Durations + * + * Simple examples: + * Two days is P2D. + * Two seconds is PT2S. + * Six years and five minutes is P6YT5M. + * + * Formats are based on the » ISO 8601 duration specification. + * + * @param string $interval_spec The character encoding + * @return new Twister\Date isntance or false on failure + */ + public function sub($interval_spec = 'P1D') + { + $this->str = (new \DateTime($this->str, new \DateTimeZone('UTC')))->add(new \DateInterval($interval_spec))->format('Y-m-d'); + return $this; + } + + function __get($name) + { + switch ($name) + { + case 'year': return $this->current->format('Y'); + case 'month': return $this->current->format('m'); + case 'day': return $this->current->format('d'); + + // http://php.net/manual/en/function.date.php + case 'd': return $this->current->format('d'); // Day of the month, 2 digits with leading zeros eg. 01 to 31 + case 'D': return $this->current->format('D'); // A textual representation of a day, three letters eg. Mon through Sun + case 'j': return $this->current->format('j'); // Day of the month without leading zeros eg. 1 to 31 + case 'l': return $this->current->format('l'); // A full textual representation of the day of the week eg. Sunday through Saturday + case 'N': return $this->current->format('N'); // ISO-8601 numeric representation of the day of the week (added in PHP 5.1.0 eg. 1 (for Monday) through 7 (for Sunday) + case 'S': return $this->current->format('S'); // English ordinal suffix for the day of the month, 2 characters eg. st, nd, rd or th. Works well with j + case 'w': return $this->current->format('w'); // Numeric representation of the day of the week eg. 0 (for Sunday) through 6 (for Saturday) + case 'z': return $this->current->format('z'); // The day of the year (starting from 0) eg. 0 through 365 + case 'W': return $this->current->format('W'); // ISO-8601 week number of year, weeks starting on Monday eg. Example: 42 (the 42nd week in the year) + case 'F': return $this->current->format('F'); // A full textual representation of a month, such as January or March eg. January through December + case 'm': return $this->current->format('m'); // Numeric representation of a month, with leading zeros eg. 01 through 12 + case 'M': return $this->current->format('M'); // A short textual representation of a month, three letters eg. Jan through Dec + case 'n': return $this->current->format('n'); // Numeric representation of a month, without leading zeros eg. 1 through 12 + case 't': return $this->current->format('t'); // Number of days in the given month eg. 28 through 31 + case 'L': return $this->current->format('L'); // Whether it's a leap year eg. 1 if it is a leap year, 0 otherwise. + case 'o': return $this->current->format('o'); // ISO-8601 week-numbering year. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0) eg. Examples: 1999 or 2003 + case 'Y': return $this->current->format('Y'); // A full numeric representation of a year, 4 digits eg. Examples: 1999 or 2003 + case 'y': return $this->current->format('y'); // A two digit representation of a year eg. Examples: 99 or 03 + + // `time` related formats are missing! + + case 'U': return $this->current->format('U'); // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) eg. See also time() + + case 'quarter': return $this->current->format('m') / 4 + 1; + + default: + + if ( ! ctype_lower($name)) + $name = strtolower($name); + + if (self::$hashAlgos === null) + self::$hashAlgos = array_flip(hash_algos()); // set the hash algorithms as keys for faster lookup with isset() instead of in_array()! + + if (isset(self::$hashAlgos[$name])) // we converted the hash name to lowercase above so we can safely support this: $this->Sha256 + return hash($name, $this->str); + + //--- Start of alias and mixed-case properties ---// + + switch ($name) + { + // possible mixed-case variants `normalized` to lowercase. eg. `Scheme` => `scheme` + case 'length': return \strlen($this->str); + } + } + } + + function __set($name, $value) + { + switch ($name) + { + case 'year': return $this->encoding = mb_internal_encoding(); + case 'month': return strlen($this->_str); + case 'day': return strlen($this->_str); + } + } + +} + +DateIterator::$utc = new \DateTimeZone('UTC'); diff --git a/src/DatePeriod.php b/src/DatePeriod.php index 5cc4a3d..c4fa2a3 100644 --- a/src/DatePeriod.php +++ b/src/DatePeriod.php @@ -3,43 +3,32 @@ namespace Twister; use \DateTime; // http://php.net/manual/en/class.datetime.php +use \DateInterval; // http://php.net/manual/en/class.dateinterval.php +use \DateTimeZone; // http://php.net/manual/en/class.datetimezone.php +use \DatePeriod; // http://php.net/manual/en/class.dateperiod.php -use IteratorAggregate; // http://php.net/manual/en/class.iteratoraggregate.php Interface to create an external Iterator. +echo 'here 1'; -class DatePeriod extends DatePeriod implements IteratorAggregate +class DatePeriod extends \DatePeriod { - public function __construct($date) - { - - } + public static $utc = null; + public static $p1d = null; - public function __construct() + public function __construct($start, $interval_spec = null, $end = '9999-12-31') { - $this->position = 0; - } - - public function rewind() - { - $this->position = 0; - } - - public function current() - { - return $this->array[$this->position]; - } - - public function key() - { - return $this->position; + parent::__construct( is_string($start) ? new \DateTime($start, self::$utc) : $start, + $interval_spec === null ? self::$p1d : (is_string($interval_spec) ? new \DateInterval($interval_spec) : $interval_spec), + is_string($end) ? new \DateTime($end, self::$utc) : $end + ); } public function next() { - ++$this->position; - } - - public function valid() - { - return isset($this->array[$this->position]); +trigger_error('HERE'); + $this->current->add($this->interval); + return $this; } } + +DatePeriod::$utc = new \DateTimeZone('UTC'); +DatePeriod::$p1d = new \DateInterval('P1D'); diff --git a/src/DateTime.php b/src/DateTime.php index 0698092..c1a88f8 100644 --- a/src/DateTime.php +++ b/src/DateTime.php @@ -2309,3 +2309,5 @@ class DateTime implements Countable, IteratorAggregate, ArrayAccess } } + +DateTime::$utc = new \DateTimeZone('UTC'); diff --git a/src/Str.php b/src/Str.php index 561663a..f943245 100644 --- a/src/Str.php +++ b/src/Str.php @@ -5,6 +5,7 @@ use ArrayAccess; // http://php.net/manual/en/class.arrayaccess.php Interf use ArrayIterator; // http://php.net/manual/en/class.arrayiterator.php This iterator allows to unset and modify values and keys while iterating over Arrays and Objects. use Countable; // http://php.net/manual/en/class.countable.php Classes implementing Countable can be used with the count() function. use IteratorAggregate; // http://php.net/manual/en/class.iteratoraggregate.php Interface to create an external Iterator. +use Iterator; // http://php.net/manual/en/class.iterator.php Interface for external iterators or objects that can be iterated themselves internally. use Exception; // http://php.net/manual/en/class.exception.php Exception is the base class for all Exceptions in PHP 5, and the base class for all user exceptions in PHP 7. use InvalidArgumentException; // http://php.net/manual/en/class.invalidargumentexception.php Exception thrown if an argument is not of the expected type. use OutOfBoundsException; // http://php.net/manual/en/class.outofboundsexception.php Exception thrown if a value is not a valid key. This represents errors that cannot be detected at compile time.