PluginProbe
Yatra – Travel Booking & Tour Operator Software / trunk
Yatra – Travel Booking & Tour Operator Software vtrunk
3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 All 82 releases
yatra / resources / js / lib / dateFormat.ts

dateFormat.ts in Yatra – Travel Booking & Tour Operator Software trunk, at resources/js/lib/dateFormat.ts

398 lines 11.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Date and Time Formatting Utilities
3 * Uses global Yatra settings for consistent date/time formatting across all pages
4 */
5
6 // Supported PHP date formats:
7 // 'Y-m-d' -> 2025-12-15
8 // 'm/d/Y' -> 12/15/2025
9 // 'd/m/Y' -> 15/12/2025
10 // 'd-m-Y' -> 15-12-2025
11 // 'M d, Y' -> Dec 15, 2025
12 // 'F d, Y' -> December 15, 2025
13 // 'd M Y' -> 15 Dec 2025
14 // 'd F Y' -> 15 December 2025
15
16 // Supported PHP time formats:
17 // 'H:i' -> 14:30 (24-hour)
18 // 'h:i A' -> 02:30 PM (12-hour)
19 // 'h:i a' -> 02:30 pm (12-hour lowercase)
20 // 'H:i:s' -> 14:30:00 (24-hour with seconds)
21 // 'h:i:s A' -> 02:30:00 PM (12-hour with seconds)
22
23 /**
24 * Get date format from Yatra settings
25 */
26 export function getDateFormat(): string {
27 const w = window as any;
28 return (
29 w?.yatraAdmin?.date_format ||
30 w?.yatraAdmin?.dateFormat ||
31 // Customer account pages localize their settings under yatraAccountPage.
32 w?.yatraAccountPage?.date_format ||
33 "Y-m-d"
34 );
35 }
36
37 /**
38 * Get time format from Yatra settings
39 */
40 export function getTimeFormat(): string {
41 const w = window as any;
42 return (
43 w?.yatraAdmin?.time_format ||
44 w?.yatraAdmin?.timeFormat ||
45 w?.yatraAccountPage?.time_format ||
46 "H:i"
47 );
48 }
49
50 /**
51 * Get timezone from Yatra settings
52 */
53 export function getTimezone(): string {
54 const w = window as any;
55 return w?.yatraAdmin?.timezone || w?.yatraAccountPage?.timezone || "UTC";
56 }
57
58 /**
59 * Parse a value into a Date, treating a date-only string ("YYYY-MM-DD") as a
60 * LOCAL calendar date.
61 *
62 * `new Date("2026-08-01")` parses as UTC midnight, but the formatters read it
63 * back with local getters (getDate/getMonth/…), so in any behind-UTC timezone
64 * the day rolls back (Aug 1 → Jul 31) — e.g. a booking's travel_date showing
65 * one day early. Building the Date from its parts pins it to the intended
66 * calendar day regardless of timezone. Datetime strings (with a time part) and
67 * Date objects are parsed/returned as before.
68 */
69 export function toDateValue(value: string | Date | null | undefined): Date {
70 // Never hand back a non-Date. This previously returned `value` unchanged for
71 // anything that wasn't a string, so a missing date came back as `undefined`
72 // and the caller's `.toLocaleDateString()` threw — taking the whole admin
73 // dashboard down with it via the error boundary. An Invalid Date keeps every
74 // caller's date maths working (comparisons are simply false) and lets the
75 // display helpers fall back to "-".
76 if (value === null || value === undefined) return new Date(NaN);
77 if (value instanceof Date) return value;
78 if (typeof value !== "string") return new Date(NaN);
79 const dateOnly = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
80 if (dateOnly) {
81 return new Date(
82 Number(dateOnly[1]),
83 Number(dateOnly[2]) - 1,
84 Number(dateOnly[3]),
85 );
86 }
87 return new Date(value);
88 }
89
90 /**
91 * Format a date string according to Yatra settings
92 * @param dateString - Date string in any parseable format (ISO, YYYY-MM-DD, etc.)
93 * @param includeTime - Whether to include time in the output
94 * @returns Formatted date string
95 */
96 export function formatDate(
97 dateString: string | Date | null | undefined,
98 includeTime: boolean = false,
99 ): string {
100 if (!dateString) return "-";
101
102 try {
103 const date = toDateValue(dateString);
104
105 if (isNaN(date.getTime())) {
106 return String(dateString);
107 }
108
109 const phpDateFormat = getDateFormat();
110 const phpTimeFormat = getTimeFormat();
111
112 // Format date part
113 const formattedDate = formatDatePart(date, phpDateFormat);
114
115 // Format time part if requested
116 if (includeTime) {
117 const formattedTime = formatTimePart(date, phpTimeFormat);
118 return `${formattedDate} ${formattedTime}`;
119 }
120
121 return formattedDate;
122 } catch (e) {
123 return String(dateString);
124 }
125 }
126
127 /**
128 * Format only the date part
129 */
130 function formatDatePart(date: Date, phpFormat: string): string {
131 const year = date.getFullYear();
132 const month = date.getMonth();
133 const day = date.getDate();
134
135 const monthNames = [
136 "January",
137 "February",
138 "March",
139 "April",
140 "May",
141 "June",
142 "July",
143 "August",
144 "September",
145 "October",
146 "November",
147 "December",
148 ];
149 const monthShort = [
150 "Jan",
151 "Feb",
152 "Mar",
153 "Apr",
154 "May",
155 "Jun",
156 "Jul",
157 "Aug",
158 "Sep",
159 "Oct",
160 "Nov",
161 "Dec",
162 ];
163
164 const pad = (n: number) => n.toString().padStart(2, "0");
165
166 const dayNames = [
167 "Sunday",
168 "Monday",
169 "Tuesday",
170 "Wednesday",
171 "Thursday",
172 "Friday",
173 "Saturday",
174 ];
175 const dayShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
176 const dayOfWeek = date.getDay();
177
178 switch (phpFormat) {
179 // Numeric formats
180 case "Y-m-d":
181 return `${year}-${pad(month + 1)}-${pad(day)}`;
182 case "Y/m/d":
183 return `${year}/${pad(month + 1)}/${pad(day)}`;
184 case "m/d/Y":
185 return `${pad(month + 1)}/${pad(day)}/${year}`;
186 case "d/m/Y":
187 return `${pad(day)}/${pad(month + 1)}/${year}`;
188 case "d-m-Y":
189 return `${pad(day)}-${pad(month + 1)}-${year}`;
190 case "d.m.Y":
191 return `${pad(day)}.${pad(month + 1)}.${year}`;
192 // Month name formats with padded day
193 case "M d, Y":
194 return `${monthShort[month]} ${pad(day)}, ${year}`;
195 case "F d, Y":
196 return `${monthNames[month]} ${pad(day)}, ${year}`;
197 case "d M Y":
198 return `${pad(day)} ${monthShort[month]} ${year}`;
199 case "d F Y":
200 return `${pad(day)} ${monthNames[month]} ${year}`;
201 // Month name formats with day without leading zero
202 case "M j, Y":
203 return `${monthShort[month]} ${day}, ${year}`;
204 case "F j, Y":
205 return `${monthNames[month]} ${day}, ${year}`;
206 case "j M Y":
207 return `${day} ${monthShort[month]} ${year}`;
208 case "j F Y":
209 return `${day} ${monthNames[month]} ${year}`;
210 // Year first with month name
211 case "Y M j":
212 return `${year} ${monthShort[month]} ${day}`;
213 case "Y F j":
214 return `${year} ${monthNames[month]} ${day}`;
215 // With weekday
216 case "l, F j, Y":
217 return `${dayNames[dayOfWeek]}, ${monthNames[month]} ${day}, ${year}`;
218 case "D, M j, Y":
219 return `${dayShort[dayOfWeek]}, ${monthShort[month]} ${day}, ${year}`;
220 default:
221 // Default to ISO format
222 return `${year}-${pad(month + 1)}-${pad(day)}`;
223 }
224 }
225
226 /**
227 * Format only the time part
228 */
229 function formatTimePart(date: Date, phpFormat: string): string {
230 const hours = date.getHours();
231 const minutes = date.getMinutes();
232 const seconds = date.getSeconds();
233
234 const pad = (n: number) => n.toString().padStart(2, "0");
235
236 const hours12 = hours % 12 || 12;
237 const ampm = hours >= 12 ? "PM" : "AM";
238
239 switch (phpFormat) {
240 case "H:i":
241 return `${pad(hours)}:${pad(minutes)}`;
242 case "h:i A":
243 return `${pad(hours12)}:${pad(minutes)} ${ampm}`;
244 case "h:i a":
245 return `${pad(hours12)}:${pad(minutes)} ${ampm.toLowerCase()}`;
246 case "H:i:s":
247 return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
248 case "h:i:s A":
249 return `${pad(hours12)}:${pad(minutes)}:${pad(seconds)} ${ampm}`;
250 default:
251 return `${pad(hours)}:${pad(minutes)}`;
252 }
253 }
254
255 /**
256 * Format a date for display with time
257 * @param dateString - Date string
258 * @returns Formatted date and time string
259 */
260 export function formatDateTime(
261 dateString: string | Date | null | undefined,
262 ): string {
263 return formatDate(dateString, true);
264 }
265
266 /**
267 * Format time only
268 * @param dateString - Date string or time string
269 * @returns Formatted time string
270 */
271 export function formatTime(
272 dateString: string | Date | null | undefined,
273 ): string {
274 if (!dateString) return "-";
275
276 try {
277 const date = toDateValue(dateString);
278
279 if (isNaN(date.getTime())) {
280 // Try to parse as time only (HH:mm or HH:mm:ss)
281 if (
282 typeof dateString === "string" &&
283 /^\d{1,2}:\d{2}(:\d{2})?$/.test(dateString)
284 ) {
285 const [hours, minutes] = dateString.split(":").map(Number);
286 const tempDate = new Date();
287 tempDate.setHours(hours, minutes, 0, 0);
288 return formatTimePart(tempDate, getTimeFormat());
289 }
290 return String(dateString);
291 }
292
293 return formatTimePart(date, getTimeFormat());
294 } catch (e) {
295 return String(dateString);
296 }
297 }
298
299 /**
300 * Get relative time (e.g., "2 hours ago", "3 days ago")
301 * @param dateString - Date string
302 * @returns Relative time string
303 */
304 export function formatRelativeTime(
305 dateString: string | Date | null | undefined,
306 ): string {
307 if (!dateString) return "-";
308
309 try {
310 const date = toDateValue(dateString);
311
312 if (isNaN(date.getTime())) {
313 return String(dateString);
314 }
315
316 const now = new Date();
317 const diffMs = now.getTime() - date.getTime();
318 const diffSecs = Math.floor(diffMs / 1000);
319 const diffMins = Math.floor(diffSecs / 60);
320 const diffHours = Math.floor(diffMins / 60);
321 const diffDays = Math.floor(diffHours / 24);
322 const diffWeeks = Math.floor(diffDays / 7);
323 const diffMonths = Math.floor(diffDays / 30);
324 const diffYears = Math.floor(diffDays / 365);
325
326 if (diffSecs < 60) return "Just now";
327 if (diffMins < 60)
328 return `${diffMins} minute${diffMins > 1 ? "s" : ""} ago`;
329 if (diffHours < 24)
330 return `${diffHours} hour${diffHours > 1 ? "s" : ""} ago`;
331 if (diffDays < 7) return `${diffDays} day${diffDays > 1 ? "s" : ""} ago`;
332 if (diffWeeks < 4)
333 return `${diffWeeks} week${diffWeeks > 1 ? "s" : ""} ago`;
334 if (diffMonths < 12)
335 return `${diffMonths} month${diffMonths > 1 ? "s" : ""} ago`;
336 return `${diffYears} year${diffYears > 1 ? "s" : ""} ago`;
337 } catch (e) {
338 return String(dateString);
339 }
340 }
341
342 /**
343 * Parse a date string to Date object
344 * @param dateString - Date string in various formats
345 * @returns Date object or null if invalid
346 */
347 export function parseDate(dateString: string | null | undefined): Date | null {
348 if (!dateString) return null;
349
350 try {
351 const date = toDateValue(dateString);
352 return isNaN(date.getTime()) ? null : date;
353 } catch (e) {
354 return null;
355 }
356 }
357
358 /**
359 * Format date for input fields (always YYYY-MM-DD for HTML date inputs)
360 * @param dateString - Date string
361 * @returns Date in YYYY-MM-DD format for input fields
362 */
363 /**
364 * Today's date as a LOCAL "YYYY-MM-DD" string.
365 *
366 * Use this instead of `new Date().toISOString().split("T")[0]` for default/
367 * "today" date values: toISOString() yields the UTC date, which is the wrong
368 * calendar day for a user whose local date differs from UTC at that moment
369 * (e.g. late evening in a behind-UTC zone, or early morning ahead of UTC) —
370 * saving a payment/booking/rule date a day off. This reads the browser's local
371 * calendar day.
372 */
373 export function todayYmd(): string {
374 return formatDateForInput(new Date());
375 }
376
377 export function formatDateForInput(
378 dateString: string | Date | null | undefined,
379 ): string {
380 if (!dateString) return "";
381
382 try {
383 const date = toDateValue(dateString);
384
385 if (isNaN(date.getTime())) {
386 return "";
387 }
388
389 const year = date.getFullYear();
390 const month = (date.getMonth() + 1).toString().padStart(2, "0");
391 const day = date.getDate().toString().padStart(2, "0");
392
393 return `${year}-${month}-${day}`;
394 } catch (e) {
395 return "";
396 }
397 }
398