Completed
Push — master ( 8ce34a...26baa3 )
by Marcus
03:49
created

DateTypeTrait::extractDateParts()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 9
cts 9
cp 1
rs 9.6
c 0
b 0
f 0
cc 2
nc 2
nop 3
crap 2
1
<?php
2
3
namespace Mbright\Validation\Rule\Validate\MySql;
4
5
/**
6
 * Functions to parse a given string to extract year, month, and day parts.
7
 *
8
 * A regex pattern (excluding the surrounding /'s) must be provided that should match all valid delimiters that can
9
 * separate year, month, and day parts. Optionally, YYMMDD strings be sanitized to have a 4 digit year YYYYMMDD format.
10
 *
11
 * A null return indicates that the string could not be parsed into YYMMDD or YYYYMMDD.
12
 */
13
trait DateTypeTrait
14
{
15
    /**
16
     * Extract date parts from the given string.
17
     *
18
     * Will return a stdClass with 3 properties: year, month, day. Each property defaults to null. If the string cannot
19
     * be parsed, return null.
20
     *
21
     * @param string $dateString
22
     * @param string $delimiterPattern regex pattern to use when searching for delimiters (excluding the surrounding /'s
23
     * @param bool normalizeYear indicates if a two digit year should get normalized to a 4 digit year
24
     *
25
     * @return null|\stdClass
26
     */
27 63
    protected function extractDateParts(string $dateString, string $delimiterPattern, bool $normalizeYear): ?\stdClass {
28
        $extractedParts = (object) [
29 63
            'year' => null,
30
            'month' => null,
31
            'day' => null
32
        ];
33
34 63
        $sanitizedDateString = $this->sanitizeDateString($dateString, $delimiterPattern, $normalizeYear);
35
36 63
        if (is_null($sanitizedDateString)) {
37 9
            return null;
38
        }
39
40
        // extract the individual date parts for checkdate
41 54
        $extractedParts->year = substr($sanitizedDateString, 0, 4);
42 54
        $extractedParts->month = substr($sanitizedDateString, 4, 2);
43 54
        $extractedParts->day = substr($sanitizedDateString, 6, 2);
44
45 54
        return $extractedParts;
46
    }
47
48
    /**
49
     * Sanitizes the given dateString to YYYYMMDD.
50
     *
51
     * Returns null if the string cannot be sanitized.
52
     *
53
     * @param string $dateString
54
     * @param string $delimiterPattern regex pattern to use when searching for delimiters (excluding the surrounding /'s
55
     * @param bool normalizeYear indicates if a two digit year should get normalized to a 4 digit year
56
     *
57
     * @return null|string
58
     */
59 63
    private function sanitizeDateString(string $dateString, string $delimiterPattern, bool $normalizeYear): ?string
60
    {
61 63
        $sanitizedString = $this->handleDelimiters($dateString, $delimiterPattern);
62
63 63
        if ($sanitizedString && $normalizeYear) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $sanitizedString of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
64 60
            $sanitizedString = $this->normalizeYear($sanitizedString);
65
66
            // More than 8 characters here means we don't have the format YYYYMMDD
67 60
            if (strlen($sanitizedString) !== 8) {
68 6
                return null;
69
            }
70
        }
71
72 57
        return $sanitizedString;
73
    }
74
75
    /**
76
     * Strips delimiters from the given date string and returns the sanitized string.
77
     *
78
     * The returned string will be either
79
     *
80
     * @param string $dateString
81
     * @param string $delimiterPattern regex pattern to use when searching for delimiters (excluding the surrounding /'s
82
     *
83
     * @return null|string
84
     */
85 63
    private function handleDelimiters(string $dateString, string $delimiterPattern): ?string
86
    {
87
        // Grab everything that isn't an int, these things must be delimiters
88 63
        $delimiters = [];
89 63
        preg_match_all("/\D/", $dateString, $delimiters);
90
91 63
        if (count($delimiters[0]) > 1) {
92
            // ensure that all delimiters are valid delimiters accepted by mysql
93 42
            $delimiterString = implode('', $delimiters[0]);
94
95 42
            if (preg_match("/$delimiterPattern/", $delimiterString) > 0) {
96 3
                return null;
97
            }
98
99
            // sanitize valid punctuation characters that are being used as delimiters
100 39
            $delimiterString = preg_quote($delimiterString, '/');
101
            // Remove delimiters to make parsing date segments easier
102 39
            $dateString = preg_replace("/[$delimiterString]/", '', $dateString);
103
        }
104
105 60
        return $dateString;
106
    }
107
108
    /**
109
     * Converts YYMMDD to YYYYMMDD.
110
     *
111
     * @param string $dateString
112
     *
113
     * @return null|string
114
     */
115 60
    private function normalizeYear(string $dateString): ?string
116
    {
117
        // if we have a two digit year, convert it to a 4 digit year for the sake of comparison
118 60
        if (strlen($dateString) === 6) {
119 9
            $twoDigitYear = substr($dateString, 0, 2);
120 9
            $yearPrefix = 0 < $twoDigitYear && $twoDigitYear < 69 ? '20' : '19';
121 9
            $dateString = $yearPrefix . $dateString;
122
        }
123
124 60
        return $dateString;
125
    }
126
}
127