Passed
Push — master ( e6d102...7dc300 )
by Eric
12:39
created

Dates::timeDifference()   B

Complexity

Conditions 6
Paths 18

Size

Total Lines 40
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 21
nc 18
nop 4
dl 0
loc 40
rs 8.9617
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
     * timeDifference()
56
     *
57
     * Formats the difference between two timestamps to be human-readable.
58
     *
59
     * @param   int     $timestampFrom  Starting unix timestamp.
60
     * @param   int     $timestampTo    Ending unix timestamp.
61
     * @param   string  $timezone       The timezone to use. Must be a valid timezone:
62
     *                                  {@see http://www.php.net/manual/en/timezones.php}
63
     * @param   string  $append         The string to append to the difference.
64
     * @return  string
65
     *
66
     * @throws  InvalidArgumentException|RuntimeException|Exception
67
     */
68
    public static function timeDifference(int $timestampFrom, int $timestampTo = 0, string $timezone = 'UTC', string $append = ' old'): string
69
    {
70
        if ($timezone === '') {
71
            $timezone = 'UTC';
72
        }
73
74
        if (!Dates::validTimezone($timezone)) {
75
            throw new RuntimeException('$timezone appears to be invalid.');
76
        }
77
78
        // Normalize timestamps
79
        $timestampTo = (Dates::validateTimestamp($timestampTo)) ? $timestampTo : time();
80
        $timestampFrom = (Dates::validateTimestamp($timestampFrom)) ? $timestampFrom : time();
81
82
        // This will generally only happen if the $timestampFrom was 0, or if it's invalid, as it is set to time();
83
        // as is $timestampTo if left at 0
84
        if ($timestampFrom >= $timestampTo) {
85
            throw new InvalidArgumentException('$timestampFrom needs to be less than $timestampTo.');
86
        }
87
88
        // Create DateTime objects and set timezone
89
        $timestampFrom = (new DateTime('@' . $timestampFrom))->setTimezone(new DateTimeZone($timezone));
90
        $timestampTo = (new DateTime('@' . $timestampTo))->setTimezone(new DateTimeZone($timezone));
91
92
        // Calculate difference
93
        $difference = $timestampFrom->diff($timestampTo);
94
95
        // Format the difference
96
        $string = match (true) {
97
            $difference->y > 0 => $difference->y . ' year(s)',
98
            $difference->m > 0 => $difference->m . ' month(s)',
99
            $difference->d >= 7 => ceil($difference->d / 7) . ' week(s)',
100
            $difference->d > 0 => $difference->d . ' day(s)',
101
            $difference->h > 0 => $difference->h . ' hour(s)',
102
            $difference->i > 0 => $difference->i . ' minute(s)',
103
            $difference->s > 0 => $difference->s . ' second(s)',
104
            default => ''
105
        };
106
107
        return $string . $append;
108
    }
109
110
    /**
111
     * timezoneInfo()
112
     *
113
     * Retrieves information about a timezone.
114
     *
115
     * Note: Must be a valid timezone recognized by PHP.
116
     *
117
     * @see http://www.php.net/manual/en/timezones.php
118
     *
119
     * @param   string  $timezone  The timezone to return information for.
120
     * @return  array<string, bool|float|int|string|null>
121
     *
122
     * @throws  InvalidArgumentException|RuntimeException|Exception
123
     */
124
    public static function timezoneInfo(string $timezone = 'UTC'): array
125
    {
126
        if ($timezone === '') {
127
            $timezone = 'UTC';
128
        }
129
130
        if (!Dates::validTimezone($timezone)) {
131
            throw new RuntimeException('$timezone appears to be invalid.');
132
        }
133
134
        $dateTimeZone = new DateTimeZone($timezone);
135
136
        $location = $dateTimeZone->getLocation();
137
138
        return [
139
            'offset'    => $dateTimeZone->getOffset(new DateTime('now', new DateTimeZone('GMT'))) / 3600,
140
            'country'   => $location['country_code'] ?? 'N/A',
141
            'latitude'  => $location['latitude'] ?? 'N/A',
142
            'longitude' => $location['longitude'] ?? 'N/A',
143
            'dst'       => $dateTimeZone->getTransitions(time(), time())[0]['isdst'] ?? null,
144
        ];
145
    }
146
147
    /**
148
     * Determines if a given timezone is valid, according to
149
     * {@link http://www.php.net/manual/en/timezones.php}.
150
     *
151
     * @param   string  $timezone  The timezone to validate.
152
     * @return  bool
153
     */
154
    public static function validTimezone(string $timezone): bool
155
    {
156
        static $validTimezones;
157
158
        $validTimezones ??= DateTimeZone::listIdentifiers();
159
        return Arrays::exists($validTimezones, $timezone);
160
    }
161
162
    /**
163
     * Determines if a given timestamp matches the valid range that is typically
164
     * found in a unix timestamp (at least in PHP).
165
     *
166
     * Typically, a timestamp for PHP can be valid if it is either 0 or between 8 and 11 digits in length.
167
     *
168
     * @param   int   $timestamp  The timestamp to validate.
169
     * @return  bool
170
     */
171
    public static function validateTimestamp(int $timestamp): bool
172
    {
173
        if ($timestamp === 0 || $timestamp < 0) {
174
            return false;
175
        }
176
        return (preg_match('/^\d{8,11}$/', (string) $timestamp) === 1);
177
    }
178
}
179