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
5 changes: 3 additions & 2 deletions src/generate/dayjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ const generateConfig: GenerateConfig<Dayjs> = {
getShortMonths: (locale) => dayjs().locale(parseLocale(locale)).localeData().monthsShort(),
format: (locale, date, format) => getUDayjs(date).locale(parseLocale(locale)).format(format),
parse: (locale, text, formats) => {
const localeStr = parseLocale(locale);
// Preserve Day.js fallback when the requested locale data is not registered.
const localeStr = dayjs().locale(parseLocale(locale)).locale();
for (let i = 0; i < formats.length; i += 1) {
const format = formats[i];
const formatText = text;
Expand All @@ -183,7 +184,7 @@ const generateConfig: GenerateConfig<Dayjs> = {
parseNoMatchNotice();
return null;
}
const date = dayjs(formatText, format, true).locale(localeStr);
const date = dayjs(formatText, format, localeStr, true);
if (date.isValid()) {
return date;
}
Expand Down
64 changes: 64 additions & 0 deletions tests/generate.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import MockDate from 'mockdate';
import dayjs from 'dayjs';
import dateFnsGenerateConfig from '../src/generate/dateFns';
import dayjsGenerateConfig from '../src/generate/dayjs';
import luxonGenerateConfig from '../src/generate/luxon';
Expand All @@ -7,6 +8,7 @@ import { getMoment } from './util/commonUtil';

import 'dayjs/locale/zh-cn';
import 'dayjs/locale/ko';
import 'dayjs/locale/fr';
import type { GenerateConfig } from '../src/generate';

describe('Picker.Generate', () => {
Expand Down Expand Up @@ -289,6 +291,68 @@ describe('Generate:moment', () => {
});

describe('Generate:dayjs', () => {
describe('locale-aware parsing', () => {
let originalLocale: string;

beforeEach(() => {
originalLocale = dayjs.locale();
dayjs.locale('en');
});

afterEach(() => {
dayjs.locale(originalLocale);
});

it.each([
['fr_FR', '25 août 2026', 'D MMM YYYY', '2026-08-25'],
['fr_FR', '25 septembre 2026', 'D MMMM YYYY', '2026-09-25'],
['fr_BE', '25 août 2026', 'D MMM YYYY', '2026-08-25'],
])('parses %s text %s without changing the global locale', (locale, text, format, expected) => {
const date = dayjsGenerateConfig.locale.parse(locale, text, [format]);

expect(date?.format('YYYY-MM-DD')).toBe(expected);
expect(date?.locale()).toBe('fr');
expect(dayjs.locale()).toBe('en');
});

it('tries alternate formats with the requested locale', () => {
const date = dayjsGenerateConfig.locale.parse('fr_FR', '25 août 2026', [
'YYYY-MM-DD',
'D MMM YYYY',
]);

expect(date?.format('YYYY-MM-DD')).toBe('2026-08-25');
expect(dayjs.locale()).toBe('en');
});

it('parses English text when the global locale is French', () => {
dayjs.locale('fr');
const date = dayjsGenerateConfig.locale.parse('en_US', '25 Aug 2026', ['D MMM YYYY']);

expect(date?.format('YYYY-MM-DD')).toBe('2026-08-25');
expect(date?.locale()).toBe('en');
expect(dayjs.locale()).toBe('fr');
});

it('keeps the global-locale fallback when locale data is not registered', () => {
const date = dayjsGenerateConfig.locale.parse('unregistered_LOCALE', '25 Aug 2026', [
'D MMM YYYY',
]);

expect(date?.format('YYYY-MM-DD')).toBe('2026-08-25');
expect(date?.locale()).toBe('en');
expect(dayjs.locale()).toBe('en');
});

it.each(['31 février 2026', '25 août 2026 extra'])(
'keeps strict validation for localized input %s',
(text) => {
expect(dayjsGenerateConfig.locale.parse('fr_FR', text, ['D MMMM YYYY'])).toBeNull();
expect(dayjs.locale()).toBe('en');
},
);
});

it('getFixedDate', () => {
const timea = dayjsGenerateConfig.getFixedDate('2019-2-08');
const timeb = dayjsGenerateConfig.getFixedDate('2019-02-08');
Expand Down
32 changes: 32 additions & 0 deletions tests/picker.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { act, createEvent, fireEvent, render } from '@testing-library/react';
import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
import 'dayjs/locale/fr';
import moment from 'moment';
import 'moment/locale/zh-cn';
import { KeyCode, resetWarned, spyElementPrototypes } from '@rc-component/util';
Expand All @@ -10,6 +11,7 @@ import Picker, { PickerPanel, type PickerRef } from '../src';
import type { PanelMode, PickerMode } from '../src/interface';
import momentGenerateConfig from '../src/generate/moment';
import enUS from '../src/locale/en_US';
import frFR from '../src/locale/fr_FR';
import zhCN from '../src/locale/zh_CN';
import {
// MomentPicker,
Expand Down Expand Up @@ -66,6 +68,36 @@ describe('Picker.Basic', () => {
fireEvent.click(document.querySelectorAll('ul')[colIndex].querySelectorAll('li')[rowIndex]);
}

it('commits edited localized month text while the global locale stays English', () => {
const originalLocale = dayjs.locale();
dayjs.locale('en');

try {
const onChange = jest.fn();
const { container } = render(
<DayPicker
locale={frFR}
format="D MMM YYYY"
defaultValue={getDay('2026-08-24')}
onChange={onChange}
/>,
);
const input = container.querySelector('input');
expect(input.value).toBe('24 août 2026');

fireEvent.change(input, { target: { value: '25 août 2026' } });
keyDown(KeyCode.ENTER);

expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange.mock.calls[0][0].format('YYYY-MM-DD')).toBe('2026-08-25');
expect(onChange.mock.calls[0][1]).toBe('25 août 2026');
expect(input).not.toHaveAttribute('aria-invalid', 'true');
expect(dayjs.locale()).toBe('en');
} finally {
dayjs.locale(originalLocale);
}
});

describe('mode', () => {
const modeList: { mode: PanelMode; className: string }[] = [
{
Expand Down
Loading