Files
spencermountain-compromise/plugins/dates/src/api/parse/one/01-tokenize/01-shift.js
T

139 lines
3.4 KiB
JavaScript

const knownUnits = {
second: true,
minute: true,
hour: true,
day: true,
week: true,
weekend: true,
month: true,
season: true,
quarter: true,
year: true,
}
const aliases = {
wk: 'week',
min: 'minute',
sec: 'second',
weekend: 'week', //for now...
}
// units made of other units
const multiples = {
fortnight: [2, 'week'],
}
// half of a unit, in smaller whole units
const halfUnits = {
year: [6, 'month'],
quarter: [45, 'day'],
season: [45, 'day'],
month: [15, 'day'],
week: [84, 'hour'],
day: [12, 'hour'],
hour: [30, 'minute'],
minute: [30, 'second'],
}
const parseUnit = function (m) {
let unit = m.match('#Duration').text('normal')
unit = unit.replace(/s$/, '')
// support shorthands like 'min'
if (Object.hasOwn(aliases, unit)) {
unit = aliases[unit]
}
return unit
}
//turn '5 weeks before' to {weeks:5}
const parseShift = function (doc) {
const result = {}
let m = doc.none()
let shift = doc.match('#DateShift+')
if (shift.found === false) {
return { result, m }
}
//is it 2 weeks ago? → -2 ('hence' means the future)
const isNegative = shift.has('(before|ago|back)$') === true
// '5 weeks'
shift.match('#Cardinal #Duration').forEach((ts) => {
let num = ts.match('#Cardinal').numbers().get()[0]
if (num && typeof num === 'number') {
let unit = parseUnit(ts)
// '1 fortnight' is 2 weeks
if (Object.hasOwn(multiples, unit)) {
num *= multiples[unit][0]
unit = multiples[unit][1]
}
if (knownUnits[unit] === true) {
result[unit] = isNegative ? num * -1 : num
}
}
})
m = shift.match('#Cardinal #Duration')
shift = shift.not(m)
// supoprt '1 day after tomorrow'
m = shift.match('[<unit>#Duration] [<dir>(after|before)]')
if (m.found) {
const unit = m.groups('unit').text('reduced')
// unit = unit.replace(/s$/, '')
const dir = m.groups('dir').text('reduced')
if (dir === 'after') {
result[unit] = 1
} else if (dir === 'before') {
result[unit] = -1
}
}
// in half an hour
m = shift.match('half (a|an) [#Duration]', 0)
if (m.found) {
const unit = parseUnit(m)
if (knownUnits[unit] === true) {
result[unit] = isNegative ? -0.5 : 0.5
}
}
// a few years / a couple of weeks
m = shift.match('a [<amt>(few|couple)] of? [<unit>#Duration]')
if (m.found) {
const unit = parseUnit(m.groups('unit'))
if (knownUnits[unit] === true) {
const num = m.groups('amt').has('few') ? 3 : 2
result[unit] = isNegative ? num * -1 : num
}
}
// '2 weeks and a half'
m = doc.match('[<unit>#Duration] and a half')
if (m.found) {
const unit = parseUnit(m.groups('unit'))
if (knownUnits[unit] === true && result[unit] !== undefined) {
result[unit] += isNegative ? -0.5 : 0.5
}
}
// spacetime drops fractional units - swap a half-unit for smaller whole units
Object.keys(result).forEach((k) => {
const whole = Math.trunc(result[k])
const frac = result[k] - whole
if (frac === 0.5 || frac === -0.5) {
const [num, smaller] = halfUnits[k] || []
if (smaller) {
result[k] = whole
result[smaller] = (result[smaller] || 0) + (frac > 0 ? num : num * -1)
if (result[k] === 0) {
delete result[k]
}
}
}
})
// finally, remove it from our text
m = doc.match('#DateShift+')
return { result, m }
}
export default parseShift