Passed
Push — master ( f509b3...5b74be )
by Eric
12:34 queued 10:26
created

Dates::validTimezone()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Utility - Collection of various PHP utility functions.
7
 *
8
 * @author    Eric Sizemore <[email protected]>
9
 * @version   2.0.0
10
 * @copyright (C) 2017 - 2024 Eric Sizemore
11
 * @license   The MIT License (MIT)
12
 *
13
 * Copyright (C) 2017 - 2024 Eric Sizemore <https://www.secondversion.com>.
14
 *
15
 * Permission is hereby granted, free of charge, to any person obtaining a copy
16
 * of this software and associated documentation files (the "Software"), to
17
 * deal in the Software without restriction, including without limitation the
18
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
19
 * sell copies of the Software, and to permit persons to whom the Software is
20
 * furnished to do so, subject to the following conditions:
21
 *
22
 * The above copyright notice and this permission notice shall be included in
23
 * all copies or substantial portions of the Software.
24
 *
25
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31
 * THE SOFTWARE.
32
 */
33
34
namespace Esi\Utility;
35
36
// Exceptions
37
use Exception;
38
use InvalidArgumentException;
39
use RuntimeException;
40
41
// Classes
42
use DateTimeZone;
43
use DateTime;
44
45
// Functions
46
use function ceil;
47
use function time;
48
49
/**
50
 * Date utilities.
51
 */
52
final class Dates
53
{
54
    /**
55
     * Regex used to validate a given timestamp.
56
     *
57
     * @var string VALIDATE_TIMESTAMP_REGEX
58
     */
59
    public const VALIDATE_TIMESTAMP_REGEX = '/^\d{8,11}$/';
60
61
    /**
62
     * timeDifference()
63
     *
64
     * Formats the difference between two timestamps to be human-readable.
65
     *
66
     * @param   int     $timestampFrom  Starting unix timestamp.
67
     * @param   int     $timestampTo    Ending unix timestamp.
68
     * @param   string  $timezone       The timezone to use. Must be a valid timezone:
69
     *                                  {@see http://www.php.net/manual/en/timezones.php}
70
     * @param   string  $append         The string to append to the difference.
71
     * @return  string
72
     *
73
     * @throws  InvalidArgumentException|RuntimeException|Exception
74
     */
75 3
    public static function timeDifference(int $timestampFrom, int $timestampTo = 0, string $timezone = 'UTC', string $append = ' old'): string
76
    {
77 3
        if ($timezone === '') {
78 1
            $timezone = 'UTC';
79
        }
80
81 3
        if (!Dates::validTimezone($timezone)) {
82 1
            throw new RuntimeException('$timezone appears to be invalid.');
83
        }
84
85
        // Normalize timestamps
86 2
        $timestampTo = (Dates::validateTimestamp($timestampTo)) ? $timestampTo : time();
87 2
        $timestampFrom = (Dates::validateTimestamp($timestampFrom)) ? $timestampFrom : time();
88
89
        // This will generally only happen if the $timestampFrom was 0, or if it's invalid, as it is set to time();
90
        // as is $timestampTo if left at 0
91 2
        if ($timestampFrom >= $timestampTo) {
92 1
            throw new InvalidArgumentException('$timestampFrom needs to be less than $timestampTo.');
93
        }
94
95
        // Create DateTime objects and set timezone
96 1
        $timestampFrom = (new DateTime('@' . $timestampFrom))->setTimezone(new DateTimeZone($timezone));
97 1
        $timestampTo = (new DateTime('@' . $timestampTo))->setTimezone(new DateTimeZone($timezone));
98
99
        // Calculate difference
100 1
        $difference = $timestampFrom->diff($timestampTo);
101
102
        // Format the difference
103 1
        $string = match (true) {
104 1
            $difference->y > 0 => $difference->y . ' year(s)',
105 1
            $difference->m > 0 => $difference->m . ' month(s)',
106 1
            $difference->d >= 7 => ceil($difference->d / 7) . ' week(s)',
107 1
            $difference->d > 0 => $difference->d . ' day(s)',
108 1
            $difference->h > 0 => $difference->h . ' hour(s)',
109 1
            $difference->i > 0 => $difference->i . ' minute(s)',
110 1
            $difference->s > 0 => $difference->s . ' second(s)',
111 1
            default => ''
112 1
        };
113
114 1
        return $string . $append;
115
    }
116
117
    /**
118
     * timezoneInfo()
119
     *
120
     * Retrieves information about a timezone.
121
     *
122
     * Note: Must be a valid timezone recognized by PHP.
123
     *
124
     * @see http://www.php.net/manual/en/timezones.php
125
     *
126
     * @param   string  $timezone  The timezone to return information for.
127
     * @return  array<string, bool|float|int|string|null>
128
     *
129
     * @throws  InvalidArgumentException|RuntimeException|Exception
130
     */
131 1
    public static function timezoneInfo(string $timezone = 'UTC'): array
132
    {
133 1
        if ($timezone === '') {
134 1
            $timezone = 'UTC';
135
        }
136
137 1
        if (!Dates::validTimezone($timezone)) {
138 1
            throw new RuntimeException('$timezone appears to be invalid.');
139
        }
140
141 1
        $dateTimeZone = new DateTimeZone($timezone);
142
143 1
        $location = $dateTimeZone->getLocation();
144
145 1
        return [
146 1
            'offset'    => $dateTimeZone->getOffset(new DateTime('now', new DateTimeZone('GMT'))) / 3600,
147 1
            'country'   => $location['country_code'] ?? 'N/A',
148 1
            'latitude'  => $location['latitude'] ?? 'N/A',
149 1
            'longitude' => $location['longitude'] ?? 'N/A',
150 1
            'dst'       => $dateTimeZone->getTransitions(time(), time())[0]['isdst'] ?? null,
151 1
        ];
152
    }
153
154
    /**
155
     * Determines if a given timezone is valid, according to
156
     * {@link http://www.php.net/manual/en/timezones.php}.
157
     *
158
     * @param   string  $timezone  The timezone to validate.
159
     * @return  bool
160
     */
161 5
    public static function validTimezone(string $timezone): bool
162
    {
163 5
        static $validTimezones;
164
165 5
        $validTimezones ??= DateTimeZone::listIdentifiers();
166 5
        return Arrays::exists($validTimezones, $timezone);
167
    }
168
169
    /**
170
     * Determines if a given timestamp matches the valid range that is typically
171
     * found in a unix timestamp (at least in PHP).
172
     *
173
     * Typically, a timestamp for PHP can be valid if it is either 0 or between 8 and 11 digits in length.
174
     *
175
     * @param   int   $timestamp  The timestamp to validate.
176
     * @return  bool
177
     */
178 2
    public static function validateTimestamp(int $timestamp): bool
179
    {
180 2
        if ($timestamp === 0 || $timestamp < 0) {
181 2
            return false;
182
        }
183 1
        return (preg_match(self::VALIDATE_TIMESTAMP_REGEX, (string) $timestamp) === 1);
184
    }
185
}
186