How to detect dates with different formats

Viewed 107

Is there a way to detect dates like: "Tomorrow at 16:00"? I know there are some apps to create todo lists or reminders that do this but I don't know where to start. Any ideas? Thanks.

3 Answers

Since you've tagged reactjs I'm assuming you are making use of a package manager and a bundler. In that case: there exist readymade packages that do exactly what you want. A quick search yields chrono-node for example: a lightweight (20KB gzipped) module that parses natural language into a javascript Date object.

Just install by running

npm i chrono-node

and then use it like

import { parseDate } from 'chrono-node';
const date = parseDate('This Friday at 13:00'); 

The date variable can now be used like any other javascript Date.

A full list of examples can be found on the project page. To cite a few:

  • Today, Tomorrow, Yesterday, Last Friday, etc
  • 17 August 2013 - 19 August 2013
  • This Friday from 13:00 - 16.00
  • 5 days ago
  • 2 weeks from now
  • Sat Aug 17 2013 18:40:39 GMT+0900 (JST)
  • 2014-11-30T08:15:30-05:30

I mean, you "could" create something to recognize dates like that, but then you're moving into a realm of NLP and very complex algorithms. Or you could just build up a large predefined dictionary of all possible words/dates combinations, but that's really not the way to do it.

You want to work with the date object (new Date) and keep the date in that, then when you display it to a user you can prettify and style it any way you want and use whatever words you want, but when working with the date itself it should be in a proper date format.

There are to answers to your question, and each depend on the complexity of the text that you want to analyze:

1. Strict and formal text format:

buckle up. If you don't want to read use chrono-node (soimon 's answer)

This means that the text you'll analyze follows a clear set of rules of formatting, therefore, a word-by-word-step-structured-functional (don't know what else to call it) parsing of the text, with the input being the curent date, could be a good way to reach your goal (from my perspective).

So texts could have this format:

time adverb [day, number, unit, weekday] at time (where day, number, unit and weekday are optional)

This means that the text starts by telling a day and then specifies a time (from general to specific)

Example:

  • Tomorrow at 19:00
  • In 5 days at 18:40
  • Next Sunday at 13:20

So let's say you'll implement a function that returns the result date from the text. You will need the granular text and the current date:

function getDateFromText(text, startDate = new Date()) {
    // ...
}

text would be something like: ["In", "5", "days", "at", "17:00"]

So if you start with "Tomorrow", that let's you know the day, but if you start with "In" or "Next" you'll have to provide aditional data (number unit and weekday respectively. That's a condition to specify in the code).

In the function, you will iterate each word,

So the array starts with a time adverb, e.g. today, tomorrow, now, next, in... You can map each of these with a function that returns the date modified accordingly.

const timeAdverbsHandlersDict = {
    "Tomorrow": (date, _) => date.setDate(date.getDate() + 1),
    "In": (date, _) => null
    // ...
}

NOTE

Returning a new Date at each step is not mandatory. Dates are objects, so their reference remains. You can just apply modifications to the input date and they will be preserved in the next steps. I return a new one at each step so I won't have to set a condition for the last step

As you can see, "Tomorrow" maps to a function that returns a date with 1 day added. "In" returns the same date because it needs an extra step (number and unit in that order). _ is an obfuscated argument which I will explain in a moment.

Since the input starts with "In", we need to implement a condition to be aware of the number and the unit. Since we're just getting a number, we can call the mapped function with the unit that is in the next index:

const unitsHandlerDict = {
    "days": (date, number) => (
        number ? 
        new Date(date.setDate(date.getDate() + parseInt(number))) 
        : 
        date
    ),
    "months": (date, number) => (
        number ? 
        new Date(date.setMonth(date.getMonth() + parseInt(number))) 
        : 
        date),
    // ...
}

return unitsHandlerDict[text[index + 1]](date, text[index])

You're now wondering why we have a conditional for number, that's because we're going to use the same map for the next index in the array, which points to "days" in our example, and we'll call it without the second argument, that will return the same date and that's great, because we have already performed the necessary operations on it (actually creating new dates from the old one).

When you reach the "at", you can just set the date's hours and minutes by simply:

const [hours, minutes] = text[index].split(":")
const newDate = new Date(date.setHours(parseInt(hours)))

return new Date(newDate.setMinutes(parseInt(minutes)))

2. Free text

As Matriarx said, NLPs should be the way to go.

Related