Test Failed
Pull Request — master (#307)
by
unknown
03:23
created

RangeParser::parseDateRange()   B

Complexity

Conditions 7
Paths 16

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 8
c 1
b 0
f 0
nc 16
nop 4
dl 0
loc 15
rs 8.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Pgsql;
6
7
use DateInterval;
8
use DateTime;
9
use DateTimeInterface;
10
use InvalidArgumentException;
11
use function preg_match;
12
13
final class RangeParser
14
{
15
    private const RANGES = [
16
        Schema::TYPE_INT_RANGE,
17
        Schema::TYPE_BIGINT_RANGE,
18
        Schema::TYPE_NUM_RANGE,
19
        Schema::TYPE_TS_RANGE,
20
        Schema::TYPE_TS_TZ_RANGE,
21
        Schema::TYPE_DATE_RANGE,
22
    ];
23
24
    private ?string $type;
25
26
    public function __construct(?string $type = null)
27
    {
28
        $this->type = $type;
29
    }
30
31
    public function withType(?string $type): self
32
    {
33
        $new = clone $this;
34
        $new->type = $type;
35
36
        return $new;
37
    }
38
39
    public function asInt(): self
40
    {
41
        return $this->withType(Schema::TYPE_INT_RANGE);
42
    }
43
44
    public function asBigInt(): self
45
    {
46
        return $this->withType(Schema::TYPE_BIGINT_RANGE);
47
    }
48
49
    public function asNumeric(): self
50
    {
51
        return $this->withType(Schema::TYPE_NUM_RANGE);
52
    }
53
54
    public function asDate(): self
55
    {
56
        return $this->withType(Schema::TYPE_DATE_RANGE);
57
    }
58
59
    public function asTimestamp(): self
60
    {
61
        return $this->withType(Schema::TYPE_TS_RANGE);
62
    }
63
64
    public function asTimestampTz(): self
65
    {
66
        return $this->withType(Schema::TYPE_TS_TZ_RANGE);
67
    }
68
69
    public function asCustom(): self
70
    {
71
        return $this->withType(null);
72
    }
73
74
    /**
75
     * @param string|null $value
76
     * @return float[]|int[]|null[]|DateTimeInterface[]|null
77
     */
78
    public function parse(?string $value): ?array
79
    {
80
        if ($value === null || $value === 'empty') {
81
            return null;
82
        }
83
84
        if (!preg_match('/^(?P<open>\[|\()(?P<lower>[^,]*),(?P<upper>[^\)\]]*)(?P<close>\)|\])$/', $value, $matches)) {
85
            throw new InvalidArgumentException('Unsupported range format');
86
        }
87
88
        $lower = $matches['lower'] ? trim($matches['lower'], '"') : null;
89
        $upper = $matches['upper'] ? trim($matches['upper'], '"') : null;
90
        $includeLower = $matches['open'] === '[';
91
        $includeUpper = $matches['close'] === ']';
92
93
        if ($lower === null && $upper === null) {
94
            return [null, null];
95
        }
96
97
        return match($this->type) {
98
            Schema::TYPE_INT_RANGE => self::parseIntRange($lower, $upper, $includeLower, $includeUpper),
99
            Schema::TYPE_BIGINT_RANGE => self::parseBigIntRange($lower, $upper, $includeLower, $includeUpper),
100
            Schema::TYPE_NUM_RANGE => self::parseNumRange($lower, $upper),
101
            Schema::TYPE_DATE_RANGE => self::parseDateRange($lower, $upper, $includeLower, $includeUpper),
102
            Schema::TYPE_TS_RANGE => self::parseTsRange($lower, $upper),
103
            Schema::TYPE_TS_TZ_RANGE => self::parseTsTzRange($lower, $upper),
104
            default => [$lower, $upper]
105
        };
106
    }
107
108
    /**
109
     * @param string|null $lower
110
     * @param string|null $upper
111
     * @param bool $includeLower
112
     * @param bool $includeUpper
113
     * @return int[]|null[]
114
     */
115
    private static function parseIntRange(?string $lower, ?string $upper, bool $includeLower, bool $includeUpper): array
116
    {
117
        $min = $lower === null ? null : (int) $lower;
118
        $max = $upper === null ? null : (int) $upper;
119
120
        if ($min !== null && $includeLower === false) {
121
            $min += 1;
122
        }
123
124
        if ($max !== null && $includeUpper === false) {
125
            $max -= 1;
126
        }
127
128
        return [$min, $max];
129
    }
130
131
    /**
132
     * @param string|null $lower
133
     * @param string|null $upper
134
     * @param bool $includeLower
135
     * @param bool $includeUpper
136
     * @return float[]|int[]|null[]
137
     */
138
    private static function parseBigIntRange(?string $lower, ?string $upper, bool $includeLower, bool $includeUpper): array
139
    {
140
        if (PHP_INT_SIZE === 8) {
141
            return self::parseIntRange($lower, $upper, $includeLower, $includeUpper);
142
        }
143
144
        [$min, $max] = self::parseNumRange($lower, $upper);
145
146
        if ($min !== null && $includeLower === false) {
147
            /** @var float $min */
148
            $min += 1;
149
        }
150
151
        if ($max !== null && $includeUpper === false) {
152
            /** @var float $max */
153
            $max -= 1;
154
        }
155
156
        return [$min, $max];
157
    }
158
159
    /**
160
     * @param string|null $lower
161
     * @param string|null $upper
162
     * @return float[]|null[]
163
     */
164
    private static function parseNumRange(?string $lower, ?string $upper): array
165
    {
166
        $min = $lower === null ? null : (float) $lower;
167
        $max = $upper === null ? null : (float) $upper;
168
169
        return [$min, $max];
170
    }
171
172
    /**
173
     * @param string|null $lower
174
     * @param string|null $upper
175
     * @param bool $includeLower
176
     * @param bool $includeUpper
177
     * @return DateTimeInterface[]|null[]
178
     */
179
    private static function parseDateRange(?string $lower, ?string $upper, bool $includeLower, bool $includeUpper): array
180
    {
181
        $interval = new DateInterval('P1D');
182
        $min = $lower ? DateTime::createFromFormat('Y-m-d', $lower) : null;
183
        $max = $upper ? DateTime::createFromFormat('Y-m-d', $upper) : null;
184
185
        if ($min && $includeLower === false) {
186
            $min->add($interval);
187
        }
188
189
        if ($max && $includeUpper === false) {
190
            $max->sub($interval);
191
        }
192
193
        return [$min, $max];
194
    }
195
196
    /**
197
     * @param string|null $lower
198
     * @param string|null $upper
199
     * @return DateTimeInterface[]|null[]
200
     */
201
    private static function parseTsRange(?string $lower, ?string $upper): array
202
    {
203
        $min = $lower ? DateTime::createFromFormat('Y-m-d H:i:s', $lower) : null;
204
        $max = $upper ? DateTime::createFromFormat('Y-m-d H:i:s', $upper) : null;
205
206
        return [$min, $max];
207
    }
208
209
    /**
210
     * @param string|null $lower
211
     * @param string|null $upper
212
     * @return DateTimeInterface[]|null[]
213
     */
214
    private static function parseTsTzRange(?string $lower, ?string $upper): array
215
    {
216
        $min = $lower ? DateTime::createFromFormat('Y-m-d H:i:sP', $lower) : null;
217
        $max = $upper ? DateTime::createFromFormat('Y-m-d H:i:sP', $upper) : null;
218
219
        return [$min, $max];
220
    }
221
222
    public static function isAllowedType(string $type): bool
223
    {
224
        return in_array($type, self::RANGES, true);
225
    }
226
}
227