| 722 | return moment; | 722 | return moment; |
| 723 | } | 723 | } |
| 724 | | 724 | |
| | 725 | /** |
| | 726 | * Parses a timestamp and returns a moment object representing the parsed date and time. |
| | 727 | * @param {string|number} timestamp - The timestamp to parse. It can be a string or a number. |
| | 728 | * @returns {moment.Moment} - A moment object representing the parsed date and time. If the timestamp is invalid, an invalid moment object is returned. |
| | 729 | */ |
| 725 | function parseTimestamp(timestamp) { | 730 | function parseTimestamp(timestamp) { |
| 726 | if (!timestamp) { | 731 | if (!timestamp) return moment.invalid(); |
| 727 | return moment.invalid(); | | |
| 728 | } | | |
| 729 | | 732 | |
| 730 | // Unix time (legacy TAI / tags) | 733 | // Unix time (legacy TAI / tags) |
| 731 | if (typeof timestamp === 'number' || /^\d+$/.test(timestamp)) { | 734 | if (typeof timestamp === 'number' || /^\d+$/.test(timestamp)) { |
| 732 | if (isNaN(timestamp) || !isFinite(timestamp) || timestamp < 0) { | 735 | const number = Number(timestamp); |
| 733 | return moment.invalid(); | 736 | if (isNaN(number)) return moment.invalid(); |
| 734 | } | 737 | if (!isFinite(number)) return moment.invalid(); |
| 735 | return moment(Number(timestamp)); | 738 | if (number < 0) return moment.invalid(); |
| | 739 | return moment(number); |
| 736 | } | 740 | } |
| 737 | | 741 | |
| 738 | // ST "humanized" format pattern | 742 | let dtFmt = []; |
| 739 | const pattern1 = /(\d{4})-(\d{1,2})-(\d{1,2}) @(\d{1,2})h (\d{1,2})m (\d{1,2})s (\d{1,3})ms/; | | |
| 740 | const replacement1 = (match, year, month, day, hour, minute, second, millisecond) => { | | |
| 741 | return `${year.padStart(4, '0')}-${month.padStart(2, '0')}-${day.padStart(2, '0')}T${hour.padStart(2, '0')}:${minute.padStart(2, '0')}:${second.padStart(2, '0')}.${millisecond.padStart(3, '0')}Z`; | | |
| 742 | }; | | |
| 743 | const isoTimestamp1 = timestamp.replace(pattern1, replacement1); | | |
| 744 | if (moment(isoTimestamp1).isValid()) { | | |
| 745 | return moment(isoTimestamp1); | | |
| 746 | } | | |
| 747 | | 743 | |
| 748 | // New format pattern: "June 19, 2023 4:13pm" | 744 | // meridiem-based format |
| 749 | const pattern2 = /(\w+)\s(\d{1,2}),\s(\d{4})\s(\d{1,2}):(\d{1,2})(am|pm)/i; | 745 | const convertFromMeridiemBased = (_, month, day, year, hour, minute, meridiem) => { |
| 750 | const replacement2 = (match, month, day, year, hour, minute, meridiem) => { | | |
| 751 | const monthNum = moment().month(month).format('MM'); | 746 | const monthNum = moment().month(month).format('MM'); |
| 752 | const hour24 = meridiem.toLowerCase() === 'pm' ? (parseInt(hour, 10) % 12) + 12 : parseInt(hour, 10) % 12; | 747 | const hour24 = meridiem.toLowerCase() === 'pm' ? (parseInt(hour, 10) % 12) + 12 : parseInt(hour, 10) % 12; |
| 753 | return `${year}-${monthNum}-${day.padStart(2, '0')}T${hour24.toString().padStart(2, '0')}:${minute.padStart(2, '0')}:00`; | 748 | return `${year}-${monthNum}-${day.padStart(2, '0')}T${hour24.toString().padStart(2, '0')}:${minute.padStart(2, '0')}:00`; |
| 754 | }; | 749 | }; |
| 755 | const isoTimestamp2 = timestamp.replace(pattern2, replacement2); | 750 | // June 19, 2023 2:20pm |
| 756 | if (moment(isoTimestamp2).isValid()) { | 751 | dtFmt.push({ callback: convertFromMeridiemBased, pattern: /(\w+)\s(\d{1,2}),\s(\d{4})\s(\d{1,2}):(\d{1,2})(am|pm)/i }); |
| 757 | return moment(isoTimestamp2); | 752 | |
| | 753 | // ST "humanized" format patterns |
| | 754 | const convertFromHumanized = (_, year, month, day, hour, min, sec, ms) => { |
| | 755 | ms = typeof ms !== 'undefined' ? `.${ms.padStart(3, '0')}` : ''; |
| | 756 | return `${year.padStart(4, '0')}-${month.padStart(2, '0')}-${day.padStart(2, '0')}T${hour.padStart(2, '0')}:${min.padStart(2, '0')}:${sec.padStart(2, '0')}${ms}Z`; |
| | 757 | }; |
| | 758 | // 2024-7-12@01h31m37s |
| | 759 | dtFmt.push({ callback: convertFromHumanized, pattern: /(\d{4})-(\d{1,2})-(\d{1,2})@(\d{1,2})h(\d{1,2})m(\d{1,2})s/ }); |
| | 760 | // 2024-6-5 @14h 56m 50s 682ms |
| | 761 | dtFmt.push({ callback: convertFromHumanized, pattern: /(\d{4})-(\d{1,2})-(\d{1,2}) @(\d{1,2})h (\d{1,2})m (\d{1,2})s (\d{1,3})ms/ }); |
| | 762 | |
| | 763 | let iso8601; |
| | 764 | for (const x of dtFmt) { |
| | 765 | let rgxMatch = timestamp.match(x.pattern); |
| | 766 | if (!rgxMatch) continue; |
| | 767 | iso8601 = x.callback(...rgxMatch); |
| | 768 | break; |
| 758 | } | 769 | } |
| 759 | | 770 | |
| 760 | // If none of the patterns match, return an invalid moment object | 771 | // If one of the patterns matched, return a valid moment object, otherwise return an invalid moment object |
| 761 | return moment.invalid(); | 772 | return iso8601 ? moment(iso8601) : moment.invalid(); |
| 762 | } | 773 | } |
| 763 | | 774 | |
| 764 | /** | 775 | /** |