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 +232 -0 0.6.1 → 1.5 View file →
@@ -1,0 +1,232 @@
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 +}