PluginProbe
The Innovative Form Builder – IvyForms / 1.5
The Innovative Form Builder – IvyForms v1.5
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
← All changes | backend/src/Common/Helpers/DateTimeHelper/DateTimeHelper.php +133 -144 1.1 → 1.5 View file →
@@ -3,241 +3,230 @@
3 3 namespace IvyForms\Common\Helpers\DateTimeHelper;
4 4
5 5 use DateTimeImmutable;
6 6 use DateTimeZone;
7 -use Exception;
8 7 use RuntimeException;
8 +use IvyForms\Common\Exceptions\InvalidArgumentException;
9 9 use IvyForms\Services\Translations\BackendStrings;
10 10
11 11 /**
12 - * Helper utilities for date and time operations.
12 + * Helper utilities for combined date-time operations.
13 13 */
14 14 class DateTimeHelper
15 15 {
16 16 /**
17 - * Convert time string (HH:MM or HH:MM:SS) to Unix timestamp using current date in WP timezone.
17 + * Get WordPress timezone.
18 18 *
19 - * @param mixed $value Time value (HH:MM, HH:MM:SS, or numeric timestamp)
20 - * @return int|null Unix timestamp or null if invalid
19 + * @return DateTimeZone
20 + * @throws RuntimeException If unable to get valid timezone
21 21 */
22 - public static function timeToTimestamp($value): ?int
22 + public static function getWpTimezone(): DateTimeZone
23 23 {
24 - if ($value === null || $value === '') {
25 - return null;
24 + if (!function_exists('wp_timezone')) {
25 + throw new RuntimeException(BackendStrings::getExceptionStrings()['wp_timezone_not_exist']);
26 26 }
27 27
28 - // If already numeric (timestamp), validate and return
29 - if (is_numeric($value)) {
30 - return (int)$value;
31 - }
32 -
33 - $timeString = trim((string)$value);
34 - return self::parseTimeStringToTimestamp($timeString);
28 + return wp_timezone();
35 29 }
36 30
37 31 /**
38 - * Parse time string (HH:MM or HH:MM:SS) to Unix timestamp.
32 + * Convert datetime string (YYYY-MM-DD|HH:MM[:SS] or YYYY-MM-DD HH:MM[:SS]) to Unix timestamp.
39 33 *
40 - * @param string $timeString Time string to parse
34 + * @param mixed $value DateTime value or numeric timestamp
41 35 * @return int|null Unix timestamp or null if invalid
42 36 */
43 - private static function parseTimeStringToTimestamp(string $timeString): ?int
37 + public static function dateTimeToTimestamp($value): ?int
44 38 {
45 - if (!preg_match('/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/', $timeString, $matches)) {
39 + if ($value === null || $value === '') {
46 40 return null;
47 41 }
48 42
49 - $hours = (int)$matches[1];
50 - $minutes = (int)$matches[2];
51 - $seconds = isset($matches[3]) ? (int)$matches[3] : 0;
52 -
53 - if (!self::isValidTimeComponents($hours, $minutes, $seconds)) {
54 - return null;
43 + if (is_numeric($value)) {
44 + return (int) $value;
55 45 }
56 46
57 - return self::createTimestampFromTime($hours, $minutes, $seconds);
58 - }
47 + $dateTimeString = str_replace('|', ' ', trim((string) $value));
59 48
60 - /**
61 - * Validate time components.
62 - *
63 - * @param int $hours Hours (0-23)
64 - * @param int $minutes Minutes (0-59)
65 - * @param int $seconds Seconds (0-59)
66 - * @return bool True if valid
67 - */
68 - private static function isValidTimeComponents(int $hours, int $minutes, int $seconds): bool
69 - {
70 - return $hours <= 23 && $minutes <= 59 && $seconds <= 59;
71 - }
72 -
73 - /**
74 - * Create Unix timestamp from time components using today's date.
75 - *
76 - * @param int $hours Hours
77 - * @param int $minutes Minutes
78 - * @param int $seconds Seconds
79 - * @return int Unix timestamp
80 - * @throws RuntimeException If unable to create timestamp
81 - */
82 - private static function createTimestampFromTime(int $hours, int $minutes, int $seconds): int
83 - {
84 49 try {
85 50 $timezone = self::getWpTimezone();
86 - $today = new DateTimeImmutable('today', $timezone);
87 - $dateString = $today->format('Y-m-d');
88 - $timeString = sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds);
89 - $dateTime = new DateTimeImmutable("$dateString $timeString", $timezone);
90 - return $dateTime->getTimestamp();
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;
91 65 } catch (\Throwable $exception) {
92 - throw new RuntimeException(
93 - BackendStrings::getExceptionStrings()['time_conversion_failed'] . ': ' . $exception->getMessage()
94 - );
66 + return null;
95 67 }
96 68 }
97 69
98 70 /**
99 - * Format Unix timestamp back to time string (HH:MM).
71 + * Parse a datetime string against one format and return timestamp when fully valid.
100 72 *
101 - * @param int $timestamp Unix timestamp
102 - * @return string Formatted time string without seconds
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
103 77 */
104 - public static function timestampToTime(int $timestamp): string
105 - {
106 - $timezone = self::getWpTimezone();
107 - $dateTime = new DateTimeImmutable('@' . $timestamp);
108 - $dateTime = $dateTime->setTimezone($timezone);
109 - return $dateTime->format('H:i');
110 - }
78 + private static function parseTimestampByFormat(
79 + string $dateTimeString,
80 + string $expectedFormat,
81 + DateTimeZone $timezone
82 + ): ?int {
83 + $dateTime = DateTimeImmutable::createFromFormat($expectedFormat, $dateTimeString, $timezone);
111 84
112 - /**
113 - * Format Unix timestamp back to time string with seconds (HH:MM:SS).
114 - *
115 - * @param int $timestamp Unix timestamp
116 - * @return string Formatted time string with seconds
117 - */
118 - public static function timestampToTimeWithSeconds(int $timestamp): string
119 - {
120 - $timezone = self::getWpTimezone();
121 - $dateTime = new DateTimeImmutable('@' . $timestamp);
122 - $dateTime = $dateTime->setTimezone($timezone);
123 - return $dateTime->format('H:i:s');
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();
124 94 }
125 95
126 96 /**
127 - * Get WordPress timezone.
97 + * Check if DateTime parsing reported warnings or errors.
128 98 *
129 - * @return DateTimeZone
130 - * @throws RuntimeException If unable to get valid timezone
99 + * @param array<string, mixed>|false $errors Parsing errors from DateTimeImmutable::getLastErrors()
100 + * @return bool
131 101 */
132 - public static function getWpTimezone(): DateTimeZone
102 + private static function hasParsingIssues($errors): bool
133 103 {
134 - if (!function_exists('wp_timezone')) {
135 - throw new RuntimeException(BackendStrings::getExceptionStrings()['wp_timezone_not_exist']);
104 + if (!is_array($errors)) {
105 + return false;
136 106 }
137 107
138 - return wp_timezone();
108 + return $errors['warning_count'] > 0 || $errors['error_count'] > 0;
139 109 }
140 110
141 111 /**
142 - * Normalize time values to Unix timestamp format.
112 + * Normalize datetime values to Unix timestamp format.
113 + * Accepts single values or range arrays and returns the storage string representation.
143 114 *
144 - * @param int|string|null $value Field value
115 + * @param mixed $value Field value
145 116 * @return string Normalized value as string
117 + * @throws InvalidArgumentException If an array value is not a valid two-element start/end range
146 118 */
147 - public static function normalizeTimeValue($value): string
119 + public static function normalizeDateTimeValue($value): string
148 120 {
149 121 if ($value === null || $value === '') {
150 122 return '';
151 123 }
152 124
153 - return (string)(self::timeToTimestamp($value) ?? $value);
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);
154 134 }
155 135
156 136 /**
157 - * Convert date string (YYYY-MM-DD or various formats) to Unix timestamp.
137 + * Whether a two-element range array represents an empty (unset) range.
158 138 *
159 - * @param mixed $value Date value (YYYY-MM-DD, or numeric timestamp)
160 - * @return int|null Unix timestamp or null if invalid
139 + * @param mixed $value
140 + * @return bool
161 141 */
162 - public static function dateToTimestamp($value): ?int
142 + public static function isEmptyDateTimeRangeValue($value): bool
163 143 {
164 - if ($value === null || $value === '') {
165 - return null;
144 + if (!is_array($value) || count($value) !== 2) {
145 + return false;
166 146 }
167 147
168 - // If already numeric (timestamp), validate and return
169 - if (is_numeric($value)) {
170 - return (int)$value;
171 - }
148 + $items = array_values($value);
172 149
173 - $dateString = trim((string)$value);
174 - return self::parseDateStringToTimestamp($dateString);
150 + return self::isEmptyDateTimeRangeItem($items[0] ?? null)
151 + && self::isEmptyDateTimeRangeItem($items[1] ?? null);
175 152 }
176 153
177 154 /**
178 - * Parse date string to Unix timestamp.
155 + * Whether a single range element represents an unset (empty) value.
156 + * Non-string falsey values (e.g. false) are not treated as empty.
179 157 *
180 - * @param string $dateString Date string to parse
181 - * @return int|null Unix timestamp or null if invalid
158 + * @param mixed $item
159 + * @return bool
182 160 */
183 - public static function parseDateStringToTimestamp(string $dateString): ?int
161 + private static function isEmptyDateTimeRangeItem($item): bool
184 162 {
185 - try {
186 - $timezone = self::getWpTimezone();
187 - $dateTime = new DateTimeImmutable($dateString, $timezone);
163 + if ($item === null || $item === '') {
164 + return true;
165 + }
188 166
189 - // Set time to midnight (00:00:00) for date-only values
190 - $dateTime = $dateTime->setTime(0, 0, 0);
167 + if (!is_string($item)) {
168 + return false;
169 + }
191 170
192 - return $dateTime->getTimestamp();
193 - } catch (\Throwable $exception) {
194 - return null;
195 - }
171 + return trim($item) === '';
196 172 }
197 173
198 174 /**
199 - * Format Unix timestamp back to date string (YYYY-MM-DD).
175 + * Format stored datetime values for frontend display.
176 + * Delegates to DateTimeFormatter.
200 177 *
201 - * @param int $timestamp Unix timestamp
202 - * @return string Formatted date string
203 - * @throws Exception
178 + * @param mixed $value Stored field value
179 + * @return string Formatted datetime value
204 180 */
205 - public static function timestampToDate(int $timestamp): string
181 + public static function formatDateTimeValue($value): string
206 182 {
207 - $timezone = self::getWpTimezone();
208 - $dateTime = new DateTimeImmutable('@' . $timestamp);
209 - $dateTime = $dateTime->setTimezone($timezone);
210 - return $dateTime->format('Y-m-d');
183 + return DateTimeFormatter::formatDateTimeValue($value);
211 184 }
212 185
213 186 /**
214 - * Format Unix timestamp to a custom date format.
187 + * Format a stored datetime value using the field's configured date/time format.
188 + * Delegates to DateTimeFormatter.
215 189 *
216 - * @param int $timestamp Unix timestamp
217 - * @param string $format PHP date format string
218 - * @return string Formatted date string
219 - * @throws Exception
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
220 194 */
221 - public static function timestampToDateFormat(int $timestamp, string $format): string
222 - {
223 - $timezone = self::getWpTimezone();
224 - $dateTime = new DateTimeImmutable('@' . $timestamp);
225 - $dateTime = $dateTime->setTimezone($timezone);
226 - return $dateTime->format($format);
195 + public static function formatDateTimeWithOptions(
196 + $value,
197 + string $dateFormat,
198 + string $timeFormat
199 + ): string {
200 + return DateTimeFormatter::formatDateTimeWithOptions($value, $dateFormat, $timeFormat);
227 201 }
228 202
229 203 /**
230 - * Normalize date values to Unix timestamp format.
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.
231 206 *
232 - * @param int|string|null $value Field value
233 - * @return string Normalized value as string
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
234 210 */
235 - public static function normalizeDateValue($value): string
211 + private static function normalizeArrayOfDateTimes(array $items): string
236 212 {
237 - if ($value === null || $value === '') {
238 - return '';
213 + $values = array_values($items);
214 +
215 + if (count($values) !== 2) {
216 + throw new InvalidArgumentException(
217 + BackendStrings::getExceptionStrings()['invalid_datetime_range']
218 + );
239 219 }
240 220
241 - return (string)(self::dateToTimestamp($value) ?? $value);
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;
242 231 }
243 232 }