UNPKG

unified-query

Version:

Composable search input with autocompletion and a rich query-language parser for the Unified Data System

137 lines (136 loc) 4.35 kB
/* -------------------------------------------------------------------------- */ /* Date / time micro-parsers */ /* -------------------------------------------------------------------------- */ /* ───────── helpers ───────── */ const daysInMonth = (y, m) => new Date(y, m, 0).getDate(); // JS months are 1-based if day = 0 function cmpOp(word) { if (word[0] === '<' || word[0] === '>') return [word[0], word.slice(1)]; return [undefined, word]; } /* -------------------------------------------------------------------------- */ /* DATE YYYY | YYYY/MM | YYYY/MM/DD */ /* -------------------------------------------------------------------------- */ export const dateParser = (raw, startPos) => { const [op, word] = cmpOp(raw); const parts = word.split('/'); /* Fast structural checks first */ if (parts.length < 1 || parts.length > 3) return null; if (!/^\d{4}$/.test(parts[0])) return null; // year must be 4 digits const year = +parts[0]; let month; let day; if (parts.length >= 2) { if (!/^(0?[1-9]|1[0-2])$/.test(parts[1])) return null; month = +parts[1]; } if (parts.length === 3) { if (!/^(0?[1-9]|[12]\d|3[01])$/.test(parts[2])) return null; day = +parts[2]; if (!month) return null; // day without month impossible if (day > daysInMonth(year, month)) return null; } const value = { y: year }; if (month !== undefined) value.m = month; if (day !== undefined) value.d = day; return { kind: 'date', op, value, raw, from: startPos, to: startPos + raw.length, }; }; /* -------------------------------------------------------------------------- */ /* DATETIME YYYY/MM/DD-HH:mm */ /* -------------------------------------------------------------------------- */ export const datetimeParser = (raw, startPos) => { const [op, word] = cmpOp(raw); const [datePart, timePart] = word.split('-'); if (!timePart) return null; const dateTok = dateParser(datePart, startPos); if (!dateTok) return null; /* 24-hour HH:mm exactly two components */ const m = /^([01]?\d|2[0-3]):([0-5]\d)$/.exec(timePart); if (!m) return null; const h = +m[1]; const min = +m[2]; const value = { y: dateTok.value.y, m: dateTok.value.m, d: dateTok.value.d, h, min, }; return { kind: 'datetime', op, value, raw, from: startPos, to: startPos + raw.length, }; }; /* -------------------------------------------------------------------------- */ /* TIME (24h or 12h with am/pm) */ /* -------------------------------------------------------------------------- */ export const timeParser = (raw, startPos) => { const [op, word] = cmpOp(raw); /* ---------- 24-hour HH or HH:mm -------- */ let m = /^([01]?\d|2[0-3])(?::([0-5]\d))?$/.exec(word); if (m) { const h = +m[1]; const min = m[2] === undefined ? undefined : +m[2]; const value = { h, clock: '24h' }; if (min !== undefined) value.m = min; return { kind: 'time', op, value, raw, from: startPos, to: startPos + raw.length, }; } /* ---------- 12-hour H or H:mm am/pm ---- */ m = /^(0?\d|1[0-2])(?::([0-5]\d))?(am|pm)$/i.exec(word); if (!m) return null; let h12 = +m[1]; const min12 = m[2] === undefined ? undefined : +m[2]; const suffix = m[3].toLowerCase(); // Convert 12am -> 0, 12pm stays 12 if (suffix === 'am') { if (h12 === 12) h12 = 0; } else { // pm if (h12 !== 12) h12 += 12; } const value = { h: h12, clock: '12h' }; if (min12 !== undefined) value.m = min12; return { kind: 'time', op, value, raw, from: startPos, to: startPos + raw.length, }; };