diff --git a/frontend/cypress/e2e/project/project-view-gantt.spec.ts b/frontend/cypress/e2e/project/project-view-gantt.spec.ts index f8e3f84f7..16eadab20 100644 --- a/frontend/cypress/e2e/project/project-view-gantt.spec.ts +++ b/frontend/cypress/e2e/project/project-view-gantt.spec.ts @@ -1,4 +1,4 @@ -import {formatISO, format} from 'date-fns' +import dayjs from 'dayjs' import {createFakeUserAndLogin} from '../../support/authenticateUser' @@ -28,8 +28,8 @@ describe('Project View Gantt', () => { cy.visit('/projects/1/2') cy.get('.g-timeunits-container') - .should('contain', format(now, 'MMMM')) - .should('contain', format(nextMonth, 'MMMM')) + .should('contain', dayjs(now).format('MMMM')) + .should('contain', dayjs(nextMonth).format('MMMM')) }) it('Shows tasks with dates', () => { @@ -112,8 +112,8 @@ describe('Project View Gantt', () => { it('Should open a task when double clicked on it', () => { const now = new Date() const tasks = TaskFactory.create(1, { - start_date: formatISO(now), - end_date: formatISO(now.setDate(now.getDate() + 4)), + start_date: dayjs(now).format(), + end_date: dayjs(now.setDate(now.getDate() + 4)).format(), }) cy.visit('/projects/1/2') diff --git a/frontend/cypress/e2e/task/task.spec.ts b/frontend/cypress/e2e/task/task.spec.ts index d84935d68..5040cc49c 100644 --- a/frontend/cypress/e2e/task/task.spec.ts +++ b/frontend/cypress/e2e/task/task.spec.ts @@ -12,7 +12,7 @@ import {BucketFactory} from '../../factories/bucket' import {TaskAttachmentFactory} from '../../factories/task_attachments' import {TaskReminderFactory} from '../../factories/task_reminders' -import {createDefaultViews} from "../project/prepareProjects"; +import {createDefaultViews} from '../project/prepareProjects' import { TaskBucketFactory } from '../../factories/task_buckets' function addLabelToTaskAndVerify(labelTitle: string) { @@ -572,7 +572,7 @@ describe('Task', () => { const day = today.toLocaleString('default', {day: 'numeric'}) const month = today.toLocaleString('default', {month: 'short'}) const year = today.toLocaleString('default', {year: 'numeric'}) - const date = `${day} ${month} ${year}, 12:00:00` + const date = `${month} ${day}, ${year} 12:00 PM` cy.get('.task-view .columns.details .column') .contains('Due Date') .get('.date-input .datepicker-popup') @@ -615,7 +615,7 @@ describe('Task', () => { const day = today.toLocaleString('default', {day: 'numeric'}) const month = today.toLocaleString('default', {month: 'short'}) const year = today.toLocaleString('default', {year: 'numeric'}) - const date = `${day} ${month} ${year}, 12:00:00` + const date = `${month} ${day}, ${year} 12:00 PM` cy.get('.task-view .columns.details .column') .contains('Due Date') .get('.date-input .datepicker-popup') diff --git a/frontend/package.json b/frontend/package.json index cab43bb20..b9655d987 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -84,7 +84,6 @@ "blurhash": "2.0.5", "bulma-css-variables": "0.9.33", "change-case": "5.4.4", - "date-fns": "4.1.0", "dayjs": "1.11.13", "dompurify": "3.2.3", "fast-deep-equal": "3.1.3", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 05d2d5cd1..9c2938fd8 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -121,9 +121,6 @@ importers: change-case: specifier: 5.4.4 version: 5.4.4 - date-fns: - specifier: 4.1.0 - version: 4.1.0 dayjs: specifier: 1.11.13 version: 1.11.13 @@ -3430,9 +3427,6 @@ packages: resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} engines: {node: '>= 0.4'} - date-fns@4.1.0: - resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} - dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} @@ -10224,8 +10218,6 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.1 - date-fns@4.1.0: {} - dayjs@1.11.13: {} de-indent@1.0.2: {} diff --git a/frontend/src/components/input/DatepickerInline.vue b/frontend/src/components/input/DatepickerInline.vue index bee2f3e85..ecd152943 100644 --- a/frontend/src/components/input/DatepickerInline.vue +++ b/frontend/src/components/input/DatepickerInline.vue @@ -127,7 +127,7 @@ const flatPickrDate = computed({ } if (date.value !== null) { - const oldDate = formatDate(date.value, 'yyy-LL-dd H:mm') + const oldDate = formatDate(date.value, 'YYYY-MM-DD h:m') if (oldDate === newValue) { return } @@ -140,7 +140,7 @@ const flatPickrDate = computed({ return '' } - return formatDate(date.value, 'yyy-LL-dd H:mm') + return formatDate(date.value, 'YYYY-MM-DD h:m') }, }) @@ -208,7 +208,7 @@ function getWeekdayFromStringInterval(dateString: string) { const interval = calculateDayInterval(dateString) const newDate = new Date() newDate.setDate(newDate.getDate() + interval) - return formatDate(newDate, 'E') + return formatDate(newDate, 'ddd') } diff --git a/frontend/src/components/tasks/GanttChart.vue b/frontend/src/components/tasks/GanttChart.vue index cf13335a5..842c0798e 100644 --- a/frontend/src/components/tasks/GanttChart.vue +++ b/frontend/src/components/tasks/GanttChart.vue @@ -52,7 +52,6 @@ import {getHexColor} from '@/models/task' import {colorIsDark} from '@/helpers/color/colorIsDark' import {isoToKebabDate} from '@/helpers/time/isoToKebabDate' -import {parseKebabDate} from '@/helpers/time/parseKebabDate' import type {ITask, ITaskPartialWithId} from '@/modelTypes/ITask' import type {DateISO} from '@/types/DateISO' @@ -181,8 +180,8 @@ async function updateGanttTask(e: { }) { emit('update:task', { id: Number(e.bar.ganttBarConfig.id), - startDate: new Date(parseKebabDate(e.bar.startDate).setHours(0,0,0,0)), - endDate: new Date(parseKebabDate(e.bar.endDate).setHours(23,59,0,0)), + startDate: new Date((new Date(e.bar.startDate)).setHours(0,0,0,0)), + endDate: new Date((new Date(e.bar.endDate)).setHours(23,59,0,0)), }) } diff --git a/frontend/src/constants/date.ts b/frontend/src/constants/date.ts index a47b0c391..23820dba2 100644 --- a/frontend/src/constants/date.ts +++ b/frontend/src/constants/date.ts @@ -1,5 +1,3 @@ -export const DATEFNS_DATE_FORMAT_KEBAB = 'yyyy-LL-dd' as const - export const SECONDS_A_MINUTE = 60 export const SECONDS_A_HOUR = SECONDS_A_MINUTE * 60 export const SECONDS_A_DAY = SECONDS_A_HOUR * 24 diff --git a/frontend/src/helpers/time/formatDate.ts b/frontend/src/helpers/time/formatDate.ts index 69408caf4..dbe73de85 100644 --- a/frontend/src/helpers/time/formatDate.ts +++ b/frontend/src/helpers/time/formatDate.ts @@ -1,15 +1,10 @@ import {createDateFromString} from '@/helpers/time/createDateFromString' -import {format, formatDistanceToNow} from 'date-fns' - -// FIXME: support all locales and load dynamically -import {enGB, de, fr, ru} from 'date-fns/locale' +import dayjs from 'dayjs' import {i18n} from '@/i18n' import {createSharedComposable} from '@vueuse/core' import {computed, toValue, type MaybeRefOrGetter} from 'vue' -const locales = {en: enGB, de, ch: de, fr, ru} - export function dateIsValid(date: Date | null) { if (date === null) { return false @@ -18,35 +13,36 @@ export function dateIsValid(date: Date | null) { return date instanceof Date && !isNaN(date) } -export const formatDate = (date, f, locale = i18n.global.t('date.locale')) => { +export const formatDate = (date: Date | string | null, f: string, locale = i18n.global.t('date.locale')) => { if (!dateIsValid(date)) { return '' } date = createDateFromString(date) - return date ? format(date, f, {locale: locales[locale]}) : '' + return date + ? dayjs(date).locale(locale).format(f) + : '' } export function formatDateLong(date) { - return formatDate(date, 'PPPPpppp') + return formatDate(date, 'LLLL') } export function formatDateShort(date) { - return formatDate(date, 'PPpp') + return formatDate(date, 'lll') } -export const formatDateSince = (date) => { +export const formatDateSince = (date: Date | string | null, locale = i18n.global.t('date.locale')) => { if (!dateIsValid(date)) { return '' } date = createDateFromString(date) - return formatDistanceToNow(date, { - locale: locales[i18n.global.t('date.locale')], - addSuffix: true, - }) + return date + ? dayjs(date).locale(locale).fromNow() + : '' } export function formatISO(date) { diff --git a/frontend/src/helpers/time/parseKebabDate.ts b/frontend/src/helpers/time/parseKebabDate.ts deleted file mode 100644 index f1643aa48..000000000 --- a/frontend/src/helpers/time/parseKebabDate.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {parse} from 'date-fns' -import {DATEFNS_DATE_FORMAT_KEBAB} from '@/constants/date' -import type {DateKebab} from '@/types/DateKebab' - -export function parseKebabDate(date: DateKebab): Date { - return parse(date, DATEFNS_DATE_FORMAT_KEBAB, new Date()) -} \ No newline at end of file diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index c0c8f26d5..cf54bee99 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -2,6 +2,14 @@ import {createI18n} from 'vue-i18n' import type {PluralizationRule} from 'vue-i18n' import langEN from './lang/en.json' +import localizedFormat from 'dayjs/plugin/localizedFormat' +import relativeTime from 'dayjs/plugin/relativeTime' +import dayjs from 'dayjs' +import {loadDayJsLocale} from '@/i18n/useDayjsLanguageSync.ts' + +dayjs.extend(localizedFormat) +dayjs.extend(relativeTime) + export const SUPPORTED_LOCALES = { 'en': 'English', 'de-DE': 'Deutsch', @@ -85,6 +93,8 @@ export async function setLanguage(lang: SupportedLocale): Promise import('dayjs/locale/ko'), } as Record Promise> +export async function loadDayJsLocale(language: SupportedLocale) { + if (language === 'en') { + return + } + + await DAYJS_LANGUAGE_IMPORTS[language.toLowerCase()]() +} + export function useDayjsLanguageSync(dayjsGlobal: typeof dayjs) { const dayjsLanguageLoaded = ref(false) @@ -70,7 +78,7 @@ export function useDayjsLanguageSync(dayjsGlobal: typeof dayjs) { if (dayjsLanguageLoaded.value) { return } - await DAYJS_LANGUAGE_IMPORTS[currentLanguage.toLowerCase()]() + await loadDayJsLocale(currentLanguage) dayjsGlobal.locale(dayjsLanguageCode) dayjsLanguageLoaded.value = true }, diff --git a/frontend/src/types/DateKebab.ts b/frontend/src/types/DateKebab.ts deleted file mode 100644 index bdc1808df..000000000 --- a/frontend/src/types/DateKebab.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** -* Date in Format 2022-12-10 -*/ -export type DateKebab = `${string}-${string}-${string}` diff --git a/frontend/src/views/tasks/ShowTasks.vue b/frontend/src/views/tasks/ShowTasks.vue index 4915d965d..6ef71bcb1 100644 --- a/frontend/src/views/tasks/ShowTasks.vue +++ b/frontend/src/views/tasks/ShowTasks.vue @@ -130,8 +130,8 @@ const pageTitle = computed(() => { return showAll.value ? t('task.show.titleCurrent') : t('task.show.fromuntil', { - from: formatDate(props.dateFrom, 'PPP'), - until: formatDate(props.dateTo, 'PPP'), + from: formatDate(props.dateFrom, 'LL'), + until: formatDate(props.dateTo, 'LL'), }) }) const hasTasks = computed(() => tasks.value && tasks.value.length > 0)