Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
6716ca7
Global format config. Add tests
GoodDayForSurf Apr 2, 2026
a434b9d
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 2, 2026
3a8179f
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 2, 2026
722f5ca
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 2, 2026
0ec8e32
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 2, 2026
d1e97af
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 2, 2026
a862455
Merge remote-tracking branch 'my/26_1_global_format' into 26_1_global…
GoodDayForSurf Apr 2, 2026
2922132
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 2, 2026
57dad57
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 2, 2026
026930e
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 2, 2026
bd86b8a
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 3, 2026
bd3bcbd
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 3, 2026
77d63d2
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 3, 2026
dbd3064
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 3, 2026
73904b5
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 3, 2026
a58d9ee
Global format config. Implementation for dateFormat, dateTimeFormat, …
GoodDayForSurf Apr 3, 2026
9c64a68
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 3, 2026
9e3c6fc
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 3, 2026
f0033b2
Merge branch '26_1' of https://github.com/DevExpress/DevExtreme into …
GoodDayForSurf Apr 15, 2026
b5bcacb
add dateTimePreset, tests and more minor changes
ajivanyandev Apr 16, 2026
4666d89
Merge branch '26_1' of https://github.com/DevExpress/DevExtreme into …
GoodDayForSurf Apr 17, 2026
7c9709d
regenerate dx.all.d.ts
GoodDayForSurf Apr 17, 2026
29baafe
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 17, 2026
ddeee1e
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 17, 2026
42c8bb1
date/time formatting priority fixes
ajivanyandev Apr 20, 2026
b9c194b
fix type error
ajivanyandev Apr 20, 2026
fa0259c
small fix and add tests
ajivanyandev Apr 20, 2026
42996e4
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 20, 2026
e4c55c9
Revert "small fix and add tests"
GoodDayForSurf Apr 20, 2026
df75349
add tests
GoodDayForSurf Apr 20, 2026
058a720
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 20, 2026
b7bd8be
fix tests
GoodDayForSurf Apr 20, 2026
a4ba13c
Merge remote-tracking branch 'my/26_1_global_format' into 26_1_global…
GoodDayForSurf Apr 20, 2026
5e9cff6
fix tests
GoodDayForSurf Apr 20, 2026
09dc46d
fix tests
GoodDayForSurf Apr 20, 2026
880cce8
fix tests
GoodDayForSurf Apr 20, 2026
10d9de1
fix tests
GoodDayForSurf Apr 21, 2026
8f15112
fix tests
GoodDayForSurf Apr 21, 2026
2fc817e
fix tests
GoodDayForSurf Apr 21, 2026
5536804
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 21, 2026
ca5abee
fix using global format in chart axis
GoodDayForSurf Apr 23, 2026
84325e9
fix using global format in header fiter in dataGrid
GoodDayForSurf Apr 23, 2026
c9b613c
fix using global format in header filter in dataGrid for dates
GoodDayForSurf Apr 23, 2026
c2bd67f
fix test for formatting axis
GoodDayForSurf Apr 24, 2026
94c8029
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 24, 2026
952cee5
fix formatting in header filter of dataGrid
GoodDayForSurf Apr 24, 2026
d15de27
fix formatting in header filter of dataGrid
GoodDayForSurf Apr 24, 2026
8c25b5a
Merge remote-tracking branch 'my/26_1_global_format' into 26_1_global…
GoodDayForSurf Apr 24, 2026
2d5c137
remove unuseful test
GoodDayForSurf Apr 24, 2026
639af0c
no use global format in numberBox in DateBox
GoodDayForSurf Apr 24, 2026
40170b9
use global format in timeline of scheduler
GoodDayForSurf Apr 24, 2026
82d5346
fix numberbox format in timeview
GoodDayForSurf Apr 24, 2026
520183b
Merge branch '26_1' of https://github.com/DevExpress/DevExtreme into …
GoodDayForSurf Apr 24, 2026
7d9eeee
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 24, 2026
82107be
Merge branch '26_1' of https://github.com/DevExpress/DevExtreme into …
GoodDayForSurf Apr 27, 2026
00bee08
add doc tags for added fields in global config
GoodDayForSurf Apr 27, 2026
b0d6f7b
regenerate
GoodDayForSurf Apr 27, 2026
66e761c
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 27, 2026
b0cbce4
fix test
GoodDayForSurf Apr 27, 2026
519b4d5
Merge branch '26_1_global_format' of https://github.com/GoodDayForSur…
GoodDayForSurf Apr 27, 2026
7676549
fix config in pivotGrid
GoodDayForSurf Apr 27, 2026
8f690bd
fix scheduler test
GoodDayForSurf Apr 27, 2026
aad5013
fix using global format in dataGrid/cardView
GoodDayForSurf Apr 27, 2026
56c9811
fix scheduler test
GoodDayForSurf Apr 27, 2026
b65f8db
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 27, 2026
64e265a
fix using global format in datagrid/cardview
GoodDayForSurf Apr 27, 2026
db95b26
Merge remote-tracking branch 'my/26_1_global_format' into 26_1_global…
GoodDayForSurf Apr 27, 2026
f338b85
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 27, 2026
0d3da67
refactor
GoodDayForSurf Apr 28, 2026
56681c1
Merge remote-tracking branch 'my/26_1_global_format' into 26_1_global…
GoodDayForSurf Apr 28, 2026
af3d2b4
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 28, 2026
0dfb828
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 28, 2026
0534b02
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 28, 2026
2d61ec5
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 29, 2026
265d7c9
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 29, 2026
1053e85
fix test
GoodDayForSurf Apr 30, 2026
5c5771c
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 30, 2026
11aa7e1
fix TS errors in test
GoodDayForSurf Apr 30, 2026
c8d2f52
Merge remote-tracking branch 'my/26_1_global_format' into 26_1_global…
GoodDayForSurf Apr 30, 2026
ea2c607
Merge branch '26_1' into 26_1_global_format
GoodDayForSurf Apr 30, 2026
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import {
afterEach, beforeEach, describe, expect, it,
} from '@jest/globals';
import dateLocalization from '@js/common/core/localization/date';
import config from '@js/core/config';

const GLOBAL_FORMAT_KEYS = ['dateFormat', 'timeFormat', 'dateTimeFormat', 'numberFormat', 'dateTimeFormatPresets'] as const;
type GlobalFormatKey = typeof GLOBAL_FORMAT_KEYS[number];

const saveAndRestore = (): { save: () => void; restore: () => void } => {
let savedValues: Partial<Record<GlobalFormatKey, unknown>> = {};

return {
save() {
const currentConfig = config();

savedValues = {};
GLOBAL_FORMAT_KEYS.forEach((key) => {
savedValues[key] = currentConfig[key];
});
},
restore() {
const currentConfig = config();

GLOBAL_FORMAT_KEYS.forEach((key) => {
if (savedValues[key] === undefined) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete currentConfig[key];
} else {
currentConfig[key] = savedValues[key] as never;
}
});
},
};
};

describe('date localization - dateTimeFormatPresets', () => {
const { save, restore } = saveAndRestore();

beforeEach(() => { save(); });
afterEach(() => { restore(); });

describe('string preset override', () => {
it('should override shortDate with custom LDML pattern', () => {
config({
...config(),
dateTimeFormatPresets: {
shortDate: 'dd/MM/yyyy',
},
});

const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');

expect(result).toBe('02/01/2020');
});

it('should override shortTime with custom LDML pattern', () => {
config({
...config(),
dateTimeFormatPresets: {
shortTime: 'HH:mm:ss',
},
});

const result = dateLocalization.format(new Date(2020, 0, 2, 14, 5, 30), 'shortTime');

expect(result).toBe('14:05:30');
});

it('should override longDate with custom LDML pattern', () => {
config({
...config(),
dateTimeFormatPresets: {
longDate: 'dd MMMM yyyy',
},
});

const result = dateLocalization.format(new Date(2020, 0, 2), 'longDate');

expect(result).toBe('02 January 2020');
});

it('should override shortDateShortTime with custom LDML pattern', () => {
config({
...config(),
dateTimeFormatPresets: {
shortDateShortTime: 'dd/MM/yyyy HH:mm',
},
});

const result = dateLocalization.format(new Date(2020, 0, 2, 14, 5), 'shortDateShortTime');

expect(result).toBe('02/01/2020 14:05');
});
});

describe('function preset override', () => {
it('should use function override for shortDate', () => {
config({
...config(),
dateTimeFormatPresets: {
shortDate: (d: Date) => `${d.getDate()}-${d.getMonth() + 1}-${d.getFullYear()}`,
},
});

const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');

expect(result).toBe('2-1-2020');
});

it('should use function override for shortTime', () => {
config({
...config(),
dateTimeFormatPresets: {
shortTime: (d: Date) => `${d.getHours()}h${String(d.getMinutes()).padStart(2, '0')}`,
},
});

const result = dateLocalization.format(new Date(2020, 0, 2, 14, 5), 'shortTime');

expect(result).toBe('14h05');
});
});

describe('case insensitivity', () => {
it('should apply override regardless of case in format name', () => {
config({
...config(),
dateTimeFormatPresets: {
shortDate: 'dd/MM/yyyy',
},
});

const date = new Date(2020, 0, 2);

expect(dateLocalization.format(date, 'shortdate')).toBe('02/01/2020');
expect(dateLocalization.format(date, 'SHORTDATE')).toBe('02/01/2020');
expect(dateLocalization.format(date, 'ShortDate')).toBe('02/01/2020');
});
});

describe('locale map in preset', () => {
it('should resolve preset with default locale', () => {
config({
...config(),
dateTimeFormatPresets: {
shortDate: {
default: 'dd/MM/yyyy',
'de-DE': 'dd.MM.yyyy',
},
},
});

const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');

expect(result).toBe('02/01/2020');
});
});

describe('no override', () => {
it('should use built-in format when no preset override is configured', () => {
const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');

// Built-in Intl format for en locale
expect(result).toBeTruthy();
expect(typeof result).toBe('string');
});

it('should leave non-preset string formats unaffected', () => {
config({
...config(),
dateTimeFormatPresets: {
shortDate: 'dd/MM/yyyy',
},
});

const result = dateLocalization.format(new Date(2020, 0, 2), 'yyyy-MM-dd');

// LDML pattern should be used directly, not affected by preset overrides
expect(result).toBe('2020-01-02');
});

it('should leave FormatObject formats unaffected', () => {
config({
...config(),
dateTimeFormatPresets: {
shortDate: 'dd/MM/yyyy',
},
});

const customFormatter = (value: number | Date): string => {
const d = value instanceof Date ? value : new Date(value);
return `custom:${d.getFullYear()}`;
};
const result = dateLocalization.format(new Date(2020, 0, 2), { formatter: customFormatter });

expect(result).toBe('custom:2020');
});

it('should not affect formatting when dateTimeFormatPresets is empty', () => {
config({
...config(),
dateTimeFormatPresets: {},
});

const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');

expect(result).toBeTruthy();
expect(typeof result).toBe('string');
});
});

describe('unknown preset key', () => {
it('should safely ignore unknown preset keys', () => {
config({
...config(),
dateTimeFormatPresets: {
unknownFormat: 'dd/MM/yyyy',
},
});

// Known presets should still work normally
const result = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');

expect(result).toBeTruthy();
expect(typeof result).toBe('string');
});
});

describe('preset override aliases another preset', () => {
it('should support aliasing one preset to another', () => {
config({
...config(),
dateTimeFormatPresets: {
shortDate: 'longDate',
},
});

const dateLong = dateLocalization.format(new Date(2020, 0, 2), 'longDate');
const dateShort = dateLocalization.format(new Date(2020, 0, 2), 'shortDate');

// shortDate should now format like longDate
expect(dateShort).toBe(dateLong);
});
});
});

describe('date localization - global *Format precedence', () => {
const { save, restore } = saveAndRestore();

beforeEach(() => { save(); });
afterEach(() => { restore(); });

it('should apply dateFormat for direct calls with the resolved format', () => {
config({
...config(),
dateFormat: 'dd/MM/yyyy',
});

const result = dateLocalization.format(new Date(2020, 0, 2), config().dateFormat);

expect(result).toBe('02/01/2020');
});

it('should apply dateTimeFormat for direct calls with the resolved format', () => {
config({
...config(),
dateTimeFormat: 'dd/MM/yyyy, HH:mm',
});

const result = dateLocalization.format(new Date(2020, 0, 2, 14, 5), config().dateTimeFormat);

expect(result).toBe('02/01/2020, 14:05');
});
});
33 changes: 33 additions & 0 deletions packages/devextreme/js/__internal/core/localization/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getFormatter as getLDMLDateFormatter } from '@ts/core/localization/ldml
import { getParser as getLDMLDateParser } from '@ts/core/localization/ldml/date.parser';
import numberLocalization from '@ts/core/localization/number';
import errors from '@ts/core/m_errors';
import { resolvePresetOverride } from '@ts/core/m_global_format_config';
import { injector as dependencyInjector } from '@ts/core/utils/m_dependency_injector';
import { each } from '@ts/core/utils/m_iterator';
import { isString } from '@ts/core/utils/m_type';
Expand Down Expand Up @@ -67,6 +68,31 @@ const dateLocalization = dependencyInjector({
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return this._getPatternByFormat(pattern) || pattern;
},
_resolveStringFormat(
format: string,
date: Date,
): string | undefined {
const presetOverride = resolvePresetOverride(format);

if (presetOverride === undefined) {
return undefined;
}
if (typeof presetOverride === 'function') {
return (presetOverride as DateFormatter)(date);
}
if (isString(presetOverride)) {
const pattern = FORMATS_TO_PATTERN_MAP[
(presetOverride as string).toLowerCase()
] || presetOverride as string;

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return numberLocalization.convertDigits(
getLDMLDateFormatter(pattern, this)(date),
);
}

return undefined;
},
formatUsesMonthName(format: string): boolean {
return this._expandPattern(format).indexOf('MMMM') !== -1;
},
Expand Down Expand Up @@ -139,6 +165,13 @@ const dateLocalization = dependencyInjector({
// eslint-disable-next-line no-param-reassign
format = (format as FormatObject).type ?? format;
if (isString(format)) {
const resolvedFormat = this._resolveStringFormat(format as string, date);

if (resolvedFormat !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return resolvedFormat;
}

// eslint-disable-next-line no-param-reassign
format = (FORMATS_TO_PATTERN_MAP[(format as string).toLowerCase()] || format) as string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'globalize/date';
import type { Format as LocalizationFormat, FormatObject } from '@js/localization';
import type { DateFormatter, DateParser, Format } from '@ts/core/localization/date';
import dateLocalization from '@ts/core/localization/date';
import { resolvePresetOverride } from '@ts/core/m_global_format_config';
import * as iteratorUtils from '@ts/core/utils/m_iterator';
import { isObject } from '@ts/core/utils/m_type';
// eslint-disable-next-line import/no-extraneous-dependencies
Expand Down Expand Up @@ -186,6 +187,23 @@ if (Globalize?.formatDate) {
format = (format as FormatObject).type ?? format;

if (typeof format === 'string') {
const presetOverride = resolvePresetOverride(format);

if (presetOverride !== undefined) {
if (typeof presetOverride === 'function') {
return (presetOverride as DateFormatter)(date);
}
if (typeof presetOverride === 'string') {
// eslint-disable-next-line no-param-reassign
format = presetOverride;
} else if (isObject(presetOverride) && this._isAcceptableFormat(presetOverride)) {
formatter = Globalize.dateFormatter(presetOverride);

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return this.removeRtlMarks(formatter(date));
}
}

formatCacheKey = `${Globalize.locale().locale}:${format}`;
formatter = formattersCache[formatCacheKey];
if (!formatter) {
Expand Down
14 changes: 14 additions & 0 deletions packages/devextreme/js/__internal/core/localization/intl/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { Format as LocalizationFormat, FormatObject } from '@js/localization';
import localizationCoreUtils from '@ts/core/localization/core';
import type { DateFormatter, Format } from '@ts/core/localization/date';
import { resolvePresetOverride } from '@ts/core/m_global_format_config';
import { extend } from '@ts/core/utils/m_extend';

interface DateArgs {
Expand Down Expand Up @@ -237,6 +238,19 @@ export default {
// eslint-disable-next-line no-param-reassign
format = (format as FormatObject).type ?? format;
}

if (typeof format === 'string') {
const presetOverride = resolvePresetOverride(format);

if (presetOverride !== undefined) {
if (typeof presetOverride === 'function') {
return (presetOverride as DateFormatter)(date);
}
// eslint-disable-next-line no-param-reassign
format = presetOverride as LocalizationFormat;
}
}

const intlFormat = getIntlFormat(format);

if (intlFormat) {
Expand Down
Loading
Loading