PluginProbe
The Innovative Form Builder – IvyForms / 1.4.1
The Innovative Form Builder – IvyForms v1.4.1
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / backend / src / Common / Helpers / DateTimeHelper / DateTimeHelper.php

DateTimeHelper.php in The Innovative Form Builder – IvyForms 1.4.1, at backend/src/Common/Helpers/DateTimeHelper/DateTimeHelper.php

233 lines 7.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace IvyForms\Common\Helpers\DateTimeHelper;
4
5 use DateTimeImmutable;
6 use DateTimeZone;
7 use RuntimeException;
8 use IvyForms\Common\Exceptions\InvalidArgumentException;
9 use IvyForms\Services\Translations\BackendStrings;
10
11 /**
12 * Helper utilities for combined date-time operations.
13 */
14 class DateTimeHelper
15 {
16 /**
17 * Get WordPress timezone.
18 *
19 * @return DateTimeZone
20 * @throws RuntimeException If unable to get valid timezone
21 */
22 public static function getWpTimezone(): DateTimeZone
23 {
24 if (!function_exists('wp_timezone')) {
25 throw new RuntimeException(BackendStrings::getExceptionStrings()['wp_timezone_not_exist']);
26 }
27
28 return wp_timezone();
29 }
30
31 /**
32 * Convert datetime string (YYYY-MM-DD|HH:MM[:SS] or YYYY-MM-DD HH:MM[:SS]) to Unix timestamp.
33 *
34 * @param mixed $value DateTime value or numeric timestamp
35 * @return int|null Unix timestamp or null if invalid
36 */
37 public static function dateTimeToTimestamp($value): ?int
38 {
39 if ($value === null || $value === '') {
40 return null;
41 }
42
43 if (is_numeric($value)) {
44 return (int) $value;
45 }
46
47 $dateTimeString = str_replace('|', ' ', trim((string) $value));
48
49 try {
50 $timezone = self::getWpTimezone();
51 $expectedFormats = [
52 'Y-m-d H:i:s',
53 'Y-m-d H:i',
54 ];
55
56 foreach ($expectedFormats as $expectedFormat) {
57 $timestamp = self::parseTimestampByFormat($dateTimeString, $expectedFormat, $timezone);
58
59 if ($timestamp !== null) {
60 return $timestamp;
61 }
62 }
63
64 return null;
65 } catch (\Throwable $exception) {
66 return null;
67 }
68 }
69
70 /**
71 * Parse a datetime string against one format and return timestamp when fully valid.
72 *
73 * @param string $dateTimeString Datetime string to parse
74 * @param string $expectedFormat Expected datetime format
75 * @param DateTimeZone $timezone Timezone used for parsing
76 * @return int|null Unix timestamp or null when parsing fails
77 */
78 private static function parseTimestampByFormat(
79 string $dateTimeString,
80 string $expectedFormat,
81 DateTimeZone $timezone
82 ): ?int {
83 $dateTime = DateTimeImmutable::createFromFormat($expectedFormat, $dateTimeString, $timezone);
84
85 if ($dateTime === false || self::hasParsingIssues(DateTimeImmutable::getLastErrors())) {
86 return null;
87 }
88
89 if ($dateTime->format($expectedFormat) !== $dateTimeString) {
90 return null;
91 }
92
93 return $dateTime->getTimestamp();
94 }
95
96 /**
97 * Check if DateTime parsing reported warnings or errors.
98 *
99 * @param array<string, mixed>|false $errors Parsing errors from DateTimeImmutable::getLastErrors()
100 * @return bool
101 */
102 private static function hasParsingIssues($errors): bool
103 {
104 if (!is_array($errors)) {
105 return false;
106 }
107
108 return $errors['warning_count'] > 0 || $errors['error_count'] > 0;
109 }
110
111 /**
112 * Normalize datetime values to Unix timestamp format.
113 * Accepts single values or range arrays and returns the storage string representation.
114 *
115 * @param mixed $value Field value
116 * @return string Normalized value as string
117 * @throws InvalidArgumentException If an array value is not a valid two-element start/end range
118 */
119 public static function normalizeDateTimeValue($value): string
120 {
121 if ($value === null || $value === '') {
122 return '';
123 }
124
125 if (is_array($value)) {
126 if (self::isEmptyDateTimeRangeValue($value)) {
127 return '';
128 }
129
130 return self::normalizeArrayOfDateTimes($value);
131 }
132
133 return (string) (self::dateTimeToTimestamp($value) ?? $value);
134 }
135
136 /**
137 * Whether a two-element range array represents an empty (unset) range.
138 *
139 * @param mixed $value
140 * @return bool
141 */
142 public static function isEmptyDateTimeRangeValue($value): bool
143 {
144 if (!is_array($value) || count($value) !== 2) {
145 return false;
146 }
147
148 $items = array_values($value);
149
150 return self::isEmptyDateTimeRangeItem($items[0] ?? null)
151 && self::isEmptyDateTimeRangeItem($items[1] ?? null);
152 }
153
154 /**
155 * Whether a single range element represents an unset (empty) value.
156 * Non-string falsey values (e.g. false) are not treated as empty.
157 *
158 * @param mixed $item
159 * @return bool
160 */
161 private static function isEmptyDateTimeRangeItem($item): bool
162 {
163 if ($item === null || $item === '') {
164 return true;
165 }
166
167 if (!is_string($item)) {
168 return false;
169 }
170
171 return trim($item) === '';
172 }
173
174 /**
175 * Format stored datetime values for frontend display.
176 * Delegates to DateTimeFormatter.
177 *
178 * @param mixed $value Stored field value
179 * @return string Formatted datetime value
180 */
181 public static function formatDateTimeValue($value): string
182 {
183 return DateTimeFormatter::formatDateTimeValue($value);
184 }
185
186 /**
187 * Format a stored datetime value using the field's configured date/time format.
188 * Delegates to DateTimeFormatter.
189 *
190 * @param mixed $value Stored field value (timestamp, canonical string, or comma-separated range)
191 * @param string $dateFormat JS-style date format token (e.g. MM/DD/YYYY, DD.MM.YYYY, YYYY-MM-DD)
192 * @param string $timeFormat 'ampm', '24h', 'ampm-s', or '24h-s'
193 * @return string
194 */
195 public static function formatDateTimeWithOptions(
196 $value,
197 string $dateFormat,
198 string $timeFormat
199 ): string {
200 return DateTimeFormatter::formatDateTimeWithOptions($value, $dateFormat, $timeFormat);
201 }
202
203 /**
204 * Normalize a two-element datetime range array to a comma-separated timestamp string.
205 * The array must contain exactly two elements: a valid start and a valid end datetime.
206 *
207 * @param array<int, mixed> $items Array of exactly two datetime values (start, end)
208 * @return string Comma-separated timestamp string
209 * @throws InvalidArgumentException If the array does not have exactly two elements or either is invalid
210 */
211 private static function normalizeArrayOfDateTimes(array $items): string
212 {
213 $values = array_values($items);
214
215 if (count($values) !== 2) {
216 throw new InvalidArgumentException(
217 BackendStrings::getExceptionStrings()['invalid_datetime_range']
218 );
219 }
220
221 $start = self::dateTimeToTimestamp($values[0]);
222 $end = self::dateTimeToTimestamp($values[1]);
223
224 if ($start === null || $end === null) {
225 throw new InvalidArgumentException(
226 BackendStrings::getExceptionStrings()['invalid_datetime_range']
227 );
228 }
229
230 return $start . ', ' . $end;
231 }
232 }
233