Skip to content

Instantly share code, notes, and snippets.

@alisdair
Last active March 7, 2018 17:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alisdair/a2b140943d0c392243ca997054fe1a15 to your computer and use it in GitHub Desktop.
Save alisdair/a2b140943d0c392243ca997054fe1a15 to your computer and use it in GitHub Desktop.
Natural language duration parser
const units = [
{
names: ['day', 'dy', 'd'],
seconds: 86400
}, {
names: ['hour', 'hr', 'h'],
seconds: 3600
}, {
names: ['minute', 'min', 'm'],
seconds: 60
}, {
names: ['second', 'sec', 's'],
seconds: 1
}
];
function pluralize(count, noun) {
return count === 1 ? noun : `${noun}s`;
}
class Duration {
constructor(number, name) {
this.count = parseFloat(number);
this.unit = units.find(u => u.names.includes(name));
this.seconds = Math.floor(this.count * this.unit.seconds);
}
static detect(seconds) {
let unit = units.find(unit => Math.floor(seconds) % unit.seconds === 0);
return new Duration(seconds / unit.seconds, unit.names[0]);
}
toString() {
return `${this.count} ${pluralize(this.count, this.unit.names[0])}`;
}
}
function tokenizer() {
let numbers = '[\\d.]+';
let names = [].concat(...units.map(m => m.names)).join('|');
return new RegExp(`(${numbers})(${names})s?`, 'g');
}
function parse(input) {
let re = tokenizer();
let durations = [];
let result;
input = input.replace(/\s/g, '');
while (result = re.exec(input)) {
let [, number, name] = result;
durations.push(new Duration(number, name));
}
return durations;
}
let good = ['1 day', '1h 2 min', '1d12h', ' 3.5 dy ', '48h'];
let bad = ['', '1 day 5', 'potato 1d tomato', '555', 'day'];
for (let inputs of [good, bad]) {
console.log(inputs.map(input => {
let durations = parse(input);
let seconds = durations.map(d => d.seconds).reduce((a, b) => a + b, 0);
let total = Duration.detect(seconds);
return {
input,
output: durations.join(' '),
seconds,
total: total.toString()
};
}));
}

Good inputs

input output seconds total
"1 day" "1 day" 86400 "1 day"
"1h 2 min" "1 hour 2 minutes" 3720 "62 minutes"
"1d12h" "1 day 12 hours" 129600 "36 hours"
" 3.5 dy " "3.5 days" 302400 "84 hours"
"48h" "48 hours" 172800 "2 days"

Bad inputs

input output seconds total
"day" "" 0 "0 days"
"555" "" 0 "0 days"
"potato 1d tomato" "1 day" 86400 "1 day"
"1 day 5" "1 day" 86400 "1 day"
"" "" 0 "0 days"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment