diff --git a/plugins/dates/builds/compromise-dates.js b/plugins/dates/builds/compromise-dates.js index 10df2b08b..25a68a9fb 100644 --- a/plugins/dates/builds/compromise-dates.js +++ b/plugins/dates/builds/compromise-dates.js @@ -1,4 +1,4 @@ -/* compromise-dates 2.0.3 MIT */ +/* compromise-dates 2.2.0 MIT */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : @@ -47,6 +47,7 @@ }; const tagDates = function (doc) { + // doc.debug() // in the evening doc.match('in the (night|evening|morning|afternoon|day|daytime)').tag('Time', 'in-the-night'); // 8 pm @@ -56,7 +57,10 @@ doc.match('/^[0-9]{4}-[0-9]{2}$/').tag('Date', '2012-06'); // misc weekday words - doc.match('(tue|thu)').tag('WeekDay', 'misc-weekday'); //months: + doc.match('(tue|thu)').tag('WeekDay', 'misc-weekday'); + doc.match('(march|april|may) (and|to|or|through|until)? (march|april|may)').tag('Date').match('(march|april|may)').tag('Month', 'march|april|may'); // april should almost-always be a date + // doc.match('[april] !#LastName?', 0).tag('Month', 'april') + //months: let month = doc.if('#Month'); @@ -68,9 +72,15 @@ month.match('#Cardinal #Month').tag('Date', 'cardinal-month'); //march 5 to 7 - month.match('#Month #Value to #Value').tag('Date', 'value-to-value'); //march the 12th + month.match('#Month #Value (and|or|to)? #Value+').tag('Date', 'value-to-value'); //march the 12th + + month.match('#Month the #Value').tag('Date', 'month-the-value'); // march to april + + month.match('(march|may) to? #Date').tag('Date').match('^.').tag('Month', 'march-to'); // 'march' - month.match('#Month the #Value').tag('Date', 'month-the-value'); + month.match('^(march|may)$').tag('Month', 'single-march'); //March or June + + month.match('#Month or #Month').tag('Date', 'month-or-month'); } //months: @@ -199,7 +209,8 @@ doc.match('(from|starting|until|by) now').tag('Date', 'for-now'); // every night - doc.match('(each|every) night').tag('Date', 'for-now'); + doc.match('(each|every) night').tag('Date', 'for-now'); // doc.debug() + return doc; }; @@ -230,7 +241,9 @@ doc.match('#Value and a half (years|months|weeks|days)').tag('Date', here$5); //on the fifth - doc.match('on the #Ordinal').tag('Date', here$5); + doc.match('on the #Ordinal').tag('Date', here$5); // 'jan 5 or 8' + + doc.match('#Month #Value+ (and|or) #Value').tag('Date', 'date-or-date'); } return doc; @@ -492,7 +505,7 @@ } //tomorrow on 5 - d.match(`on #Cardinal$`).unTag('Date', here); //this tomorrow + d.match(`on #Cardinal$`).unTag('Date', 'on 5'); //this tomorrow d.match(`this tomorrow`).terms(0).unTag('Date', 'this-tomorrow'); //q2 2019 @@ -500,29 +513,27 @@ // d.match(`^#Value #WeekDay`).terms(0).unTag('Date'); //5 next week - d.match(`^#Value (this|next|last)`).terms(0).unTag('Date', here); + d.match(`^#Value (this|next|last)`).terms(0).unTag('Date', '4 next'); if (d.has('(last|this|next)')) { //this month 7 - d.match(`(last|this|next) #Duration #Value`).terms(2).unTag('Date', here); //7 this month + d.match(`(last|this|next) #Duration #Value`).terms(2).unTag('Date', 'this month 7'); //7 this month - d.match(`!#Month #Value (last|this|next) #Date`).terms(0).unTag('Date', here); + d.match(`!#Month #Value (last|this|next) #Date`).terms(0).unTag('Date', '7 this month'); } //january 5 5 - - - if (d.has('(#Year|#Time|#TextValue|#NumberRange)') === false) { - d.match('(#Month|#WeekDay) #Value #Value').terms(2).unTag('Date', here); - } //between june + // if (d.has('(#Year|#Time|#TextValue|#NumberRange)') === false) { + // d.match('(#Month|#WeekDay) #Value #Value !(or|and)?').terms(2).unTag('Date', 'jan 5 5') + // } + //between june if (d.has('^between') && !d.has('and .')) { d.unTag('Date', here); } //june june - - - if (d.has('#Month #Month') && !d.has('@hasHyphen') && !d.has('@hasComma')) { - d.match('#Month').lastTerm().unTag('Date', 'month-month'); - } // over the years + // if (d.has('#Month #Month') && !d.has('@hasHyphen') && !d.has('@hasComma')) { + // d.match('#Month').lastTerm().unTag('Date', 'month-month') + // } + // over the years d.match('(in|over) the #Duration #Date+?').unTag('Date', 'over-the-duration'); // log the hours @@ -576,6 +587,156 @@ var _01Tagger = tagDate; + // chop things up into bite-size pieces + const split = function (dates) { + let m = null; // don't split anything if it looks like a range + + if (dates.has('^(between|within) #Date')) { + return dates; + } + + if (dates.has('#Month')) { + // 'june 5, june 10' + m = dates.match('[#Month #Value] and? #Month', 0).ifNo('@hasDash$'); + + if (m.found) { + dates = dates.splitAfter(m); + } // '5 june, 10 june' + + + m = dates.match('[#Value #Month] and? #Value #Month', 0); + + if (m.found) { + dates = dates.splitAfter(m); + } // 'june, august' + + + m = dates.match('^[#Month] and? #Month #Ordinal?$', 0); + + if (m.found) { + dates = dates.splitAfter(m); + } // 'june 5th, june 10th' + + + m = dates.match('[#Month #Value] #Month', 0).ifNo('@hasDash$'); + + if (m.found) { + dates = dates.splitAfter(m); + } + } + + if (dates.has('#WeekDay')) { + // 'tuesday, wednesday' + m = dates.match('^[#WeekDay] and? #WeekDay$', 0).ifNo('@hasDash$'); + + if (m.found) { + dates = dates.splitAfter(m); + } // 'tuesday, wednesday, and friday' + + + m = dates.match('#WeekDay #WeekDay and? #WeekDay'); + + if (m.found) { + dates = dates.splitOn('#WeekDay'); + } // monday, wednesday + + + m = dates.match('[#WeekDay] (and|or|this|next)? #WeekDay', 0).ifNo('@hasDash$'); + + if (m.found) { + dates = dates.splitAfter('#WeekDay'); + } + } // next week tomorrow + + + m = dates.match('(this|next) #Duration [(today|tomorrow|yesterday)]', 0); + + if (m.found) { + dates = dates.splitBefore(m); + } // tomorrow 15 march + + + m = dates.match('[(today|tomorrow|yesterday)] #Value #Month', 0); + + if (m.found) { + dates = dates.splitAfter(m); + } // tomorrow yesterday + + + m = dates.match('[(today|tomorrow|yesterday)] (today|tomorrow|yesterday|#WeekDay)', 0).ifNo('@hasDash$'); + + if (m.found) { + dates = dates.splitAfter(m); + } // cleanup any splits + + + dates = dates.not('^and'); + return dates; + }; + + var split_1 = split; + + const findDate = function (doc) { + if (doc.world.isVerbose() === 'date') { + doc.debug(); + console.log(' ---'); + } + + let dates = doc.match('#Date+'); // ignore only-durations like '20 minutes' + + dates = dates.filter(m => { + let isDuration = m.has('^#Duration+$') || m.has('^#Value #Duration+$'); // allow 'q4', etc + + if (isDuration === true && m.has('(#FinancialQuarter|quarter)')) { + return true; + } + + return isDuration === false; + }); // 30 minutes on tuesday + + let m = dates.match('[#Cardinal #Duration (in|on|this|next|during|for)] #Date', 0); + + if (m.found) { + dates = dates.not(m); + } // 30 minutes tuesday + + + m = dates.match('[#Cardinal #Duration] #WeekDay', 0); + + if (m.found) { + dates = dates.not(m); + } // tuesday for 30 mins + + + m = dates.match('#Date [for #Value #Duration]$', 0); + + if (m.found) { + dates = dates.not(m); + } // '20 minutes june 5th' + + + m = dates.match('[#Cardinal #Duration] #Date', 0); //but allow '20 minutes ago' + + if (m.found && !dates.has('#Cardinal #Duration] (ago|from|before|after|back)')) { + dates = dates.not(m); + } // for 20 minutes + + + m = dates.match('for #Cardinal #Duration'); + + if (m.found) { + dates = dates.not(m); + } // 'one saturday' + + + dates = dates.notIf('^one (#WeekDay|#Month)$'); // tokenize the dates + + dates = split_1(dates); + return dates; + }; + + var _02Find = findDate; + var _tags = { FinancialQuarter: { isA: 'Date', @@ -7108,8 +7269,8 @@ if (m.found) { let obj = { - month: m.groups('month').text(), - date: m.groups('date').text(), + month: m.groups('month').text('reduced'), + date: m.groups('date').text('reduced'), year: m.groups('year').text() || impliedYear }; let unit = new CalendarDate(obj, null, context); @@ -7124,8 +7285,8 @@ if (m.found) { let obj = { - month: m.groups('month').text(), - year: m.groups('year').text() || impliedYear + month: m.groups('month').text('reduced'), + year: m.groups('year').text('reduced') || impliedYear }; let unit = new Month$1(obj, null, context); @@ -7144,8 +7305,8 @@ if (m.found) { let obj = { - month: m.groups('month').text(), - date: m.groups('date').text(), + month: m.groups('month').text('reduced'), + date: m.groups('date').text('reduced'), year: context.today.year() }; let unit = new CalendarDate(obj, null, context); // assume 'feb' in the future @@ -7163,7 +7324,7 @@ if (doc.has('#Month')) { let obj = { - month: doc.match('#Month').text(), + month: doc.match('#Month').text('reduced'), date: 1, //assume 1st year: context.today.year() @@ -7186,7 +7347,7 @@ if (m.found) { let obj = { month: context.today.month(), - date: m.groups('date').text(), + date: m.groups('date').text('reduced'), year: context.today.year() }; let unit = new CalendarDate(obj, null, context); @@ -7202,7 +7363,7 @@ if (m.found) { let obj = { month: context.today.month(), - date: m.groups('date').text(), + date: m.groups('date').text('reduced'), year: context.today.year() }; let unit = new CalendarDate(obj, null, context); @@ -7385,15 +7546,15 @@ if (doc.world.isVerbose() === 'date') { // console.log('\n\n=-= - - - - - =-=-') - console.log(` str: '${doc.text()}'`); - console.log(` shift: ${JSON.stringify(shift)}`); - console.log(` counter: `, counter); - console.log(` rel: ${rel || '-'}`); - console.log(` section: ${section || '-'}`); - console.log(` time: ${time || '-'}`); - console.log(` weekDay: ${weekDay || '-'}`); + // console.log(` str: '${doc.text()}'`) + // console.log(` shift: ${JSON.stringify(shift)}`) + // console.log(` counter: `, counter) + // console.log(` rel: ${rel || '-'}`) + // console.log(` section: ${section || '-'}`) + // console.log(` time: ${time || '-'}`) + // console.log(` weekDay: ${weekDay || '-'}`) console.log(' unit: ', unit); - console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\n'); + console.log(''); // console.log('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\n') } if (!unit) { @@ -7440,7 +7601,7 @@ return unit; }; - var _03Parse = parseDate; + var _04Parse = parseDate; const normalize = function (doc) { doc = doc.clone(); // 'four thirty' -> 4:30 @@ -7474,26 +7635,23 @@ doc.adverbs().remove(); // 'week-end' - doc.replace('week end', 'weekend').tag('Date'); // 'a up to b' + doc.replace('week end', 'weekend', true).tag('Date'); // 'a up to b' - doc.replace('up to', 'upto').tag('Date'); // 'a year ago' + doc.replace('up to', 'upto', true).tag('Date'); // 'a year ago' if (doc.has('once (a|an) #Duration') === false) { doc.match('[(a|an)] #Duration', 0).replaceWith('1'); _01Tagger(doc); } // 'in a few years' + // m = doc.match('in [a few] #Duration') + // if (m.found) { + // m.groups('0').replaceWith('2') + // tagger(doc) + // } + // jan - feb - m = doc.match('in [a few] #Duration'); - - if (m.found) { - m.groups('0').replaceWith('2'); - _01Tagger(doc); - } // jan - feb - - - doc.match('@hasDash').insertAfter('to').tag('Date'); // console.log('=-=-=-= here -=-=-=-') - // doc.debug() + doc.match('@hasDash').insertAfter('to').tag('Date'); // doc.debug() return doc; }; @@ -7675,7 +7833,7 @@ return null; }; - var _00Repeats = parseIntervals; + var intervals = parseIntervals; // somewhat-intellegent response to end-before-start situations const reverseMaybe = function (obj) { @@ -7720,11 +7878,11 @@ parse: (m, context) => { let from = m.groups('from'); let to = m.groups('to'); - let end = _03Parse(to, context); + let end = _04Parse(to, context); if (end) { let start = end.clone(); - start.applyTime(from.text()); + start.applyTime(from.text('reduced')); if (start) { let obj = { @@ -7751,11 +7909,11 @@ parse: (m, context) => { let from = m.groups('from'); let to = m.groups('to'); - from = _03Parse(from, context); + from = _04Parse(from, context); if (from) { let end = from.clone(); - end.applyTime(to.text()); + end.applyTime(to.text('reduced')); if (end) { let obj = { @@ -7777,15 +7935,126 @@ } }]; - var _02TwoDate = [{ + // and not a start-end range. + + var combos = [{ + // 'jan, or march 1999' + match: '^during? #Month+ (or|and) #Month [#Year]?', + desc: 'march or june', + parse: (m, context) => { + let before = m.match('^during? [#Month]', 0); + m = m.not('(or|and)'); + let start = _04Parse(before, context); + + if (start) { + let result = [{ + start: start, + end: start.clone().end(), + unit: start.unit + }]; // add more run-on numbers? + + let more = m.not(before); + + if (more.found) { + more.match('#Month').forEach(month => { + let s = _04Parse(month, context); // s.d = s.d.month(month.text('reduced')) + + result.push({ + start: s, + end: s.clone().end(), + unit: s.unit + }); + }); + } // apply the year + + + let year = m.match('#Year$'); + + if (year.found) { + year = year.text('reduced'); + result.forEach(o => { + o.start.d = o.start.d.year(year); + o.end.d = o.end.d.year(year); + }); + } + + return result; + } + + return null; + } + }, { + // 'jan 5 or 8' - (one month, shared dates) + match: '^#Month #Value+ (or|and)? #Value$', + desc: 'jan 5 or 8', + parse: (m, context) => { + m = m.not('(or|and)'); + let before = m.match('^#Month #Value'); + let start = _04Parse(before, context); + + if (start) { + let result = [{ + start: start, + end: start.clone().end(), + unit: start.unit + }]; // add more run-on numbers? + + let more = m.not(before); + + if (more.found) { + more.match('#Value').forEach(v => { + let s = start.clone(); + s.d = s.d.date(v.text('reduced')); + result.push({ + start: s, + end: s.clone().end(), + unit: s.unit + }); + }); + } + + return result; + } + + return null; + } + }, { + // 'june or july 2019' + match: '^!(between|from|during)? [#Date+] (and|or) [#Date+]$', + desc: 'A or B', + parse: (m, context) => { + let fromDoc = m.groups('from'); + let toDoc = m.groups('to'); + let from = _04Parse(fromDoc, context); + let to = _04Parse(toDoc, context); + + if (from && to) { + // make their years agree.... + // if (toDoc.has('#Year') && !fromDoc.has('#Year') && from.d.isSame(to.d, 'year') === false) { + // from.d = from.d.year(to.d.year()) + // } + return [{ + start: from, + end: from.clone().end() + }, { + start: to, + end: to.clone().end() + }]; + } + + return null; + } + }]; + + var _02DateRange = [{ // two explicit dates - 'between friday and sunday' match: 'between [.+] and [.+]', desc: 'between friday and sunday', parse: (m, context) => { let start = m.groups('start'); - start = _03Parse(start, context); + start = _04Parse(start, context); let end = m.groups('end'); - end = _03Parse(end, context); + end = _04Parse(end, context); if (start && end) { end = end.before(); @@ -7799,7 +8068,7 @@ } }, { // two months, no year - 'june 5 to june 7' - match: '[#Month #Value] (to|through|thru|and) [#Month #Value] [#Year?]', + match: '[#Month #Value] (to|through|thru) [#Month #Value] [#Year?]', desc: 'june 5 to june 7', parse: (m, context) => { let res = m.groups(); @@ -7809,7 +8078,7 @@ start = start.append(res.year); } - start = _03Parse(start, context); + start = _04Parse(start, context); if (start) { let end = res.to; @@ -7818,7 +8087,7 @@ end = end.append(res.year); } - end = _03Parse(end, context); + end = _04Parse(end, context); if (end) { // assume end is after start @@ -7849,11 +8118,11 @@ } = m.groups(); let year2 = year.clone(); let start = from.prepend(month.text()).append(year.text()); - start = _03Parse(start, context); + start = _04Parse(start, context); if (start) { let end = to.prepend(month.text()).append(year2); - end = _03Parse(end, context); + end = _04Parse(end, context); return { start: start, end: end.end() @@ -7864,11 +8133,11 @@ } }, { // one month, one year, second form - '5 to 7 of january 1998' - match: '[#Value] (to|through|thru|and) [#Value of? #Month #Date+?]', + match: '[#Value] (to|through|thru) [#Value of? #Month #Date+?]', desc: '5 to 7 of january 1998', parse: (m, context) => { let to = m.groups('to'); - to = _03Parse(to, context); + to = _04Parse(to, context); if (to) { let fromDate = m.groups('from'); @@ -7884,11 +8153,11 @@ } }, { // one month, no year - 'january 5 to 7' - match: '[#Month #Value] (to|through|thru|and) [#Value]', + match: '[#Month #Value] (to|through|thru) [#Value]', desc: 'january 5 to 7', parse: (m, context) => { let from = m.groups('from'); - from = _03Parse(from, context); + from = _04Parse(from, context); if (from) { let toDate = m.groups('to'); @@ -7904,14 +8173,14 @@ } }, { // 'january to may 2020' - match: 'from? [#Month] (to|until|upto|through|thru|and) [#Month] [#Year]', + match: 'from? [#Month] (to|until|upto|through|thru) [#Month] [#Year]', desc: 'january to may 2020', parse: (m, context) => { let from = m.groups('from'); let year = from.groups('year').numbers().get(0); let to = m.groups('to'); - from = _03Parse(from, context); - to = _03Parse(to, context); + from = _04Parse(from, context); + to = _04Parse(to, context); from.d = from.d.year(year); to.d = to.d.year(year); @@ -7936,13 +8205,13 @@ var _03OneDate = [{ // 'from A to B' - match: 'from? [.+] (to|until|upto|through|thru|and) [.+]', + match: 'from? [.+] (to|until|upto|through|thru) [.+]', desc: 'from A to B', parse: (m, context) => { let from = m.groups('from'); let to = m.groups('to'); - from = _03Parse(from, context); - to = _03Parse(to, context); + from = _04Parse(from, context); + to = _04Parse(to, context); if (from && to) { let obj = { @@ -7959,9 +8228,9 @@ // 'before june' match: '^due? (by|before) [.+]', desc: 'before june', - group: 0, parse: (m, context) => { - let unit = _03Parse(m, context); + m = m.group(0); + let unit = _04Parse(m, context); if (unit) { let start = new Unit_1(context.today, null, context); @@ -7988,9 +8257,9 @@ // 'in june' match: '^(on|in|at|@|during) [.+]', desc: 'in june', - group: 0, parse: (m, context) => { - let unit = _03Parse(m, context); + m = m.group(0); + let unit = _04Parse(m, context); if (unit) { return { @@ -8006,9 +8275,9 @@ // 'after june' match: '^(after|following) [.+]', desc: 'after june', - group: 0, parse: (m, context) => { - let unit = _03Parse(m, context); + m = m.group(0); + let unit = _04Parse(m, context); if (unit) { unit = unit.after(); @@ -8024,9 +8293,9 @@ // 'middle of' match: '^(middle|center|midpoint) of [.+]', desc: 'middle of', - group: 0, parse: (m, context) => { - let unit = _03Parse(m, context); + m = m.group(0); + let unit = _04Parse(m, context); let start = unit.clone().middle(); let end = unit.beforeEnd(); @@ -8044,7 +8313,7 @@ match: '.+ after #Time+$', desc: 'tuesday after 5pm', parse: (m, context) => { - let unit = _03Parse(m, context); + let unit = _04Parse(m, context); if (unit) { let start = unit.clone(); @@ -8063,7 +8332,7 @@ match: '.+ before #Time+$', desc: 'tuesday before noon', parse: (m, context) => { - let unit = _03Parse(m, context); + let unit = _04Parse(m, context); let end = unit.clone(); let start = unit.start(); @@ -8079,29 +8348,48 @@ } }]; - const ranges = [].concat(_01TwoTimes, _02TwoDate, _03OneDate); // loop thru each range template + const ranges = [].concat(_01TwoTimes, combos, _02DateRange, _03OneDate); - const parseRange = function (doc, context) { - // parse-out 'every week ..' - let repeats = _00Repeats(doc, context) || {}; // if it's *only* an interval response + const isArray = function (arr) { + return Object.prototype.toString.call(arr) === '[object Array]'; + }; //else, try whole thing, non ranges - if (doc.found === false) { - return Object.assign({}, repeats, { - start: null, - end: null - }); - } // try each template in order + const tryFull = function (doc, context) { + let res = { + start: null, + end: null + }; + + if (!doc.found) { + return res; + } + + let unit = _04Parse(doc, context); + if (unit) { + if (doc.world.isVerbose() === 'date') { + console.log(` --[no-range]--`); + } + + let end = unit.clone().end(); + res = { + start: unit, + end: end, + unit: unit.setTime ? 'time' : unit.unit + }; + } + + return res; + }; + + const tryRanges = function (doc, context) { + // try each template in order for (let i = 0; i < ranges.length; i += 1) { let fmt = ranges[i]; let m = doc.match(fmt.match); if (m.found) { - if (fmt.group !== undefined) { - m = m.groups(fmt.group); - } - if (doc.world.isVerbose() === 'date') { console.log(` ---[${fmt.desc}]---`); } @@ -8109,43 +8397,42 @@ let res = fmt.parse(m, context); if (res !== null) { - return Object.assign({}, repeats, res); + // did it return more than one date? + if (!isArray(res)) { + res = [res]; + } + + return res; } } - } //else, try whole thing + } + return null; + }; // loop thru each range template - let res = { - start: null, - end: null - }; - let unit = _03Parse(doc, context); - if (unit) { - if (doc.world.isVerbose() === 'date') { - console.log(` --[no-range]--`); - } + const parseRanges = function (doc, context) { + // parse-out 'every week ..' + let repeats = intervals(doc, context) || {}; // try picking-apart ranges - let end = unit.clone().end(); - res = { - start: unit, - end: end, - unit: unit.setTime ? 'time' : unit.unit - }; - } + let found = tryRanges(doc, context); - let combined = Object.assign({}, repeats, res); // ensure start is not after end - // console.log(combined) + if (!found) { + found = [tryFull(doc, context)]; + } // add the repeat info to each date - if (combined.start && combined.end && combined.start.d.epoch > combined.end.d.epoch) { - // console.warn('Warning: Start date is after End date') - combined.start = combined.start.start(); // combined.end = combined.start.clone() - } - return combined; + found = found.map(o => Object.assign({}, repeats, o)); // ensure start is not after end + + found.forEach(res => { + if (res.start && res.end && res.start.d.epoch > res.end.d.epoch) { + res.start = res.start.start(); + } + }); + return found; }; - var _02Ranges = parseRange; + var _03Ranges = parseRanges; const maxDate = 8640000000000000; const max_loops = 500; @@ -8255,18 +8542,21 @@ doc = normalize_1(doc); //interpret 'between [A] and [B]'... - let result = _02Ranges(doc, context); // format as iso + let results = _03Ranges(doc, context); // who knows - result.start = toISO(result.start); - result.end = toISO(result.end); // generate interval dates + results.forEach(result => { + // format as iso + result.start = toISO(result.start); + result.end = toISO(result.end); // generate interval dates - if (result.repeat) { - result = generate(result, context); - } // add timezone + if (result.repeat) { + result = generate(result, context); + } // add timezone - result.tz = context.timezone; - return result; + result.tz = context.timezone; + }); + return results; }; var getDates = getDate; @@ -8365,9 +8655,11 @@ get: function (options) { let arr = []; this.forEach(doc => { - let date = getDates(doc, this.context); - date.unit = unit(date); - arr.push(date); + let dates = getDates(doc, this.context); + dates.forEach(date => { + date.unit = unit(date); + }); + arr = arr.concat(dates); }); if (typeof options === 'number') { @@ -8392,8 +8684,9 @@ let res = []; this.forEach(doc => { let json = doc.json(options)[0]; - let found = getDates(doc, this.context); - json = Object.assign(json, found); // add more data + let date = getDates(doc, this.context)[0]; //FIX: return all the dates! + + json = Object.assign(json, date); // add more data json.duration = duration(json); json.unit = unit(json); @@ -8410,25 +8703,26 @@ /** render all dates according to a specific format */ format: function (fmt) { this.forEach(doc => { - let obj = getDates(doc, this.context); - - if (obj.start) { - let start = spacetime(obj.start, this.context.timezone); - let str = start.format(fmt); + let dates = getDates(doc, this.context); + dates.forEach(obj => { + if (obj.start) { + let start = spacetime(obj.start, this.context.timezone); + let str = start.format(fmt); - if (obj.end) { - let end = spacetime(obj.end, this.context.timezone); + if (obj.end) { + let end = spacetime(obj.end, this.context.timezone); - if (start.isSame(end, 'day') === false) { - str += ' to ' + end.format(fmt); + if (start.isSame(end, 'day') === false) { + str += ' to ' + end.format(fmt); + } } - } - doc.replaceWith(str, { - keepTags: true, - keepCase: false - }); - } + doc.replaceWith(str, { + keepTags: true, + keepCase: false + }); + } + }); }); return this; }, @@ -8489,7 +8783,7 @@ if (twoWord.found) { twoWord.forEach(m => { let num = m.numbers().get(0); - let unit = m.terms().last().nouns().toSingular().text(); // turn 'mins' into 'minute' + let unit = m.terms().last().nouns().toSingular().text('reduced'); // turn 'mins' into 'minute' if (mapping.hasOwnProperty(unit)) { unit = mapping[unit]; @@ -8698,122 +8992,6 @@ var times = addTimes; - const findDate = function (doc) { - // let r = this.clauses() - let dates = doc.match('#Date+'); // ignore only-durations like '20 minutes' - - dates = dates.filter(m => { - let isDuration = m.has('^#Duration+$') || m.has('^#Value #Duration+$'); // allow 'q4', etc - - if (isDuration === true && m.has('(#FinancialQuarter|quarter)')) { - return true; - } - - return isDuration === false; - }); // 30 minutes on tuesday - - let m = dates.match('[#Cardinal #Duration (in|on|this|next|during|for)] #Date', 0); - - if (m.found) { - dates = dates.not(m); - } // 30 minutes tuesday - - - m = dates.match('[#Cardinal #Duration] #WeekDay', 0); - - if (m.found) { - dates = dates.not(m); - } // tuesday for 30 mins - - - m = dates.match('#Date [for #Value #Duration]$', 0); - - if (m.found) { - dates = dates.not(m); - } // 'tuesday, wednesday' - - - m = dates.match('^[#WeekDay] and? #WeekDay$', 0); - - if (m.found) { - if (m.first().has('@hasDash') === false) { - dates = dates.splitAfter(m); - dates = dates.not('^and'); - } - } // 'june, august' - - - m = dates.match('^[#Month] and? #Month #Ordinal?$', 0); - - if (m.found) { - dates = dates.splitAfter(m); - dates = dates.not('^and'); - } // 'tuesday, wednesday, and friday' - - - m = dates.match('#WeekDay #WeekDay and? #WeekDay'); - - if (m.found) { - dates = dates.splitOn('#WeekDay'); - dates = dates.not('^and'); - } // '5 june, 10 june' - - - m = dates.match('[#Value #Month] #Value #Month', 0); - - if (m.found) { - dates = dates.splitAfter(m); - } // 'june 5th, june 10th' - - - m = dates.match('[#Month #Value] #Month', 0).ifNo('@hasDash$'); - - if (m.found) { - dates = dates.splitAfter(m); - } // '20 minutes june 5th' - - - m = dates.match('[#Cardinal #Duration] #Date', 0); //but allow '20 minutes ago' - - if (m.found && !dates.has('#Cardinal #Duration] (ago|from|before|after|back)')) { - dates = dates.not(m); - } // for 20 minutes - - - m = dates.match('for #Cardinal #Duration'); - - if (m.found) { - dates = dates.not(m); - } // 'one saturday' - - - dates = dates.notIf('^one (#WeekDay|#Month)$'); // next week tomorrow - - m = dates.match('(this|next) #Duration [(today|tomorrow|yesterday)]', 0); - - if (m.found) { - dates = dates.splitBefore(m); - } // tomorrow 15 march - - - m = dates.match('[(today|tomorrow|yesterday)] #Value #Month', 0); - - if (m.found) { - dates = dates.splitAfter(m); - } // tomorrow yesterday - - - m = dates.match('[(today|tomorrow|yesterday)] (today|tomorrow|yesterday)', 0); - - if (m.found) { - dates = dates.splitAfter(m); - } - - return dates; - }; - - var find = findDate; - const opts = { punt: { weeks: 2 @@ -8863,7 +9041,7 @@ context.timezone = 'ETC/UTC'; } - let dates = find(this); + let dates = _02Find(this); if (typeof n === 'number') { dates = dates.get(n); diff --git a/plugins/dates/builds/compromise-dates.min.js b/plugins/dates/builds/compromise-dates.min.js index 07fa3abef..36a8b6459 100644 --- a/plugins/dates/builds/compromise-dates.min.js +++ b/plugins/dates/builds/compromise-dates.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).compromiseDates=t()}(this,(function(){"use strict";const e="(in|by|before|during|on|until|after|of|within|all)",t="(last|next|this|previous|current|upcoming|coming)",a="(start|end|middle|starting|ending|midpoint|beginning)",n="(spring|summer|winter|fall|autumn)",r=(e,t)=>{!0===e.found&&e.forEach((e=>{let a=e.text("reduced"),n=parseInt(a,10);n&&n>1e3&&n<3e3&&e.tag("Year",t)}))},i=(e,t)=>{!0===e.found&&e.forEach((e=>{let a=e.text("reduced"),n=parseInt(a,10);n&&n>1900&&n<2030&&e.tag("Year",t)}))};var o=function(o){o.match("in the (night|evening|morning|afternoon|day|daytime)").tag("Time","in-the-night"),o.match("(#Value|#Time) (am|pm)").tag("Time","value-ampm"),o.match("/^[0-9]{4}-[0-9]{2}$/").tag("Date","2012-06"),o.match("(tue|thu)").tag("WeekDay","misc-weekday");let s=o.if("#Month");!0===s.found&&(s.match("#Month #Date+").tag("Date","correction-numberRange"),s.match("#Value of #Month").tag("Date","value-of-month"),s.match("#Cardinal #Month").tag("Date","cardinal-month"),s.match("#Month #Value to #Value").tag("Date","value-to-value"),s.match("#Month the #Value").tag("Date","month-the-value"));let u=o.if("#Value");if(!0===u.found){u.match("(#WeekDay|#Month) #Value").ifNo("#Money").tag("Date","date-value"),u.match("#Value (#WeekDay|#Month)").ifNo("#Money").tag("Date","value-date"),u.match("#TextValue #TextValue").if("#Date").tag("#Date","textvalue-date"),u.match("#Value (#WeekDay|#Duration) back").tag("#Date","3-back");let t=u.if("#Duration");!0===t.found&&(t.match("for #Value #Duration").tag("Date","for-x-duration"),t.match("#Value #Duration #Conjunction").tag("Date","val-duration-conjunction"),t.match(e+"? #Value #Duration").tag("Date","value-duration"),t.match("#Value #Duration old").unTag("Date","val-years-old"))}let d=o.if(n);!0===d.found&&(d.match(`${e}? ${t} ${n}`).tag("Date","thisNext-season"),d.match(`the? ${a} of ${n}`).tag("Date","section-season"),d.match(`${n} ${e}? #Cardinal`).tag("Date","season-year"));let l=o.if("#Date");!0===l.found&&(l.match("#Date the? #Ordinal").tag("Date","correction"),l.match(t+" #Date").tag("Date","thisNext-date"),l.match("due? (by|before|after|until) #Date").tag("Date","by"),l.match("(last|next|this|previous|current|upcoming|coming|the) #Date").tag("Date","next-feb"),l.match(`the? ${a} of #Date`).tag("Date","section-of"),l.match("#Ordinal #Duration in #Date").tag("Date","duration-in"),l.match("(early|late) (at|in)? the? #Date").tag("Time","early-evening"),l.match("#Date (by|before|after|at|@|about) #Cardinal").not("^#Date").tag("Time","date-before-Cardinal"),l.match("#Date [(am|pm)]",0).unTag("Verb").unTag("Copula").tag("Time","date-am"),l.match("#Date (#Preposition|to) #Date").ifNo("#Duration").tag("Date","date-prep-date"));let h=o.if("#Cardinal");if(!0===h.found){let e=h.match("#Date #Value [#Cardinal]",0);r(e,"date-value-year"),e=h.match("#Date [#Cardinal]",0),i(e,"date-year"),e=h.match(a+" of [#Cardinal]"),i(e,"section-year"),e=h.match("#Month #Value [#Cardinal]",0),r(e,"month-value-year"),e=h.match("#Month #Value to #Value [#Cardinal]",0),r(e,"month-range-year"),e=h.match("(in|of|by|during|before|starting|ending|for|year|since) [#Cardinal]",0),r(e,"in-year-1"),e=h.match("(q1|q2|q3|q4) [#Cardinal]",0),r(e,"in-year-2"),e=h.match("#Ordinal quarter of? [#Cardinal]",0),r(e,"in-year-3"),e=h.match("the year [#Cardinal]",0),r(e,"in-year-4"),e=h.match("it (is|was) [#Cardinal]",0),i(e,"in-year-5"),h.match(a+" of #Year").tag("Date");let t=h.match("between [#Cardinal] and [#Cardinal]");r(t.groups("0"),"between-year-and-year-1"),r(t.groups("1"),"between-year-and-year-2")}let m=o.if("#Time");!0===m.found&&m.match("(by|before|after|at|@|about) #Time").tag("Time","preposition-time");let c=o.match("^/^20[012][0-9]$/$");return i(c,"2020-ish"),o.match("(in|after) /^[0-9]+(min|sec|wk)s?/").tag("Date","shift-units"),o.match("#Date [(now|night|sometime)]",0).tag("Time","date-now"),o.match("(from|starting|until|by) now").tag("Date","for-now"),o.match("(each|every) night").tag("Date","for-now"),o};const s="date-values";var u=function(e){return e.has("#Value")&&(e.match("#Month #Value to #Value of? #Year?").tag("Date",s),e.match("#Value to #Value of? #Month #Year?").tag("Date",s),e.match("#Value #Duration of #Date").tag("Date",s),e.match("#Value+ #Duration (after|before|into|later|afterwards|ago)?").tag("Date",s),e.match("#Value #Date").tag("Date",s),e.match("#Date #Value").tag("Date",s),e.match("#Date #Preposition #Value").tag("Date",s),e.match("#Date (after|before|during|on|in) #Value").tag("Date",s),e.match("#Value (year|month|week|day) and a half").tag("Date",s),e.match("#Value and a half (years|months|weeks|days)").tag("Date",s),e.match("on the #Ordinal").tag("Date",s)),e};const d="date-tagger";var l=function(e){return e.match("(spring|summer|winter|fall|autumn|springtime|wintertime|summertime)").match("#Noun").tag("Season",d),e.match("(q1|q2|q3|q4)").tag("FinancialQuarter",d),e.match("(this|next|last|current) quarter").tag("FinancialQuarter",d),e.match("(this|next|last|current) season").tag("Season",d),e.has("#Date")&&(e.match("#Date #Preposition #Date").tag("Date",d),e.match("(once|twice) (a|an|each) #Date").tag("Date",d),e.match("#Date+").tag("Date",d),e.match("(by|until|on|in|at|during|over|every|each|due) the? #Date").notIf("#PhrasalVerb").tag("Date","until-june"),e.match("a #Duration").tag("Date",d),e.match("(between|from) #Date").tag("Date",d),e.match("(to|until|upto) #Date").tag("Date",d),e.match("#Date and #Date").tag("Date",d),e.match("(by|until|after|before|during|on|in|following|since) (next|this|last)? (#Date|#Date)").notIf("#PhrasalVerb").tag("Date",d),e.match("the? #Date after next one?").tag("Date",d),e.match("(about|approx|approximately|around) #Date").tag("Date",d)),e};const h="section-tagger";var m=function(e){return e.has("#Date")&&(e.match("this? (last|next|past|this|previous|current|upcoming|coming|the) #Date").tag("Date",h),e.match("(starting|beginning|ending) #Date").tag("Date",h),e.match("the? (start|end|middle|beginning) of (last|next|this|the) (#Date|#Date)").tag("Date",h),e.match("(the|this) #Date").tag("Date",h),e.match("#Date up to #Date").tag("Date",h)),e};const c="time-tagger",f=function(e,t){if(e.found){e.tag("Date",t),e.numbers().lessThan(31).ifNo("#Year").tag("#Time",t)}};var y=function(e){e.match("#Cardinal oclock").tag("Time",c),e.match("/^[0-9]{2}h[0-9]{2}$/").tag("Time",c),e.match("/^[0-9]{2}/[0-9]{2}/").tag("Date",c).unTag("Value"),e.match("[#Value] (in|at) the? (morning|evening|night|nighttime)").tag("Time",c),e.has("#Month")||(e.match("(5|10|15|20|five|ten|fifteen|quarter|twenty|half) (to|after|past) #Cardinal").tag("Time",c),e.match("(at|by|before) (5|10|15|20|five|ten|fifteen|twenty|quarter|half) (after|past|to)").tag("Time","at-20-past"));let t=e.if("#Date");if(t.found){t.match("/^[0-9]{4}[:-][0-9]{2}[:-][0-9]{2}T[0-9]/").tag("Time",c),t.match("#Date [at #Cardinal]",0).notIf("#Year").tag("Time",c),t.match("half an (hour|minute|second)").tag("Date",c),t.match("(in|for|by|near|at) #Timezone").tag("Timezone",c),t.match("#Time to #Time").tag("Date",c),t.match("#Time [(sharp|on the dot)]",0).tag("Time",c);let a=t.if("#NumberRange");if(a.found){let e=a.match("[#NumberRange+] (on|by|at)? #WeekDay",0);f(e,"3-4-tuesday"),a.match("[#NumberRange+] (on|by|at)? #Month #Value",0),f(e,"3-4 mar 3"),a.match("[#NumberRange] to (#NumberRange && #Time)",0),f(e,"3-4pm"),a.match("(#NumberRange && #Time) to [#NumberRange]",0),f(e,"3pm-4")}let n=t.match("(from|between) #Cardinal and #Cardinal (in|on)? (#WeekDay|tomorrow|yesterday)");f(n,"from 9-5 tues"),n=e.match("#Cardinal to #Cardinal (#WeekDay|tomorrow|yesterday)"),f(n,"9-5 tues"),n=t.match("(from|between) [#NumericValue] (to|and) #Time",0).tag("Time","4-to-5pm"),f(n,"from 9-5pm"),n=t.match("(#WeekDay|tomorrow|yesterday) (from|between)? (#Cardinal|#Time) (and|to) (#Cardinal|#Time)"),f(n,"tues 3-5"),n=t.match("#Month #Value+ (from|between) [