3v4l.org

run code in 300+ PHP versions simultaneously
<?php declare(strict_types = 1); /** * Value object representing a time interval. * */ namespace TimeParser; use \DateInterval; class IntervalFlags { const INTERVAL_ONLY = 0b00000000; const REQUIRE_TRAILING = 0b00000001; const REQUIRE_LEADING = 0b00000010; const MULTIPLE_INTERVALS = 0b00000100; } class TimeInterval { private $intervalOffset; private $intervalLength; private $interval; private $leadingData; private $trailingData; public function __construct( int $intervalOffset, int $intervalLength, DateInterval $interval, string $leadingData = null, string $trailingData = null ) { $this->interval = $interval; $this->intervalOffset = $intervalOffset; $this->intervalLength = $intervalLength; $this->leadingData = $leadingData; $this->trailingData = $trailingData; } public function getInterval() : DateInterval { return $this->interval; } public function getIntervalOffset() : int { return $this->intervalOffset; } public function getIntervalLength() : int { return $this->intervalLength; } public function getLeadingData() : string { return $this->leadingData; } public function getTrailingData() : string { return $this->trailingData; } } class TimeParser { /** * Set of regular expressions utilized to match/replace/validate parts of a given input * * Thanks a ton to @bwoebi and @pcrov for helping out on all regexes <3 * * If leading text is required, it should separate time and text by in * ie. foo in 9 weeks 5 days * * @var string $separatorExpression */ private static $leadingDataSeparator = "/(.*)\s+(?:in)\s+(.*)/ui"; # Definitions of sub patterns for a valid interval private static $intervalSeparatorDefinitions = <<<'REGEX' /(?(DEFINE) (?<integer> (?:\G|(?!\n)) (\s*\b)? \d{1,5} \s* ) (?<timepart> (?&integer) ( s(ec(ond)?s?)? | m(on(ths?)?|in(ute)?s?)? | h(rs?|ours?)? | d(ays?)? | w(eeks?)? ) ) ) REGEX; # Concatenate with above definitions before its use private static $intervalOnly = "^(?<interval>(?&timepart)++)$/uix"; private static $intervalWithTrailingData = "^(?<interval>(?&timepart)++)(?<trailing>.+)$/uix"; /** * Used to turn a given non-strtotime-compatible time string into a compatible one within preg_replace_callback * Only modifies the non-strtotime-compatible time strings provided leaving the rest intact * This requires interval offset to be 0, meaning no leading data is allowed. * * @var string $normalizerExpression */ private static $normalizerExpression = <<<'REGEX' ~ # grab the integer part of time string \s? (?<int> \d{1,5}) \s? # match only the shortest abbreviations that aren't supported by strtotime (?<time> (?: s (?=(?:ec(?:ond)?s?)?(?:\b|\d)) | m (?=(?:in(?:ute)?s?)?(?:\b|\d)) | h (?=(?:(?:ou)?rs?)?(?:\b|\d)) | d (?=(?:ays?)?(?:\b|\d)) | w (?=(?:eeks?)?(?:\b|\d)) | mon (?=(?:(?:th)?s?)?(?:\b|\d)) ) ) [^\d]*?(?=\b|\d) # do only extract start of string | (?<text> .+) ~uix REGEX; /** * Looks for a valid interval along with leading and/or trailing data IF the respective flags are set. * TimeInterval is essentially DateInterval with extra information such as interval offset & length, leading/trailing data. * TODO: MULTIPLE_INTERVALS is not yet implemented. * * @param string $input * @param int $flags * @return TimeInterval * @throws \Error|\InvalidArgumentException */ public function findInterval(string $input, int $flags = IntervalFlags::INTERVAL_ONLY) : TimeInterval { if($flags & IntervalFlags::INTERVAL_ONLY){ # If interval contains non-strtotime-compatible abbreviations, replace 'em $input = $this->normalizeTimeInterval($input); if(preg_match(self::$intervalOnly, $input)){ $intervalOffset = 0; $intervalLength = strlen($input); # create and return the interval object $interval = DateInterval::createFromDateString($input); return new TimeInterval($intervalOffset, $intervalLength, $interval, null, null); } throw new \InvalidArgumentException("Given input is not a valid interval."); } if($flags == (IntervalFlags::REQUIRE_LEADING | IntervalFlags::REQUIRE_TRAILING)){ # Requires the "in" separator, TODO: allow at|this|next too $leadingSeparation = preg_match(self::$leadingDataSeparator, $input, $matches, PREG_OFFSET_CAPTURE); if(!$leadingSeparation){ throw new \Error("Allowing leading data requires using a separator. Ie. foo in <interval>"); } $leadingData = $matches[1][0] ?? null; $intervalAndTrailingData = $matches[2][0] ?? null; # throw early for missing parts if(!$leadingData){ throw new \InvalidArgumentException("Given input does not contain a valid leading data."); } if(!$intervalAndTrailingData){ throw new \InvalidArgumentException("Given input does not contain a valid interval and/or trailing data."); } $intervalOffset = $matches[2][1] ?? null; # If interval contains non-strtotime-compatible abbreviations, replace 'em $intervalAndTrailingData = $this->normalizeTimeInterval($intervalAndTrailingData); $expression = self::$intervalSeparatorDefinitions . self::$intervalWithTrailingData; if(preg_match($expression, $intervalAndTrailingData, $parts)){ $interval = $parts['interval']; $trailingData = $parts['trailing']; $intervalLength = strlen($interval); # create and return the interval object $interval = DateInterval::createFromDateString($interval); return new TimeInterval($intervalOffset, $intervalLength, $interval, $leadingData, $trailingData); } throw new \InvalidArgumentException("Given input does not contain a valid interval and/or trailing data."); } if($flags & IntervalFlags::REQUIRE_LEADING){ # Requires the "in" separator, TODO: allow at|this|next too $leadingSeparation = preg_match(self::$leadingDataSeparator, $input, $matches, PREG_OFFSET_CAPTURE); if(!$leadingSeparation){ throw new \Error("Allowing leading data requires using a separator. Ie. foo in <interval>"); } $leadingData = $matches[1][0] ?? null; $intervalAndPossibleTrailingData = $matches[2][0] ?? null; if(!$leadingData || !$intervalAndPossibleTrailingData){ throw new \Error("Could not find any valid interval and/or leading data."); } $intervalOffset = $matches[2][1] ?? null; # If interval contains non-strtotime-compatible abbreviations, replace 'em $safeInterval = $this->normalizeTimeInterval($intervalAndPossibleTrailingData); # since above normalization is expected to not return any trailing data, only check for a valid interval $expression = self::$intervalSeparatorDefinitions . self::$intervalOnly; if(preg_match($expression, $safeInterval, $parts)){ $interval = $parts['interval']; $intervalLength = strlen($interval); # create the interval object $interval = DateInterval::createFromDateString($interval); return new TimeInterval($intervalOffset, $intervalLength, $interval, $leadingData, null); } throw new \InvalidArgumentException("Given input does not contain a valid interval. Keep in mind trailing data is not allowed with currently specified flag."); } if($flags & IntervalFlags::REQUIRE_TRAILING){ $expression = self::$intervalSeparatorDefinitions . self::$intervalWithTrailingData; # If interval contains non-strtotime-compatible abbreviations, replace 'em $safeInterval = $this->normalizeTimeInterval($input); # Separate interval from trailing data if(preg_match($expression, $safeInterval, $parts)){ $trailingData = $parts['trailing'] ?? null; $interval = $parts['interval'] ?? null; if(!$trailingData || !$interval){ throw new \Error("Could not find any valid interval and/or trailing data..."); } $intervalLength = strlen($interval); $intervalOffset = 0; # since we don't allow leading data here # create the interval object $interval = DateInterval::createFromDateString($interval); return new TimeInterval($intervalOffset, $intervalLength, $interval, null, $trailingData); } throw new \InvalidArgumentException("Given input does not contain a valid interval. Keep in mind leading data is not allowed with currently specified flag."); } if($flags & IntervalFlags::MULTIPLE_INTERVALS){ throw new \Error("I'm sorry, multiple intervals is not allowed/implemented, yet."); } # This bit is not tested if( $flags & ~IntervalFlags::INTERVAL_ONLY & ~IntervalFlags::REQUIRE_TRAILING & ~IntervalFlags::REQUIRE_LEADING & ~IntervalFlags::MULTIPLE_INTERVALS ){ throw new \InvalidArgumentException("You have tried to use an invalid flag combination."); } } /** * Turns any non-strtotime-compatible time string into a compatible one. * If the passed input has trailing data, it won't be lost since within the callback the input is reassembled. * However no leading data is accepted. * * @param string $input * @return string */ public function normalizeTimeInterval(string $input): string { $output = preg_replace_callback(self::$normalizerExpression, function ($matches) { switch ($matches['time']) { case 's': $t = ' seconds '; break; case 'm': $t = ' minutes '; break; case 'h': $t = ' hours '; break; case 'd': $t = ' days '; break; case 'w': $t = ' weeks '; break; case 'mon': $t = ' months '; break; case 'y': $t = ' years '; break; } $t = $t ?? ''; # rebuild the interval string $time = $matches['int'] . $t; if(isset($matches['text'])){ $time .= $matches['text']; } return $time; }, $input); return trim($output); } /** * Normalizes any non-strtotime-compatible time string, then validates the interval and returns a DateInterval object. * No leading or trailing data is accepted. * * @param string $input * @return DateInterval * @throws \InvalidArgumentException */ public function parseInterval(string $input): \DateInterval { $input = trim($this->normalizeTimeInterval($input)); $expression = self::$intervalSeparatorDefinitions . self::$intervalOnly; if(preg_match($expression, $input, $matches)){ return DateInterval::createFromDateString($input); } throw new \InvalidArgumentException("Given string is not a valid time interval."); } } $trailingString = '7mon6w5d4h3m2s bazinga!'; $leadingString = 'foo in 9w8d7h6m5s'; $both = 'foo in 9d8h5m bar'; $onlyInterval = '9 mon 2 w 3 m 4 d'; $intervalParser = new TimeParser(); #$timeIntervalWithTrailingData = $intervalParser->findInterval($trailingString, IntervalFlags::REQUIRE_TRAILING); #var_dump($timeIntervalWithTrailingData); #$timeIntervalWithLeadingData = $intervalParser->findInterval($leadingString, IntervalFlags::REQUIRE_LEADING); #var_dump($timeIntervalWithLeadingData); #$timeIntervalWithBoth = $intervalParser->findInterval($both, IntervalFlags::REQUIRE_TRAILING | IntervalFlags::REQUIRE_LEADING); #var_dump($timeIntervalWithBoth); $dateInterval = $intervalParser->parseInterval($onlyInterval); var_dump($dateInterval);

Here you find the average performance (time & memory) of each version. A grayed out version indicates it didn't complete successfully (based on exit-code).

VersionSystem time (s)User time (s)Memory (MiB)
8.3.70.0040.01217.13
8.3.60.0030.01217.09
8.3.50.0120.00721.34
8.3.40.0200.00019.32
8.3.30.0120.00319.29
8.3.20.0040.00420.50
8.3.10.0030.00621.02
8.3.00.0030.00522.03
8.2.190.0240.00017.38
8.2.180.0080.00825.92
8.2.170.0160.00319.65
8.2.160.0160.00622.96
8.2.150.0030.00525.66
8.2.140.0080.00024.66
8.2.130.0080.00026.16
8.2.120.0000.00821.04
8.2.110.0080.00320.81
8.2.100.0120.00018.28
8.2.90.0060.00319.72
8.2.80.0090.00018.41
8.2.70.0090.00018.25
8.2.60.0040.00418.16
8.2.50.0090.00018.30
8.2.40.0060.00320.60
8.2.30.0040.00419.63
8.2.20.0000.00818.52
8.2.10.0030.00618.40
8.2.00.0040.00418.38
8.1.280.0100.00625.92
8.1.270.0040.00423.99
8.1.260.0060.00326.35
8.1.250.0040.00428.09
8.1.240.0060.00322.49
8.1.230.0120.00018.00
8.1.220.0040.00418.04
8.1.210.0040.00718.77
8.1.200.0050.00518.10
8.1.190.0060.00317.98
8.1.180.0000.00818.10
8.1.170.0000.00819.02
8.1.160.0040.00422.32
8.1.150.0000.00819.28
8.1.140.0050.00319.83
8.1.130.0050.00317.98
8.1.120.0000.00818.02
8.1.110.0060.00318.02
8.1.100.0040.00417.86
8.1.90.0000.00817.89
8.1.80.0000.00817.97
8.1.70.0040.00417.95
8.1.60.0040.00717.99
8.1.50.0050.00517.88
8.1.40.0000.01017.99
8.1.30.0000.00818.12
8.1.20.0040.00418.01
8.1.10.0000.00818.05
8.1.00.0090.00017.91
8.0.300.0060.00320.20
8.0.290.0080.00017.38
8.0.280.0020.00518.73
8.0.270.0040.00417.52
8.0.260.0030.00317.69
8.0.250.0000.00817.59
8.0.240.0070.00017.59
8.0.230.0100.00017.66
8.0.220.0070.00017.59
8.0.210.0000.00717.45
8.0.200.0000.00717.73
8.0.190.0000.00817.50
8.0.180.0040.00417.50
8.0.170.0050.00517.48
8.0.160.0030.00617.55
8.0.150.0030.00617.56
8.0.140.0050.00317.51
8.0.130.0030.00313.96
8.0.120.0030.00617.61
8.0.110.0030.00617.64
8.0.100.0060.00317.47
8.0.90.0040.00417.54
8.0.80.0100.00617.66
8.0.70.0030.00717.50
8.0.60.0000.00917.44
8.0.50.0050.00317.48
8.0.30.0100.01017.69
8.0.20.0100.01117.65
8.0.10.0000.00817.71
8.0.00.0120.01217.38
7.4.330.0030.00315.39
7.4.320.0040.00417.09
7.4.300.0000.00817.16
7.4.290.0040.00416.98
7.4.280.0040.00417.14
7.4.270.0000.00717.11
7.4.260.0000.00717.09
7.4.250.0050.00317.18
7.4.240.0050.00317.15
7.4.230.0000.00717.16
7.4.220.0090.02217.07
7.4.210.0080.00717.11
7.4.200.0000.00717.10
7.4.160.0050.01217.04
7.4.150.0100.00917.40
7.4.140.0080.01117.86
7.4.130.0130.00717.14
7.4.120.0130.00717.01
7.4.110.0100.01016.96
7.4.100.0040.01617.07
7.4.90.0120.01516.94
7.4.80.0080.02319.39
7.4.70.0120.00917.08
7.4.60.0040.01417.06
7.4.50.0040.01416.94
7.4.40.0080.01216.88
7.4.30.0160.01016.98
7.4.00.0110.00715.63
7.3.330.0030.00313.87
7.3.320.0060.00013.89
7.3.310.0040.00416.75
7.3.300.0040.00316.92
7.3.290.0020.00516.71
7.3.280.0090.01216.86
7.3.270.0140.00917.40
7.3.260.0140.00717.07
7.3.250.0130.01017.05
7.3.240.0130.00716.91
7.3.230.0160.00316.91
7.3.210.0110.00716.93
7.3.200.0120.00817.05
7.3.190.0040.01817.04
7.3.180.0100.01416.92
7.3.170.0140.00517.01
7.3.160.0160.00616.90
7.3.10.0070.00717.07
7.3.00.0030.01316.95
7.2.330.0040.02117.04
7.2.320.0150.00917.12
7.2.310.0160.00317.10
7.2.300.0000.01917.09
7.2.290.0080.01217.09
7.2.130.0080.00417.26
7.2.120.0060.00917.28
7.2.110.0040.00717.21
7.2.100.0070.00317.25
7.2.90.0030.00717.21
7.2.80.0030.01017.41
7.2.70.0030.00617.17
7.2.60.0040.01317.34
7.2.50.0000.01817.46
7.2.40.0090.00317.34
7.2.30.0120.00317.48
7.2.20.0090.00917.39
7.2.10.0030.01317.43
7.2.00.0030.01218.63
7.1.250.0080.00415.96
7.1.200.0030.00915.98
7.1.100.0000.01418.52
7.1.70.0000.01117.57
7.1.60.0030.01317.59
7.1.50.0120.00917.46
7.1.00.0070.07322.30
7.0.200.0310.00715.10
7.0.110.0230.06720.37
7.0.100.0430.06020.38
7.0.90.0770.09020.46
7.0.80.0470.05720.41
7.0.70.0300.05720.50
7.0.60.0270.07020.47
7.0.50.0300.06020.45
7.0.40.0330.06320.39
7.0.30.0470.05020.39
7.0.20.0470.05320.41
7.0.10.0300.05720.46
7.0.00.0400.06020.49
5.6.260.0200.04321.07
5.6.250.0030.06721.08
5.6.240.0100.05720.87
5.6.230.0100.06021.12
5.6.220.0130.05720.83
5.6.210.0130.05320.97
5.6.200.0130.06021.05
5.6.190.0170.06721.04
5.6.180.0100.06321.01
5.6.170.0070.05721.01
5.6.160.0130.06020.85
5.6.150.0070.06320.77
5.6.140.0070.06320.76
5.6.130.0030.06320.85
5.6.120.0100.07020.85
5.6.110.0100.06721.09
5.6.100.0100.05720.92
5.6.90.0030.06020.75
5.6.80.0170.05320.20
5.6.70.0100.05720.42
5.6.60.0130.05320.24
5.6.50.0100.05320.32
5.6.40.0130.05020.39
5.6.30.0200.04720.13
5.6.20.0130.06320.12
5.6.10.0170.04720.22
5.6.00.0100.05720.10

preferences:
22.92 ms | 401 KiB | 5 Q