fix: remove date-fns (#3039)

This removes date-fns and replaces it with the already used dayjs library. It does not make sense to have two libraries for the same purpose, and dayjs seems to be smaller and its translations are already integrated. Since we have to use dayjs because it is used by the gantt chart, this was the obvious way to go (instead of replacing dayjs with date-fns).

Resolves https://github.com/go-vikunja/vikunja/issues/391

Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/3039
Co-authored-by: kolaente <k@knt.li>
Co-committed-by: kolaente <k@knt.li>
This commit is contained in:
kolaente 2025-02-21 15:42:58 +00:00 committed by konrad
parent 5cea469f8c
commit 021d71b90e
13 changed files with 45 additions and 54 deletions

View File

@ -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')

View File

@ -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')

View File

@ -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",

View File

@ -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: {}

View File

@ -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')
}
</script>

View File

@ -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)),
})
}

View File

@ -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

View File

@ -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) {

View File

@ -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())
}

View File

@ -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<SupportedLocal
return setLanguage(getBrowserLanguage())
}
}
await loadDayJsLocale(lang)
i18n.global.locale.value = lang
document.documentElement.lang = lang

View File

@ -56,6 +56,14 @@ export const DAYJS_LANGUAGE_IMPORTS = {
'ko-kr': () => import('dayjs/locale/ko'),
} as Record<SupportedLocale, () => Promise<ILocale>>
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
},

View File

@ -1,4 +0,0 @@
/**
* Date in Format 2022-12-10
*/
export type DateKebab = `${string}-${string}-${string}`

View File

@ -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)