Skip to content

Commit

Permalink
feat(language+en): Rewrite of date and time utilities + week support …
Browse files Browse the repository at this point in the history
…for dates
  • Loading branch information
aholstenson committed Sep 29, 2018
1 parent 21041cc commit ceaef0d
Show file tree
Hide file tree
Showing 13 changed files with 1,735 additions and 510 deletions.
434 changes: 321 additions & 113 deletions language/dates.js

Large diffs are not rendered by default.

105 changes: 46 additions & 59 deletions language/en/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@
const Parser = require('../../parser');
const utils = require('../dates');

const cloneDeep = require('lodash.clonedeep');

const setISODay = require('date-fns/set_iso_day');
const getISODay = require('date-fns/get_iso_day');
const addWeeks = require('date-fns/add_weeks');
const addDays = require('date-fns/add_days');

function value(v) {
if(Array.isArray(v)) {
if(v[0] === v) return null;
Expand All @@ -31,49 +24,19 @@ function hasMonth(v) {
}

function withDay(date, day) {
const result = cloneDeep(date);
result.day = value(day);
return result;
return utils.combine(date, {
day: value(day)
});
}

function withYear(date, year) {
const result = cloneDeep(date);
result.year = year.year > 0 ? year.year : value(year);
return result;
}

function currentTime(encounter) {
if(encounter.options.now) {
return encounter.options.now;
} else {
return encounter.options.now = new Date();
}
function withYear(v) {
return utils.combine(v[0], {
year: value(v[1])
});
}

function adjustedDays(date, diff) {
date = addDays(date, diff);
return {
year: date.getFullYear(),
month: date.getMonth(),
day: date.getDate()
};
}

function nextDayOfWeek(v, e) {
let date = currentTime(e);

// TODO: Would this change if start of week is on Sunday?
const dayOfWeek = v[0].value;
const currentDayOfWeek = getISODay(date);
if(currentDayOfWeek >= dayOfWeek) {
date = addWeeks(date, 1);
}
date = setISODay(date, dayOfWeek);
return {
year: date.getFullYear(),
month: date.getMonth(),
day: date.getDate()
};
function nextDayOfWeek(v) {
return { dayOfWeek: v[0].value };
}

module.exports = function(language) {
Expand All @@ -83,38 +46,43 @@ module.exports = function(language) {
const month = language.month;
const year = language.year;

const day = Parser.result(ordinal, v => v.value >= 0 && v.value < 31);

return new Parser(language)
.name('date')

.skipPunctuation()

// This Sunday, Next Monday or On Tuesday
.add(dayOfWeek, nextDayOfWeek)
.add([ 'this', dayOfWeek ], nextDayOfWeek)
.add([ 'next', dayOfWeek ], nextDayOfWeek)
.add([ 'on', dayOfWeek ], nextDayOfWeek)

// Expressions for describing the day, such as today and tomorrow
.add('today', (v, e) => adjustedDays(currentTime(e), 0))
.add('tomorrow', (v, e) => adjustedDays(currentTime(e), 1))
.add('day after tomorrow', (v, e) => adjustedDays(currentTime(e), 2))
.add('the day after tomorrow', (v, e) => adjustedDays(currentTime(e), 2))
.add('yesterday', (v, e) => adjustedDays(currentTime(e), -1))
.add('today', () => ({ relativeDays: 0 }))
.add('tomorrow', () => ({ relativeDays: 1 }))
.add('day after tomorrow', () => ({ relativeDays: 2 }))
.add('the day after tomorrow', () => ({ relativeDays: 2 }))
.add('yesterday', () => ({ relativeDays: -1 }))

// Month followed by day - Jan 12, February 1st
.add([ month, Parser.result(ordinal, v => v.value >= 0 && v.value < 31) ], v => withDay(v[0], v[1]))
.add([ month, day ], v => withDay(v[0], v[1]))

// Just the day
.add([ day ], v => { return { day: v[0].value } })

// Day followed by month - 12 Jan, 1st February
.add([ ordinal, month ], v => withDay(v[1], v[0]))
.add([ ordinal, 'of', month ], v => withDay(v[1], v[0]))
.add([ day, month ], v => withDay(v[1], v[0]))
.add([ day, 'of', month ], v => withDay(v[1], v[0]))

// Non-year (month and day) followed by year
// With day: 12 Jan 2018, 1st February 2018
// Without day: Jan 2018, this month 2018
.add([ Parser.result(hasMonth), year ], v => utils.combine(v[0], v[1]))

.add([ month, /^[0-9]{1,2}$/ ], v => withYear(v[0], v[1]))
.add([ month, 'in', /^[0-9]{1,2}$/ ], v => withYear(v[0], v[1]))
.add([ month, 'of', /^[0-9]{1,2}$/ ], v => withYear(v[0], v[1]))
.add([ month, 'in', /^[0-9]{1,2}$/ ], withYear)
.add([ month, 'of', /^[0-9]{1,2}$/ ], withYear)

// Year - Month - Day, such as 2017-01-24 or 2017 2 5
.add([ /^[0-9]{4}$/, /^[0-9]{1,2}$/, /^[0-9]{1,2}$/ ], v => {
Expand Down Expand Up @@ -145,8 +113,13 @@ module.exports = function(language) {
// Relative dates
.add([ integer, 'days' ], v => { return { relativeDays: v[0].value }})
.add([ integer, 'months' ], v => { return { relativeMonths: v[0].value }})
.add([ integer, 'weeks' ], v => { return { relativeDays: v[0].value * 7 }})
.add([ integer, 'years' ], v => { return { relativeYears: v[0].value, period: 'year' }})
.add([ integer, 'weeks' ], v => { return { relativeWeeks: v[0].value }})
.add([ year ], v => v[0])

.add([ 'this week' ], () => ({ relativeWeeks: 0, intervalEdge: 'start' }))
.add([ 'week', ordinal ], v => ({ week: v[0].value }))
.add('start of week', () => ({ relativeWeeks: 0, intervalEdge: 'start' }))
.add('end of week', () => ({ relativeWeeks: 0, intervalEdge: 'end' }))

.add([ Parser.result(utils.isRelative), Parser.result(utils.isRelative) ], v => utils.combine(v[0], v[1]))
.add([ Parser.result(utils.isRelative), 'and', Parser.result(utils.isRelative) ], v => utils.combine(v[0], v[1]))
Expand All @@ -168,10 +141,24 @@ module.exports = function(language) {
dayOfWeekOrdinal: v[0].value
}))

// Week N of year
.add([ 'week', ordinal, year ], v => utils.combine(v[1], {
week: v[0].value
}))

.add([ ordinal, 'week', year ], v => utils.combine(v[1], {
week: v[0].value
}))

.add([ 'in', Parser.result() ], v => v[0])
.add([ 'on', Parser.result() ], v => v[0])
.add([ 'on', 'the', Parser.result() ], v => v[0])

// Edges, such as start of [date] or end of [date]
.add([ 'start of', Parser.result() ], utils.startOf)
.add([ 'beginning of', Parser.result() ], utils.startOf)
.add([ 'end of', Parser.result() ], utils.endOf)

.mapResults(utils.mapDate)
.onlyBest();
}
11 changes: 3 additions & 8 deletions language/en/datetime.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,15 @@ module.exports = function(language) {

.skipPunctuation()

.add(time, v => v[0])

.add([ time, date ], v => utils.combine(v[0], v[1]))
.add([ time, 'and', date ], v => utils.combine(v[0], v[1]))

.add([ date, time ], v => utils.combine(v[0], v[1]))
.add([ date, 'and', time ], v => utils.combine(v[0], v[1]))

.add(Parser.result(date, utils.isRelative), (v, e) => {
const now = utils.currentTime(e);
return utils.combine(v[0], {
hour: now.getHours(),
minute: now.getMinutes()
});
})
.add(time, v => v[0])
.add(Parser.result(date, utils.isRelative), (v, e) => v[0])

.mapResults(utils.mapDateTime)
.onlyBest();
Expand Down
28 changes: 6 additions & 22 deletions language/en/month.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,7 @@
'use strict';

const Parser = require('../../parser');
const utils = require('../dates');
const addMonths = require('date-fns/add_months');

function currentTime(encounter) {
if(encounter.options.now) {
return encounter.options.now;
} else {
return encounter.options.now = new Date();
}
}

function adjustedMonth(date, diff) {
date = addMonths(date, diff);
return {
year: date.getFullYear(),
month: date.getMonth()
};
}
const { mapMonth } = require('../dates');

module.exports = function(language) {
const integer = language.integer;
Expand Down Expand Up @@ -71,13 +54,14 @@ module.exports = function(language) {
)

// Dynamic months
.add('this month', (v, e) => adjustedMonth(currentTime(e), 0))
.add('last month', (v, e) => adjustedMonth(currentTime(e), -1))
.add('next month', (v, e) => adjustedMonth(currentTime(e), +1))
.add('this month', () => ({ relativeMonths: 0 }))
.add('previous month', () => ({ relativeMonths: -1 }))
.add('last month', () => ({ relativeMonths: -1 }))
.add('next month', () => ({ relativeMonths: 1 }))
.add([ 'in', integer, 'months' ], v => { return { relativeMonths: v[0].value }})

.add([ 'in', Parser.result() ], v => v[0])

.mapResults(utils.mapMonth)
.mapResults(mapMonth)
.onlyBest();
}
30 changes: 17 additions & 13 deletions language/en/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,24 @@ function isHour(v) {
}

function adjustMinutes(time, minutes) {
const result = cloneDeep(time);
result.minute = (result.minute || 0) + minutes;
return result;
}

function relativeTime(seconds) {
return {
relative: seconds
}
return utils.combine(time, {
relativeMinutes: minutes
});
}

function reverseRelativeTime(v) {
const result = cloneDeep(v[0]);
result.relative = -result.relative;
if(result.relativeHours) {
result.relativeHours = - result.relativeHours;
}

if(result.relativeMinutes) {
result.relativeMinutes = - result.relativeMinutes;
}

if(result.relativeSeconds) {
result.relativeSeconds = - result.relativeSeconds;
}
return result;
}

Expand All @@ -45,9 +49,9 @@ module.exports = function(language) {
const relativeTimes = new Parser(language)
.name('relativeTime')

.add([ integer, 'hours' ], v => relativeTime(v[0].value * 3600))
.add([ integer, 'minutes' ], v => relativeTime(v[0].value * 60))
.add([ integer, 'seconds' ], v => relativeTime(v[0].value))
.add([ integer, 'hours' ], v => ({ relativeHours: v[0].value }))
.add([ integer, 'minutes' ], v => ({ relativeMinutes: v[0].value }))
.add([ integer, 'seconds' ], v => ({ relativeSeconds: v[0].value }))

.add([ Parser.result(), Parser.result() ], v => utils.combine(v[0], v[1]))
.add([ Parser.result(), 'and', Parser.result() ], v => utils.combine(v[0], v[1]));
Expand Down
10 changes: 5 additions & 5 deletions language/en/year.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ module.exports = function(language) {
return new Parser(language)
.name('year')

.add([ /^[0-9]{4}$/ ], v => { return { year: parseInt(v[0]) }})
.add('this year', (v, e) => { return { year: utils.currentTime(e).getFullYear() }})
.add('next year', (v, e) => { return { year: utils.currentTime(e).getFullYear() + 1 }})
.add('last year', (v, e) => { return { year: utils.currentTime(e).getFullYear() - 1 }})
.add([ 'in', integer, 'years' ], v => { return { relativeYear: v[0].value }})
.add([ /^[0-9]{4}$/ ], v => { return { year: parseInt(v[0]), yearType: 'numeric' }})
.add('this year', (v, e) => { return { year: utils.currentTime(e).getFullYear(), yearType: 'text' }})
.add('next year', (v, e) => { return { year: utils.currentTime(e).getFullYear() + 1, yearType: 'text', }})
.add('last year', (v, e) => { return { year: utils.currentTime(e).getFullYear() - 1, yearType: 'text' }})
.add([ 'in', integer, 'years' ], v => { return { relativeYears: v[0].value, yearType: 'relative' }})

.add([ 'in', Parser.result() ], v => v[0])
.add([ 'of', Parser.result() ], v => v[0])
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"description": "Natural language handling for commands and intents",
"main": "index.js",
"scripts": {
"test": "node_modules/.bin/mocha test/**/*.test.js && node_modules/.bin/eslint *.js language/**/*.js resolver/*.js values/*.js",
"coverage": "node_modules/istanbul/lib/cli.js cover node_modules/mocha/bin/_mocha -- -R spec ./test/*.test.js"
"test": "node_modules/.bin/mocha --recursive test/ && node_modules/.bin/eslint .",
"coverage": "node_modules/istanbul/lib/cli.js cover node_modules/mocha/bin/_mocha -- -R spec ./test/**.test.js"
},
"license": "MIT",
"repository": "aholstenson/ecolect-js",
Expand Down
Loading

0 comments on commit ceaef0d

Please sign in to comment.