Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
7.14% covered (danger)
7.14%
9 / 126
6.25% covered (danger)
6.25%
1 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
DateUtils
7.14% covered (danger)
7.14%
9 / 126
6.25% covered (danger)
6.25%
1 / 16
1666.33
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getIsoArchiveThreshold
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getCurrentDateInFormat
53.33% covered (warning)
53.33%
8 / 15
0.00% covered (danger)
0.00%
0 / 1
14.50
 getIsoToday
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getIsoNow
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 sanitizeDatetimeValue
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 sanitizeDateValue
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 isoDateTime
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 isoDate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 compactDate
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 compactTime
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 olzDate
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
2
 getTimestamp
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 formatDateTimeRange
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
90
 parseDateTimeRange
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 getDayRange
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace Olz\Utils;
4
5class DateUtils {
6    use WithUtilsTrait;
7
8    public const WEEKDAYS_SHORT_DE = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
9    public const WEEKDAYS_LONG_DE = ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"];
10    public const MONTHS_SHORT_DE = ["Jan.", "Feb.", "März", "April", "Mai", "Juni", "Juli", "Aug.", "Sept.", "Okt.", "Nov.", "Dez."];
11    public const MONTHS_LONG_DE = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"];
12
13    public const ARCHIVE_YEARS_THRESHOLD = 4;
14
15    public function __construct(protected ?string $date = null) {
16    }
17
18    public function getIsoArchiveThreshold(): string {
19        $years_ago = intval($this->getCurrentDateInFormat('Y')) - self::ARCHIVE_YEARS_THRESHOLD;
20        return "{$years_ago}-01-01";
21    }
22
23    public function getCurrentDateInFormat(string $format): string {
24        if ($this->date !== null) {
25            if ($this->date === 'live') {
26                return date($format);
27            }
28            return date($format, @strtotime($this->date) ?: null);
29        }
30        $class_name = $this->envUtils()->getDateUtilsClassName();
31        $class_args = $this->envUtils()->getDateUtilsClassArgs();
32
33        if ($class_name == 'FixedDateUtils') {
34            $fixed_date = $class_args[0];
35            $fixed_date = is_int($fixed_date)
36                ? $fixed_date
37                : (@strtotime($fixed_date) ?: null);
38            return date($format, $fixed_date);
39        }
40        if ($class_name == 'LiveDateUtils') {
41            return date($format);
42        }
43        throw new \Exception("Date class must be FixedDateUtils or LiveDateUtils, was: {$class_name}");
44    }
45
46    public function getIsoToday(): string {
47        return $this->getCurrentDateInFormat('Y-m-d');
48    }
49
50    public function getIsoNow(): string {
51        return $this->getCurrentDateInFormat('Y-m-d H:i:s');
52    }
53
54    public function sanitizeDatetimeValue(string|\DateTime|null $value): ?\DateTime {
55        if ($value == null) {
56            return null;
57        }
58        if ($value instanceof \DateTime) {
59            return $value;
60        }
61        $res = preg_match('/[0-9]+\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/', $value);
62        if (!$res) {
63            throw new \Exception("Invalid datetime: {$value}", 1);
64        }
65        $datetime = \DateTime::createFromFormat('Y-m-d H:i:s', $value);
66        if (!$datetime) {
67            throw new \Exception("Invalid datetime: {$value}", 1);
68        }
69        return $datetime;
70    }
71
72    public function sanitizeDateValue(string|\DateTime|null $value): ?\DateTime {
73        if ($value == null) {
74            return null;
75        }
76        if ($value instanceof \DateTime) {
77            return $value;
78        }
79        $res = preg_match('/[0-9]+\-[0-9]{2}\-[0-9]{2}/', $value);
80        if (!$res) {
81            throw new \Exception("Invalid datetime: {$value}", 1);
82        }
83        $datetime = \DateTime::createFromFormat('Y-m-d', $value);
84        if (!$datetime) {
85            throw new \Exception("Invalid datetime: {$value}", 1);
86        }
87        return $datetime;
88    }
89
90    public function isoDateTime(string|\DateTime|null $date = null): string {
91        $timestamp = $this->getTimestamp($date);
92        return date("Y-m-d H:i:s", $timestamp);
93    }
94
95    public function isoDate(string|\DateTime|null $date = null): string {
96        $timestamp = $this->getTimestamp($date);
97        return date("Y-m-d", $timestamp);
98    }
99
100    public function compactDate(string|\DateTime|null $date = null): string {
101        return $this->olzDate("W,\xc2\xa0tt.mm.", $date);
102    }
103
104    public function compactTime(string|\DateTime|null $date = null): string {
105        $timestamp = $this->getTimestamp($date);
106        return date("H:i", $timestamp);
107    }
108
109    public function olzDate(string $format, string|\DateTime|null $date = null): string {
110        $date = $this->getTimestamp($date);
111
112        return str_replace(
113            [
114                "tt",
115                "t",
116                "mm",
117                "m",
118                "MM",
119                "M",
120                "xxxxx",
121                "jjjj",
122                "jj",
123                "w",
124                "WW",
125                "W",
126            ],
127            [
128                date("d", $date),
129                date("j", $date),
130                date("m", $date),
131                date("n", $date),
132                "xxxxx",
133                self::MONTHS_SHORT_DE[date("n", $date) - 1],
134                self::MONTHS_LONG_DE[date("n", $date) - 1],
135                date("Y", $date),
136                date("y", $date),
137                date("w", $date),
138                self::WEEKDAYS_LONG_DE[date("w", $date)],
139                self::WEEKDAYS_SHORT_DE[date("w", $date)],
140            ],
141            $format
142        );
143    }
144
145    protected function getTimestamp(string|\DateTime|null $date = null): int {
146        if ($date == null || $date == '') {
147            $date = $this->getIsoNow();
148        }
149        if ($date instanceof \DateTime) {
150            $date = $date->format(\DateTime::ATOM);
151        }
152        $timestamp = strtotime($date);
153        $this->generalUtils()->checkNotFalse($timestamp, "No timestamp for {$date}");
154        return $timestamp;
155    }
156
157    public function formatDateTimeRange(
158        string $start_date,
159        ?string $start_time,
160        ?string $end_date,
161        ?string $end_time,
162        string $format = 'long',
163    ): string {
164        if (!$end_date) {
165            $end_date = $start_date;
166        }
167        $out = '';
168        // Date
169        if ($end_date == $start_date) {
170            // Eintägig
171            $out = $this->olzDate('WW, t. MM jjjj', $start_date);
172        } else {
173            $weekday_prefix = $this->olzDate('WW', $start_date).' – '.$this->olzDate('WW', $end_date).', ';
174            if ($this->olzDate('jjjj-m', $start_date) == $this->olzDate('jjjj-m', $end_date)) {
175                // Mehrtägig, innerhalb Monat
176                $out = $weekday_prefix.$this->olzDate('t.', $start_date).' – '.$this->olzDate('t. ', $end_date).$this->olzDate('MM jjjj', $start_date);
177            } elseif ($this->olzDate('jjjj', $start_date) == $this->olzDate('jjjj', $end_date)) {
178                // Mehrtägig, innerhalb Jahr
179                $out = $weekday_prefix.$this->olzDate('t. MM', $start_date).' – '.$this->olzDate('t. MM jjjj', $end_date);
180            } else {
181                // Mehrtägig, jahresübergreifend
182                $out = $weekday_prefix.$this->olzDate('t. MM jjjj', $start_date).' – '.$this->olzDate('t. MM jjjj', $end_date);
183            }
184        }
185        // Time
186        if ($start_time) {
187            $out .= ' '.date('H:i', strtotime($start_time) ?: null);
188            if ($end_time) {
189                $out .= ' – '.date('H:i', strtotime($end_time) ?: null);
190            }
191        }
192        return $out;
193    }
194
195    /** @return ?array{start:\DateTime, end:\DateTime} */
196    public function parseDateTimeRange(string $input): ?array {
197        // Year (e.g. 2020)
198        if (preg_match("/^(2[0-9]{3})$/", $input)) {
199            $year = intval($input);
200            $next_year = $year + 1;
201            return [
202                'start' => new \DateTime("{$year}-01-01 00:00:00"),
203                'end' => new \DateTime("{$next_year}-01-01 00:00:00"),
204            ];
205        }
206        // ISO Day (e.g. 2020-03-13)
207        if (preg_match("/^(2[0-9]{3})\\-([01][0-9])\\-([0123][0-9])$/", $input)) {
208            return $this->getDayRange($input);
209        }
210        // Swiss Day (e.g. 13.3.2020)
211        if (preg_match("/^([0123]?[0-9])\\.\\s*([01]?[0-9])\\.\\s*(2[0-9]{3})$/", $input, $matches)) {
212            $day = str_pad($matches[1], 2, '0', STR_PAD_LEFT);
213            $month = str_pad($matches[2], 2, '0', STR_PAD_LEFT);
214            return $this->getDayRange("{$matches[3]}-{$month}-{$day}");
215        }
216        return null;
217    }
218
219    /** @return ?array{start:\DateTime, end:\DateTime} */
220    protected function getDayRange(string $iso_date): ?array {
221        $day_iso = substr($iso_date, 0, 10);
222        $day_start = new \DateTime("{$day_iso} 00:00:00");
223        $plus_one_day = \DateInterval::createFromDateString("+1 days");
224        $next_day_start = (new \DateTime("{$day_iso} 00:00:00"))->add($plus_one_day);
225        return [
226            'start' => $day_start,
227            'end' => $next_day_start,
228        ];
229    }
230}