fix(quick add magic): time parsing for certain conditions (#2367)

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2367
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
konrad 2022-09-15 11:59:29 +00:00
parent 2df2bd38e2
commit b24d5f2dce
3 changed files with 64 additions and 22 deletions

View file

@ -11,7 +11,7 @@
"build:dev": "vite build -m development --outDir dist-dev/", "build:dev": "vite build -m development --outDir dist-dev/",
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts", "lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
"cypress:open": "cypress open", "cypress:open": "cypress open",
"test:unit": "vitest", "test:unit": "vitest --run",
"test:unit-watch": "vitest watch", "test:unit-watch": "vitest watch",
"test:frontend": "cypress run", "test:frontend": "cypress run",
"typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.vitest.json --composite false", "typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.vitest.json --composite false",

View file

@ -12,7 +12,9 @@ interface dateFoundResult {
date: Date | null, date: Date | null,
} }
export const parseDate = (text: string): dateParseResult => { const monthsRegexGroup = '(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)'
export const parseDate = (text: string, now: Date = new Date()): dateParseResult => {
const lowerText: string = text.toLowerCase() const lowerText: string = text.toLowerCase()
if (lowerText.includes('today')) { if (lowerText.includes('today')) {
@ -63,38 +65,43 @@ export const parseDate = (text: string): dateParseResult => {
parsed = getDayFromText(text) parsed = getDayFromText(text)
if (parsed.date !== null) { if (parsed.date !== null) {
return addTimeToDate(text, parsed.date, parsed.foundText) const month = getMonthFromText(text, parsed.date)
return addTimeToDate(text, month.date, parsed.foundText)
} }
parsed = getDateFromTextIn(text) parsed = getDateFromTextIn(text, now)
if (parsed.date !== null) { if (parsed.date !== null) {
return { return addTimeToDate(text, parsed.date, parsed.foundText)
newText: replaceAll(text, parsed.foundText, ''),
date: parsed.date,
}
} }
parsed = getDateFromText(text) parsed = getDateFromText(text)
if (parsed.date === null) {
return { return {
newText: replaceAll(text, parsed.foundText, ''), newText: replaceAll(text, parsed.foundText, ''),
date: parsed.date, date: parsed.date,
} }
}
return addTimeToDate(text, parsed.date, parsed.foundText)
} }
const addTimeToDate = (text: string, date: Date, match: string | null): dateParseResult => { const addTimeToDate = (text: string, date: Date, previousMatch: string | null): dateParseResult => {
if (match === null) { previousMatch = previousMatch?.trim() || ''
text = replaceAll(text, previousMatch, '')
if (previousMatch === null) {
return { return {
newText: text, newText: text,
date: null, date: null,
} }
} }
const matcher = new RegExp(`(${match} (at|@) )([0-9][0-9]?(:[0-9][0-9]?)?( ?(a|p)m)?)`, 'ig') const timeRegex = ' (at|@) ([0-9][0-9]?(:[0-9][0-9]?)?( ?(a|p)m)?)'
const matcher = new RegExp(timeRegex, 'ig')
const results = matcher.exec(text) const results = matcher.exec(text)
if (results !== null) { if (results !== null) {
const time = results[3] const time = results[2]
const parts = time.split(':') const parts = time.split(':')
let hours = parseInt(parts[0]) let hours = parseInt(parts[0])
let minutes = 0 let minutes = 0
@ -110,7 +117,7 @@ const addTimeToDate = (text: string, date: Date, match: string | null): datePars
date.setSeconds(0) date.setSeconds(0)
} }
const replace = results !== null ? results[0] : match const replace = results !== null ? results[0] : previousMatch
return { return {
newText: replaceAll(text, replace, ''), newText: replaceAll(text, replace, ''),
date: date, date: date,
@ -127,10 +134,10 @@ export const getDateFromText = (text: string, now: Date = new Date()) => {
let containsYear: boolean = true let containsYear: boolean = true
if (result === null) { if (result === null) {
// 2. Try parsing the date as something like "jan 21" or "21 jan" // 2. Try parsing the date as something like "jan 21" or "21 jan"
const monthRegex: RegExp = / ((jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) [0-9][0-9]?|[0-9][0-9]? (jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec))/ig const monthRegex: RegExp = new RegExp(` (${monthsRegexGroup} [0-9][0-9]?|[0-9][0-9]? ${monthsRegexGroup})`, 'ig')
results = monthRegex.exec(text) results = monthRegex.exec(text)
result = results === null ? null : `${results[0]} ${now.getFullYear()}` result = results === null ? null : `${results[0]} ${now.getFullYear()}`.trim()
foundText = results === null ? '' : results[0] foundText = results === null ? '' : results[0].trim()
containsYear = false containsYear = false
if (result === null) { if (result === null) {
@ -320,6 +327,25 @@ const getDayFromText = (text: string) => {
} }
} }
const getMonthFromText = (text: string, date: Date) => {
const matcher = new RegExp(monthsRegexGroup, 'ig')
const results = matcher.exec(text)
if (results === null) {
return {
newText: text,
date,
}
}
const fullDate = new Date(`${results[0]} 1 ${(new Date()).getFullYear()}`)
date.setMonth(fullDate.getMonth())
return {
newText: replaceAll(text, results[0], ''),
date,
}
}
const getDateFromInterval = (interval: number): Date => { const getDateFromInterval = (interval: number): Date => {
const newDate = new Date() const newDate = new Date()
newDate.setDate(newDate.getDate() + interval) newDate.setDate(newDate.getDate() + interval)

View file

@ -1,7 +1,7 @@
import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest' import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest'
import {parseTaskText, PrefixMode} from './parseTaskText' import {parseTaskText, PrefixMode} from './parseTaskText'
import {getDateFromText, getDateFromTextIn} from '../helpers/time/parseDate' import {getDateFromText, getDateFromTextIn, parseDate} from '../helpers/time/parseDate'
import {calculateDayInterval} from '../helpers/time/calculateDayInterval' import {calculateDayInterval} from '../helpers/time/calculateDayInterval'
import {PRIORITIES} from '@/constants/priorities' import {PRIORITIES} from '@/constants/priorities'
@ -483,6 +483,15 @@ describe('Parse Task Text', () => {
now.setMinutes(0) now.setMinutes(0)
now.setSeconds(0) now.setSeconds(0)
beforeEach(() => {
vi.useFakeTimers()
vi.setSystemTime(now)
})
afterEach(() => {
vi.useRealTimers()
})
const cases = { const cases = {
'Lorem Ipsum in 1 hour': '2021-6-24 13:0', 'Lorem Ipsum in 1 hour': '2021-6-24 13:0',
'in 2 hours': '2021-6-24 14:0', 'in 2 hours': '2021-6-24 14:0',
@ -493,11 +502,18 @@ describe('Parse Task Text', () => {
'in 4 weeks': '2021-7-22 12:0', 'in 4 weeks': '2021-7-22 12:0',
'in 1 month': '2021-7-24 12:0', 'in 1 month': '2021-7-24 12:0',
'in 3 months': '2021-9-24 12:0', 'in 3 months': '2021-9-24 12:0',
'Something in 5 days at 10:00': '2021-6-29 10:0',
'Something 17th at 10:00': '2021-7-17 10:0',
'Something sep 17 at 10:00': '2021-9-17 10:0',
'Something sep 17th at 10:00': '2021-9-17 10:0',
'Something at 10:00 in 5 days': '2021-6-29 10:0',
'Something at 10:00 17th': '2021-7-17 10:0',
'Something at 10:00 sep 17th': '2021-9-17 10:0',
} }
for (const c in cases) { for (const c in cases) {
it(`should parse '${c}' as '${cases[c]}'`, () => { it(`should parse '${c}' as '${cases[c]}'`, () => {
const {date} = getDateFromTextIn(c, now) const {date} = parseDate(c, now)
if (date === null && cases[c] === null) { if (date === null && cases[c] === null) {
expect(date).toBeNull() expect(date).toBeNull()
return return