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