/** * @test * @dataProvider sampleHaystackStringsAndNeedleStrings */ public function stringIsFoundAtBeginningOfAnotherString($haystack, $needle, $comparison) { $expectedResult = $comparison === 'beginning' || $comparison === 'both' ? TRUE : FALSE; $result = \TYPO3\Flow\I18n\Utility::stringBeginsWith($haystack, $needle); $this->assertEquals($expectedResult, $result); }
/** * Parses date and / or time in strict mode. * * @param string $datetimeToParse Date/time to be parsed * @param array $parsedFormat Format parsed by DatesReader * @param array $localizedLiterals Array of date / time literals from CLDR * @return array Array of parsed date and / or time elements, FALSE on failure * @throws \TYPO3\Flow\I18n\Exception\InvalidArgumentException When unexpected symbol found in format * @see \TYPO3\Flow\I18n\Cldr\Reader\DatesReader */ protected function doParsingInStrictMode($datetimeToParse, array $parsedFormat, array $localizedLiterals) { $datetimeElements = array('year' => null, 'month' => null, 'day' => null, 'hour' => null, 'minute' => null, 'second' => null, 'timezone' => null); $using12HourClock = false; $timeIsPm = false; try { foreach ($parsedFormat as $subformat) { if (is_array($subformat)) { // This is literal string, should match exactly if (\TYPO3\Flow\I18n\Utility::stringBeginsWith($datetimeToParse, $subformat[0])) { $datetimeToParse = substr_replace($datetimeToParse, '', 0, strlen($subformat[0])); continue; } else { return false; } } $lengthOfSubformat = strlen($subformat); $numberOfCharactersToRemove = 0; switch ($subformat[0]) { case 'h': case 'K': $hour = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 1, 12); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $hour < 10 ? 1 : 2; if ($subformat[0] === 'h' && $hour === 12) { $hour = 0; } $datetimeElements['hour'] = $hour; $using12HourClock = true; break; case 'k': case 'H': $hour = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 1, 24); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $hour < 10 ? 1 : 2; if ($subformat[0] === 'k' && $hour === 24) { $hour = 0; } $datetimeElements['hour'] = $hour; break; case 'a': $dayPeriods = $localizedLiterals['dayPeriods']['format']['wide']; if (\TYPO3\Flow\I18n\Utility::stringBeginsWith($datetimeToParse, $dayPeriods['am'])) { $numberOfCharactersToRemove = strlen($dayPeriods['am']); } elseif (\TYPO3\Flow\I18n\Utility::stringBeginsWith($datetimeToParse, $dayPeriods['pm'])) { $timeIsPm = true; $numberOfCharactersToRemove = strlen($dayPeriods['pm']); } else { return false; } break; case 'm': $minute = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 0, 59); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $minute < 10 ? 1 : 2; $datetimeElements['minute'] = $minute; break; case 's': $second = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 0, 59); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $second < 10 ? 1 : 2; $datetimeElements['second'] = $second; break; case 'd': $dayOfTheMonth = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 1, 31); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $dayOfTheMonth < 10 ? 1 : 2; $datetimeElements['day'] = $dayOfTheMonth; break; case 'M': case 'L': $typeOfLiteral = $subformat[0] === 'L' ? 'stand-alone' : 'format'; if ($lengthOfSubformat <= 2) { $month = $this->extractAndCheckNumber($datetimeToParse, $lengthOfSubformat === 2, 1, 12); $numberOfCharactersToRemove = $lengthOfSubformat === 1 && $month < 10 ? 1 : 2; } elseif ($lengthOfSubformat <= 4) { $lengthOfLiteral = $lengthOfSubformat === 3 ? 'abbreviated' : 'wide'; $month = 0; foreach ($localizedLiterals['months'][$typeOfLiteral][$lengthOfLiteral] as $monthId => $monthName) { if (\TYPO3\Flow\I18n\Utility::stringBeginsWith($datetimeToParse, $monthName)) { $month = $monthId; break; } } } else { throw new \TYPO3\Flow\I18n\Exception\InvalidArgumentException('Cannot parse formats with narrow month pattern as it is not unique.', 1279965245); } if ($month === 0) { return false; } $datetimeElements['month'] = $month; break; case 'y': if ($lengthOfSubformat === 2) { /** @todo How should the XX date be returned? Like 19XX? **/ $year = substr($datetimeToParse, 0, 2); $numberOfCharactersToRemove = 2; } else { $year = substr($datetimeToParse, 0, $lengthOfSubformat); $datetimeToParseLength = strlen($datetimeToParse); for ($i = $lengthOfSubformat; $i < $datetimeToParseLength; ++$i) { if (is_numeric($datetimeToParse[$i])) { $year .= $datetimeToParse[$i]; } else { break; } } $numberOfCharactersToRemove = $i; } if (!is_numeric($year)) { return false; } $year = (int) $year; $datetimeElements['year'] = $year; break; case 'v': case 'z': if ($lengthOfSubformat <= 3) { $pattern = self::PATTERN_MATCH_STRICT_TIMEZONE_ABBREVIATION; } else { $pattern = self::PATTERN_MATCH_STRICT_TIMEZONE_TZ; } if (preg_match($pattern, $datetimeToParse, $matches) !== 1) { return false; } $datetimeElements['timezone'] = $matches[0]; break; case 'D': case 'F': case 'w': case 'W': case 'Q': case 'q': case 'G': case 'S': case 'E': case 'Y': case 'u': case 'l': case 'g': case 'e': case 'c': case 'A': case 'Z': case 'V': // Silently ignore unsupported formats or formats that there is no need to parse break; default: throw new \TYPO3\Flow\I18n\Exception\InvalidArgumentException('Unexpected format symbol, "' . $subformat[0] . '" detected for date / time parsing.', 1279965528); } if ($using12HourClock && $timeIsPm) { $datetimeElements['hour'] += 12; $timeIsPm = false; } if ($numberOfCharactersToRemove > 0) { $datetimeToParse = substr_replace($datetimeToParse, '', 0, $numberOfCharactersToRemove); } } } catch (\TYPO3\Flow\I18n\Parser\Exception\InvalidParseStringException $exception) { // Method extractAndCheckNumber() throws exception when constraints in $datetimeToParse are not fulfilled return false; } return $datetimeElements; }
/** * Parses number in lenient mode. * * Lenient parsing ignores everything that can be ignored, and tries to * extract number from the string, even if it's not well formed. * * Implementation is simple but should work more often than strict parsing. * * Algorithm: * 1. Find first digit * 2. Find last digit * 3. Find decimal separator between first and last digit (if any) * 4. Remove non-digits from integer part * 5. Remove non-digits from decimal part (optional) * 6. Try to match negative prefix before first digit * 7. Try to match negative suffix after last digit * * @param string $numberToParse Number to be parsed * @param array $parsedFormat Parsed format (from NumbersReader) * @param array $localizedSymbols An array with symbols to use * @return mixed Parsed float number or FALSE on failure */ protected function doParsingInLenientMode($numberToParse, array $parsedFormat, array $localizedSymbols) { $numberIsNegative = false; $positionOfFirstDigit = null; $positionOfLastDigit = null; $charactersOfNumberString = str_split($numberToParse); foreach ($charactersOfNumberString as $position => $character) { if (ord($character) >= 48 && ord($character) <= 57) { $positionOfFirstDigit = $position; break; } } if ($positionOfFirstDigit === null) { return false; } krsort($charactersOfNumberString); foreach ($charactersOfNumberString as $position => $character) { if (ord($character) >= 48 && ord($character) <= 57) { $positionOfLastDigit = $position; break; } } $positionOfDecimalSeparator = strrpos($numberToParse, $localizedSymbols['decimal'], $positionOfFirstDigit); if ($positionOfDecimalSeparator === false) { $integerPart = substr($numberToParse, $positionOfFirstDigit, $positionOfLastDigit - $positionOfFirstDigit + 1); $decimalPart = false; } else { $integerPart = substr($numberToParse, $positionOfFirstDigit, $positionOfDecimalSeparator - $positionOfFirstDigit); $decimalPart = substr($numberToParse, $positionOfDecimalSeparator + 1, $positionOfLastDigit - $positionOfDecimalSeparator); } $parsedNumber = (int) preg_replace(self::PATTERN_MATCH_NOT_DIGITS, '', $integerPart); if ($decimalPart !== false) { $decimalPart = (int) preg_replace(self::PATTERN_MATCH_NOT_DIGITS, '', $decimalPart); $parsedNumber = (double) ($parsedNumber . '.' . $decimalPart); } $partBeforeNumber = substr($numberToParse, 0, $positionOfFirstDigit); $partAfterNumber = substr($numberToParse, -(strlen($numberToParse) - $positionOfLastDigit - 1)); if (!empty($parsedFormat['negativePrefix']) && !empty($parsedFormat['negativeSuffix'])) { if (\TYPO3\Flow\I18n\Utility::stringEndsWith($partBeforeNumber, $parsedFormat['negativePrefix']) && \TYPO3\Flow\I18n\Utility::stringBeginsWith($partAfterNumber, $parsedFormat['negativeSuffix'])) { $numberIsNegative = true; } } elseif (!empty($parsedFormat['negativePrefix']) && \TYPO3\Flow\I18n\Utility::stringEndsWith($partBeforeNumber, $parsedFormat['negativePrefix'])) { $numberIsNegative = true; } elseif (!empty($parsedFormat['negativeSuffix']) && \TYPO3\Flow\I18n\Utility::stringBeginsWith($partAfterNumber, $parsedFormat['negativeSuffix'])) { $numberIsNegative = true; } $parsedNumber /= $parsedFormat['multiplier']; if ($numberIsNegative) { $parsedNumber = 0 - $parsedNumber; } return $parsedNumber; }