Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src/components/NcDateTimePicker/NcDateTimePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ import NcTimezonePicker from '../NcTimezonePicker/NcTimezonePicker.vue'
import { t } from '../../l10n.ts'
import NcButton from '../NcButton/index.ts'
import { getDateFormat, getDateTimeFormat, getMonthFormat, getTimeFormat, getWeekFormat, getYearFormat } from './format.ts'
import { checkForNonStandardTokens } from './formatValidation.ts'
import useDateFnsLocale from './useDateFnsLocale.ts'

type LibraryFormatOptions = VueDatePickerProps['format']
Expand Down Expand Up @@ -556,6 +557,20 @@ watch(dateFnsLocale, () => {
flush: 'post',
})

watchEffect(() => {
if (typeof props.format !== 'string') {
return
}
const nonStandardTokens = checkForNonStandardTokens(props.format)
if (nonStandardTokens.length !== 0) {
let warning = `[NcDateTimePicker] The \`format\` property value "${props.format}" uses the non-standard formating tokens ${nonStandardTokens.join(', ')}.`
warning += ' They will be disabled in the future because they are only supported by the date-fns library.'
warning += ' Only use tokens from the Unicode Technical Standard #35.'
warning += ' See https://github.com/nextcloud-libraries/nextcloud-vue/issues/8931'
warn(warning)
}
})

/**
* The date (time) formatting to be used by the library.
* We use the provided format if possible, otherwise we provide a localized formatting
Expand Down
42 changes: 42 additions & 0 deletions src/components/NcDateTimePicker/formatValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

/**
* List of tokens supported by date-fns but not Unicode Technical Standard #35.
*
* @see https://date-fns.org/v4.4.0/docs/format
*/
const NON_STANDARD_TOKENS_OF_DATE_FNS = new Set([
'i',
'I',
'R',
't',
'T',
'o',
'P',
'p',
])

const QUOTE_CHAR = "'"

/**
* Returns array of used non-standard tokens.
*
* @param format The format string.
*/
export function checkForNonStandardTokens(format: string): string[] {
const usedNonStandardTokens = new Set<string>()
let isQuoted = false
for (const char of format) {
if (char === QUOTE_CHAR) {
isQuoted = !isQuoted
} else if (!isQuoted) {
if (NON_STANDARD_TOKENS_OF_DATE_FNS.has(char)) {
usedNonStandardTokens.add(char)
}
}
}
return [...usedNonStandardTokens]
}
44 changes: 44 additions & 0 deletions tests/unit/components/NcDateTimePicker/NcDateTimePicker.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,49 @@ describe('NcDateTimePicker.vue', () => {

expect(wrapper.find('[data-test-id="open-time-picker-btn"]').exists()).toBe(isRendered)
})

describe('Non-standard token handling', () => {
it('warns about non-standard formatting tokens', () => {
const warnHandler = vi.fn()

mount(NcDateTimePicker, {
props: { format: 'Pp' },
global: {
config: { warnHandler },
},
})

expect(warnHandler).toHaveBeenCalledOnce()
expect(warnHandler.mock.calls[0][0]).toEqual('[NcDateTimePicker] The `format` property value "Pp" uses the non-standard formating tokens P, p. They will be disabled in the future because they are only supported by the date-fns library. Only use tokens from the Unicode Technical Standard #35. See https://github.com/nextcloud-libraries/nextcloud-vue/issues/8931')
})

it('warns about non-standard formatting tokens after property change', async () => {
const warnHandler = vi.fn()
const wrapper = mount(NcDateTimePicker, {
props: {
format: 'HH',
},
global: {
config: { warnHandler },
},
})

await wrapper.setProps({ format: 'II-RR' })

expect(warnHandler).toHaveBeenCalledOnce()
expect(warnHandler.mock.calls[0][0])
.toContain('[NcDateTimePicker] The `format` property value "II-RR" uses the non-standard formating tokens I, R.')
})

it('supports non-standard tokens', async () => {
const wrapper = mount(NcDateTimePicker, {
props: { modelValue: new Date(2026, 1, 10), format: 'II-RR' },
})

await nextTick()

expect(wrapper.find('input').element.value).toBe('07-2026')
})
})
})
})
31 changes: 31 additions & 0 deletions tests/unit/components/NcDateTimePicker/formatValidation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import { describe, expect, it } from 'vitest'
import { checkForNonStandardTokens } from '../../../../src/components/NcDateTimePicker/formatValidation.ts'

describe('checkForNonStandardTokens', () => {
it.for([
"''",
"h 'o''clock'",
"yyyy''",
'hh@mm',
])('detects in %s no non-standard tokens', (format) => {
const tokens = checkForNonStandardTokens(format)

expect(tokens).toHaveLength(0)
})

const casesWithNonStandardTokens = [
['II-RR', ['I', 'R']],
['iIRtToPp', ['i', 'I', 'R', 't', 'T', 'o', 'P', 'p']],
] as const

it.for(casesWithNonStandardTokens)('detects in %s non-standard tokens %j ', ([format, nonStandardTokens]) => {
const tokens = checkForNonStandardTokens(format)

expect(tokens).toEqual(nonStandardTokens)
})
})
Loading