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