Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
DateFormatToRegex
100.00% covered (success)
100.00%
31 / 31
100.00% covered (success)
100.00%
3 / 3
11
100.00% covered (success)
100.00%
1 / 1
 formatToRegex
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 createRegex
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
8
 createTimezoneIdentifierRegex
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2namespace Apie\DateformatToRegex;
3
4use DateTime;
5use DateTimeZone;
6
7final class DateFormatToRegex
8{
9    // see https://www.php.net/manual/en/datetime.format.php
10    private const FORMATS = [
11        'd' => '((0[1-9])|([12][0-9])|(3[01]))', // days 01=>31
12        'j' => '(([1-9])|([12][0-9])|(3[01]))', // days 1=>31
13        'D' => '(Mon|Tue|Wed|Thu|Fri|Sat|Sun)', // mon-sun
14        'l' => '(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)', // monday-sunday
15        'N' => '[1-7]', // day of the week number 1-7
16        'w' => '[0-6]', // day of the week number 0-6
17        'S' => '(st|nd|rd|th)', // day suffix
18        'z' => '([0-9]|[1-9][0-9]|[12][0-9]{2}|3[0-5][0-9]|36[0-5])',  // day of the year 0-365
19        'W' =>  '(0[1-9]|[1-4][0-9]|5[0-3])', // week number 1-53
20        'F' => '(January|February|March|April|May|June|July|August|September|October|November|December)', // january-december
21        'M' => '(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)', //jan-dec
22        'm' => '((0[1-9])|(1[0-2]))', // months 01=>12
23        'n' => '([1-9]|1[0-2])', // months 1-12
24        't' => '(28|29|30|31)',  //number of days in month
25        'L' => '[01]', // is leap year
26        'o' => '((|\-)[0-9]{4})', // year of current week
27        'Y' => '((|\-)[0-9]{4})', // current year
28        'y' => '[0-9]{2}', // year in 2 digits
29        'a' => '(am|pm)', // am|pm
30        'A' => '(AM|PM)', // AM|PM
31        'B' => '[0-9]{3}', // swatch digit time
32        'g' => '([1-9]|1[0-2])', // hours 1-12
33        'h' => '((0[1-9])|(1[0-2]))', // hours 01-12
34        'G' => '([0-9]|1[0-9]|2[0-3])', // hours 0-23
35        'H' => '(([01][0-9])|(2[0-3]))', // hours 00-23
36        'i' => '[0-5][0-9]', // minutes 00-59
37        's' => '[0-5][0-9]', // minutes 00-59
38        'u' => '[0-9]{6}', // microseconds
39        'v' => '[0-9]{3}', // milliseconds
40        'I' => '[01]', // 0-1 is DST
41        'O' => '(\+\d{4})', // GMT difference, e.g. '+0200'
42        'P' => '(\+\d{2}:\d{2})', // GMT difference, e.g. '+02:00'
43        'p' => '(\+\d{2}:\d{2})|Z', // GMT difference, Z on +00:00
44        'Z' => '(-[1-9]|-[1-3][0-9]{4}|-4[0-2][0-9]{3}|-43[01][0-9]{2}|-43200|-?[1-9][0-9]{1,3}|[0-9]|[1-4][0-9]{4}|50[0-3][0-9]{2}|50400)', // GMT difference in seconds: -43200-50400
45        'U' => '((-|\+|)\d+)', // unix time stamp
46        'T' => '(GMT|EST|MDT)(\+|-|)\d{4}'
47    ];
48
49    public static function formatToRegex(string $formatString, string $delimiter = '/'): string
50    {
51        $regex = self::createRegex($formatString, $delimiter);
52        
53        return $delimiter . '^' . $regex . '$' . $delimiter;
54    }
55
56    private static function createRegex(string $formatString, string $delimiter = '/'): string
57    {
58        $nextIsLiteral = false;
59        $regex = '';
60        foreach (str_split($formatString) as $character) {
61            if ($nextIsLiteral) {
62                $regex .= preg_quote($character, $delimiter);
63                $nextIsLiteral = false;
64                continue;
65            }
66            if ($character === '\\') {
67                $nextIsLiteral = true;
68                continue;
69            }
70            if ($character === 'c') {
71                // https://www.php.net/manual/en/class.datetime.php#111532
72                $regex .= self::createRegex('Y-m-d\TH:i:sP');
73                continue;
74            }
75            if ($character === 'r') {
76                $regex .= self::createRegex(DateTime::RFC2822);
77                continue;
78            }
79            if ($character === 'e') { // timezone identifiers
80                $regex .= self::createTimezoneIdentifierRegex($delimiter);
81                continue;
82            }
83            if (isset(self::FORMATS[$character])) {
84                $regex .= self::FORMATS[$character];
85            } else {
86                $regex .= preg_quote($character, $delimiter);
87            }
88        }
89        return $regex;
90    }
91
92    public static function createTimezoneIdentifierRegex(string $delimiter = '/'): string
93    {
94        $regexes = [
95            '(\+\d{2}:\d{2})',
96        ];
97        foreach (DateTimeZone::listIdentifiers() as $identifier) {
98            $regexes[] = preg_quote($identifier, $delimiter);
99        }
100        return '(' . implode('|', $regexes) . ')';
101    }
102}