diff --git a/package.json b/package.json index b68a1798e..0918af8a8 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build:dev": "vite build -m development --outDir dist-dev/", "lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts", "cypress:open": "cypress open", - "test:unit": "vitest", + "test:unit": "vitest --run", "test:unit-watch": "vitest watch", "test:frontend": "cypress run", "typecheck": "vue-tsc --noEmit && vue-tsc --noEmit -p tsconfig.vitest.json --composite false", diff --git a/src/helpers/time/parseDate.ts b/src/helpers/time/parseDate.ts index b4206a1eb..7d157d258 100644 --- a/src/helpers/time/parseDate.ts +++ b/src/helpers/time/parseDate.ts @@ -12,7 +12,9 @@ interface dateFoundResult { 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() if (lowerText.includes('today')) { @@ -62,39 +64,44 @@ export const parseDate = (text: string): dateParseResult => { } parsed = getDayFromText(text) + if (parsed.date !== null) { + const month = getMonthFromText(text, parsed.date) + return addTimeToDate(text, month.date, parsed.foundText) + } + + parsed = getDateFromTextIn(text, now) if (parsed.date !== null) { return addTimeToDate(text, parsed.date, parsed.foundText) } - parsed = getDateFromTextIn(text) - if (parsed.date !== null) { + parsed = getDateFromText(text) + + if (parsed.date === null) { return { newText: replaceAll(text, parsed.foundText, ''), date: parsed.date, } } - parsed = getDateFromText(text) - - return { - newText: replaceAll(text, parsed.foundText, ''), - date: parsed.date, - } + return addTimeToDate(text, parsed.date, parsed.foundText) } -const addTimeToDate = (text: string, date: Date, match: string | null): dateParseResult => { - if (match === null) { +const addTimeToDate = (text: string, date: Date, previousMatch: string | null): dateParseResult => { + previousMatch = previousMatch?.trim() || '' + text = replaceAll(text, previousMatch, '') + if (previousMatch === null) { return { newText: text, 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) if (results !== null) { - const time = results[3] + const time = results[2] const parts = time.split(':') let hours = parseInt(parts[0]) let minutes = 0 @@ -110,7 +117,7 @@ const addTimeToDate = (text: string, date: Date, match: string | null): datePars date.setSeconds(0) } - const replace = results !== null ? results[0] : match + const replace = results !== null ? results[0] : previousMatch return { newText: replaceAll(text, replace, ''), date: date, @@ -127,10 +134,10 @@ export const getDateFromText = (text: string, now: Date = new Date()) => { let containsYear: boolean = true if (result === null) { // 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) - result = results === null ? null : `${results[0]} ${now.getFullYear()}` - foundText = results === null ? '' : results[0] + result = results === null ? null : `${results[0]} ${now.getFullYear()}`.trim() + foundText = results === null ? '' : results[0].trim() containsYear = false if (result === null) { @@ -309,7 +316,7 @@ const getDayFromText = (text: string) => { while (date < now) { date.setMonth(date.getMonth() + 1) } - + if (date.getDate() !== day) { date.setDate(day) } @@ -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 newDate = new Date() newDate.setDate(newDate.getDate() + interval) diff --git a/src/modules/parseTaskText.test.ts b/src/modules/parseTaskText.test.ts index 02bd871e8..83b291a70 100644 --- a/src/modules/parseTaskText.test.ts +++ b/src/modules/parseTaskText.test.ts @@ -1,7 +1,7 @@ import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest' 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 {PRIORITIES} from '@/constants/priorities' @@ -359,7 +359,7 @@ describe('Parse Task Text', () => { it('should not recognize dates in urls', () => { const text = 'https://some-url.org/blog/2019/1/233526-some-more-text' const result = parseTaskText(text) - + expect(result.text).toBe(text) expect(result.date).toBeNull() }) @@ -483,6 +483,15 @@ describe('Parse Task Text', () => { now.setMinutes(0) now.setSeconds(0) + beforeEach(() => { + vi.useFakeTimers() + vi.setSystemTime(now) + }) + + afterEach(() => { + vi.useRealTimers() + }) + const cases = { 'Lorem Ipsum in 1 hour': '2021-6-24 13: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 1 month': '2021-7-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) { it(`should parse '${c}' as '${cases[c]}'`, () => { - const {date} = getDateFromTextIn(c, now) + const {date} = parseDate(c, now) if (date === null && cases[c] === null) { expect(date).toBeNull() return