@kermank/nldp
Version:
A modular date/time parser for converting natural language into dates and times
209 lines • 7.56 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.absoluteDatesRule = void 0;
const Logger_1 = require("../utils/Logger");
const luxon_1 = require("luxon");
function createDateComponent(date, span, originalText, preferences) {
return {
type: 'date',
span,
value: date,
confidence: 1,
metadata: {
originalText,
dateType: 'absolute'
}
};
}
const MONTHS = {
'january': 1, 'jan': 1,
'february': 2, 'feb': 2,
'march': 3, 'mar': 3,
'april': 4, 'apr': 4,
'may': 5,
'june': 6, 'jun': 6,
'july': 7, 'jul': 7,
'august': 8, 'aug': 8,
'september': 9, 'sep': 9,
'october': 10, 'oct': 10,
'november': 11, 'nov': 11,
'december': 12, 'dec': 12
};
function parseMonthName(monthStr) {
const month = MONTHS[monthStr.toLowerCase()];
return month || 0;
}
function isValidDate(year, month, day) {
if (month < 1 || month > 12)
return false;
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
return day >= 1 && day <= lastDay;
}
function createDateParser(format) {
return (matches, preferences) => {
const [_, ...parts] = matches;
let year, month, day;
switch (format) {
case 'YMD':
[year, month, day] = parts.map(p => parseInt(p));
break;
case 'MDY':
[month, day, year] = parts.map(p => parseInt(p));
break;
case 'DMY':
[day, month, year] = parts.map(p => parseInt(p));
break;
default:
return null;
}
// Handle 2-digit years
if (year < 100) {
year += year < 50 ? 2000 : 1900;
}
const date = luxon_1.DateTime.utc(year, month, day);
if (!date.isValid) {
return null;
}
return {
type: 'single',
start: date,
confidence: 1,
text: matches[0]
};
};
}
function createMonthNameParser(format) {
return (matches, preferences) => {
var _a;
Logger_1.Logger.debug('Parsing month name date', {
format,
matches: matches.map(m => m),
});
let year, month, day;
const currentYear = ((_a = preferences.referenceDate) === null || _a === void 0 ? void 0 : _a.toUTC().year) || new Date().getUTCFullYear();
if (format === 'MonthFirst') {
month = parseMonthName(matches[1]);
day = parseInt(matches[2]);
year = matches[3] ? parseInt(matches[3]) : currentYear;
}
else {
day = parseInt(matches[1]);
month = parseMonthName(matches[2]);
year = matches[3] ? parseInt(matches[3]) : currentYear;
}
if (!isValidDate(year, month, day)) {
Logger_1.Logger.debug('Invalid date components', { year, month, day });
return null;
}
const result = luxon_1.DateTime.utc(year, month, day);
return {
type: 'single',
start: result,
confidence: 1.0,
text: matches[0]
};
};
}
const patterns = [
{
regex: /^(\d{4})-(\d{2})-(\d{2})(?:\s+(\d{1,2}):(\d{2}))?(?:\s*([+-]\d{4})?)?$/,
parse: (matches, preferences) => {
const [fullMatch, year, month, day, hours, minutes, timezone] = matches;
// Create base date in UTC
let date = luxon_1.DateTime.utc(parseInt(year), parseInt(month), parseInt(day));
// Add time if provided
if (hours && minutes) {
date = date.set({
hour: parseInt(hours),
minute: parseInt(minutes)
});
}
// Handle timezone if provided
if (timezone) {
// Convert timezone offset from ±HHMM to ±HH:MM format
const formattedTz = timezone.replace(/([+-])(\d{2})(\d{2})/, '$1$2:$3');
date = date.setZone(formattedTz);
}
else if (preferences.timeZone) {
// If no explicit timezone but preferences has one
date = date.setZone(preferences.timeZone);
}
// Convert to UTC for storage
const utcDate = date.toUTC();
if (!utcDate.isValid) {
return null;
}
return createDateComponent(utcDate, { start: 0, end: fullMatch.length }, fullMatch, preferences);
}
},
{
regex: /^(\d{1,2})\/(\d{1,2})\/(\d{2,4})(?:\s+(\d{1,2}):(\d{2}))?(?:\s*([+-]\d{4})?)?$/,
parse: (matches, preferences) => {
const [fullMatch, month, day, year, hours, minutes, timezone] = matches;
let parsedYear = parseInt(year);
// Handle 2-digit years
if (parsedYear < 100) {
parsedYear += parsedYear < 50 ? 2000 : 1900;
}
// Create base date in UTC
let date = luxon_1.DateTime.utc(parsedYear, parseInt(month), parseInt(day));
// Add time if provided
if (hours && minutes) {
date = date.set({
hour: parseInt(hours),
minute: parseInt(minutes)
});
}
// Handle timezone if provided
if (timezone) {
// Convert timezone offset from ±HHMM to ±HH:MM format
const formattedTz = timezone.replace(/([+-])(\d{2})(\d{2})/, '$1$2:$3');
date = date.setZone(formattedTz);
}
else if (preferences.timeZone) {
// If no explicit timezone but preferences has one
date = date.setZone(preferences.timeZone);
}
// Convert to UTC for storage
const utcDate = date.toUTC();
if (!utcDate.isValid) {
return null;
}
return createDateComponent(utcDate, { start: 0, end: fullMatch.length }, fullMatch, preferences);
}
},
{
regex: /^(\d{4})-(\d{2})-(\d{2})(?:\s+at\s+(\d{1,2}):(\d{2})(?:\s*(AM|PM))?)?$/i,
parse: (matches, preferences) => {
const [fullMatch, year, month, day, hours, minutes, meridiem] = matches;
// Parse the time components
let hour = hours ? parseInt(hours) : 0;
const minute = minutes ? parseInt(minutes) : 0;
if (meridiem) {
if (hour > 12)
return null;
if (meridiem.toUpperCase() === 'PM' && hour < 12)
hour += 12;
if (meridiem.toUpperCase() === 'AM' && hour === 12)
hour = 0;
}
// Create base date in UTC
let date = luxon_1.DateTime.utc(parseInt(year), parseInt(month), parseInt(day), hour, minute);
// Handle timezone if preferences has one
if (preferences.timeZone) {
date = date.setZone(preferences.timeZone);
}
// Convert to UTC for storage
const utcDate = date.toUTC();
if (!utcDate.isValid) {
return null;
}
return createDateComponent(utcDate, { start: 0, end: fullMatch.length }, fullMatch, preferences);
}
}
];
exports.absoluteDatesRule = {
name: 'absolute-dates',
patterns
};
//# sourceMappingURL=absolute-dates.js.map