Skip to content

Commit eba11a5

Browse files
Change OData store default behavior and add field types for Date values
1 parent 6289100 commit eba11a5

File tree

8 files changed

+139
-26
lines changed

8 files changed

+139
-26
lines changed

packages/devextreme/js/__internal/data/odata/m_query_adapter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ const compileCriteria = (() => {
106106

107107
return formatter(
108108
serializePropName(fieldName),
109-
serializeValue(value, protocolVersion),
109+
serializeValue(value, protocolVersion, fieldTypes?.[fieldName]),
110110
);
111111
};
112112

@@ -277,7 +277,7 @@ const createODataQueryAdapter = (queryOptions) => {
277277
jsonp: queryOptions.jsonp,
278278
withCredentials: queryOptions.withCredentials,
279279
countOnly: _countQuery,
280-
deserializeDates: queryOptions.deserializeDates,
280+
processDatesAsUtc: queryOptions.processDatesAsUtc,
281281
fieldTypes: queryOptions.fieldTypes,
282282
isPaged: isFinite(_take),
283283
},

packages/devextreme/js/__internal/data/odata/m_request_dispatcher.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export default class RequestDispatcher {
1818
// @ts-expect-error
1919
this._withCredentials = options.withCredentials;
2020
// @ts-expect-error
21-
this._deserializeDates = options.deserializeDates;
21+
this._processDatesAsUtc = options.processDatesAsUtc ?? options.deserializeDates ?? false;
2222
// @ts-expect-error
2323
this._filterToLower = options.filterToLower;
2424
}
@@ -40,7 +40,7 @@ export default class RequestDispatcher {
4040
// @ts-expect-error
4141
withCredentials: this._withCredentials,
4242
// @ts-expect-error
43-
deserializeDates: this._deserializeDates,
43+
processDatesAsUtc: this._processDatesAsUtc,
4444
},
4545
);
4646
}

packages/devextreme/js/__internal/data/odata/m_store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ const ODataStore = Store.inherit({
106106
withCredentials: this._requestDispatcher._withCredentials,
107107
expand: loadOptions?.expand,
108108
requireTotalCount: loadOptions?.requireTotalCount,
109-
deserializeDates: this._requestDispatcher._deserializeDates,
109+
processDatesAsUtc: this._requestDispatcher._processDatesAsUtc,
110110
fieldTypes: this._fieldTypes,
111111
};
112112

packages/devextreme/js/__internal/data/odata/m_utils.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -210,15 +210,15 @@ const ajaxOptionsForRequest = (protocolVersion, request, options = {}) => {
210210

211211
export const sendRequest = (protocolVersion, request, options) => {
212212
const {
213-
deserializeDates, fieldTypes, countOnly, isPaged,
213+
processDatesAsUtc, fieldTypes, countOnly, isPaged,
214214
} = options;
215215
// @ts-expect-error
216216
const d = new Deferred();
217217
const ajaxOptions = ajaxOptionsForRequest(protocolVersion, request, options);
218218

219219
ajax.sendRequest(ajaxOptions).always((obj, textStatus) => {
220220
const transformOptions = {
221-
deserializeDates,
221+
processDatesAsUtc,
222222
fieldTypes,
223223
};
224224
const tuple = interpretJsonFormat(obj, textStatus, transformOptions, ajaxOptions);
@@ -387,14 +387,14 @@ const transformTypes = (obj, options = {}) => {
387387
transformTypes(obj[key], options);
388388
} else if (typeof value === 'string') {
389389
// @ts-expect-error
390-
const { fieldTypes, deserializeDates } = options;
390+
const { fieldTypes, processDatesAsUtc } = options;
391391
const canBeGuid = !fieldTypes || fieldTypes[key] !== 'String';
392392

393393
if (canBeGuid && GUID_REGEX.test(value)) {
394394
obj[key] = new Guid(value);
395395
}
396396

397-
if (deserializeDates !== false) {
397+
if (processDatesAsUtc !== false) {
398398
if (VERBOSE_DATE_REGEX.exec(value)) {
399399
// @ts-expect-error
400400
const date = new Date(Number(RegExp.$1) + RegExp.$2 * 60 * 1000);
@@ -415,20 +415,20 @@ export const serializePropName = (propName) => (propName instanceof EdmLiteral
415415
? propName.valueOf()
416416
: propName.replace(/\./g, '/'));
417417

418-
const serializeValueV4 = (value) => {
418+
const serializeValueV4 = (value, fieldType) => {
419419
if (value instanceof Date) {
420420
return formatISO8601(value, false, false);
421421
}
422422
if (value instanceof Guid) {
423423
return value.valueOf();
424424
}
425425
if (Array.isArray(value)) {
426-
return `[${value.map((item) => serializeValueV4(item)).join(',')}]`;
426+
return `[${value.map((item) => serializeValueV4(item, fieldType)).join(',')}]`;
427427
}
428-
return serializeValueV2(value);
428+
return serializeValueV2(value, fieldType);
429429
};
430430

431-
const serializeValueV2 = (value) => {
431+
const serializeValueV2 = (value, fieldType) => {
432432
if (value instanceof Date) {
433433
return serializeDate(value);
434434
}
@@ -438,19 +438,22 @@ const serializeValueV2 = (value) => {
438438
if (value instanceof EdmLiteral) {
439439
return value.valueOf();
440440
}
441+
if (fieldType && ['Date', 'DateTimeOffset'].includes(fieldType)) {
442+
return value;
443+
}
441444
if (typeof value === 'string') {
442445
return serializeString(value);
443446
}
444447
return String(value);
445448
};
446449

447-
export const serializeValue = (value, protocolVersion) => {
450+
export const serializeValue = (value, protocolVersion, fieldType?) => {
448451
switch (protocolVersion) {
449452
case 2:
450453
case 3:
451-
return serializeValueV2(value);
454+
return serializeValueV2(value, fieldType);
452455
case 4:
453-
return serializeValueV4(value);
456+
return serializeValueV4(value, fieldType);
454457
default: throw errors.Error('E4002');
455458
}
456459
};
@@ -480,6 +483,10 @@ export const keyConverters = {
480483
Single: (value) => (value instanceof EdmLiteral ? value : new EdmLiteral(`${value}f`)),
481484

482485
Decimal: (value) => (value instanceof EdmLiteral ? value : new EdmLiteral(`${value}m`)),
486+
487+
DateTimeOffset: (value) => value,
488+
489+
Date: (value) => value,
483490
};
484491

485492
export const convertPrimitiveValue = (type, value) => {

packages/devextreme/js/common/data.d.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1130,8 +1130,16 @@ export type ODataContextOptions = {
11301130
/**
11311131
* @docid
11321132
* @public
1133+
* @default false
1134+
* @deprecated ODataContextOptions.processDatesAsUtc
11331135
*/
11341136
deserializeDates?: boolean;
1137+
/**
1138+
* @docid ODataContextOptions.processDatesAsUtc
1139+
* @public
1140+
* @default false
1141+
*/
1142+
processDatesAsUtc?: boolean;
11351143
/**
11361144
* @docid
11371145
* @public
@@ -1232,8 +1240,16 @@ export type ODataStoreOptions<
12321240
/**
12331241
* @docid
12341242
* @public
1243+
* @default false
1244+
* @deprecated ODataStoreOptions.processDatesAsUtc
12351245
*/
12361246
deserializeDates?: boolean;
1247+
/**
1248+
* @docid ODataStoreOptions.processDatesAsUtc
1249+
* @public
1250+
* @default false
1251+
*/
1252+
processDatesAsUtc?: boolean;
12371253
/**
12381254
* @docid
12391255
* @type_function_param1 e:Error
@@ -1247,7 +1263,9 @@ export type ODataStoreOptions<
12471263
* @default {}
12481264
* @public
12491265
*/
1250-
fieldTypes?: any;
1266+
fieldTypes?: {
1267+
[fieldName: string]: 'String' | 'Int32' | 'Int64' | 'Guid' | 'Boolean' | 'Single' | 'Decimal' | 'Date' | 'DateTimeOffset';
1268+
};
12511269
/**
12521270
* @docid
12531271
* @public

packages/devextreme/testing/tests/DevExpress.data/odataQuery.tests.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -916,16 +916,27 @@ QUnit.test('Values are converted according to \'fieldTypes\' property', function
916916
fieldTypes: {
917917
id1: 'Int64',
918918
name: 'String',
919-
total: 'Decimal' // T566307
919+
total: 'Decimal', // T566307
920+
date: 'Date',
921+
dateTime: 'DateTimeOffset'
920922
}
921923
})
922-
.filter([['id1', '=', 123], 'and', ['id2', '=', 456], 'and', ['name', '=', 789], 'and', ['total', '=', null]])
924+
.filter([
925+
['id1', '=', 123], 'and',
926+
['id2', '=', 456], 'and',
927+
['name', '=', 789], 'and',
928+
['total', '=', null], 'and',
929+
['date', '=', '2025-11-11'], 'and',
930+
['dateTime', '=', '2025-11-11T11:11:11.000Z'], 'and',
931+
['dateText', '=', '2025-11-11'], 'and',
932+
['dateTimeText', '=', '2025-11-11T11:11:11.000Z'],
933+
])
923934
.enumerate()
924935
.fail(function() {
925936
assert.ok(false, MUST_NOT_REACH_MESSAGE);
926937
})
927938
.done(function(r) {
928-
assert.equal(r[0].data['$filter'], '(id1 eq 123L) and (id2 eq 456) and (name eq \'789\') and (total eq null)');
939+
assert.equal(r[0].data['$filter'], '(id1 eq 123L) and (id2 eq 456) and (name eq \'789\') and (total eq null) and (date eq 2025-11-11) and (dateTime eq 2025-11-11T11:11:11.000Z) and (dateText eq \'2025-11-11\') and (dateTimeText eq \'2025-11-11T11:11:11.000Z\')');
929940
})
930941
.always(done);
931942
});

packages/devextreme/testing/tests/DevExpress.data/odataStore.tests.js

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1718,7 +1718,7 @@ QUnit.test('Dates, on updating', function(assert) {
17181718
});
17191719

17201720
QUnit.module('Deserialization', moduleConfig);
1721-
QUnit.test('Dates, disableable, ODataStore', function(assert) {
1721+
QUnit.test('Dates, default behaviour (processDatesAsUtc = false), ODataStore', function(assert) {
17221722
assert.expect(2);
17231723

17241724
const done = assert.async();
@@ -1733,7 +1733,7 @@ QUnit.test('Dates, disableable, ODataStore', function(assert) {
17331733
responseText: { dateProperty: '1945-05-09T14:25:12.1234567Z' }
17341734
});
17351735

1736-
const store = new ODataStore({ version: 4, url: 'odata.org', deserializeDates: false });
1736+
const store = new ODataStore({ version: 4, url: 'odata.org' });
17371737
const promises = [
17381738
store.load()
17391739
.done(function(r) {
@@ -1751,7 +1751,7 @@ QUnit.test('Dates, disableable, ODataStore', function(assert) {
17511751
.always(done);
17521752
});
17531753

1754-
QUnit.test('Dates, disableable, ODataContext', function(assert) {
1754+
QUnit.test('Dates, default behaviour (processDatesAsUtc = false) and fieldType of dateProperty is undefined, ODataContext', function(assert) {
17551755
assert.expect(4);
17561756

17571757
const done = assert.async();
@@ -1774,10 +1774,9 @@ QUnit.test('Dates, disableable, ODataContext', function(assert) {
17741774
const ctx = new ODataContext({
17751775
version: 4,
17761776
url: 'odata.org',
1777-
deserializeDates: false,
17781777
entities: {
17791778
'X': { name: 'name' },
1780-
'Y': { name: 'name', deserializeDates: true }
1779+
'Y': { name: 'name', processDatesAsUtc: true }
17811780
}
17821781
});
17831782

@@ -1808,6 +1807,63 @@ QUnit.test('Dates, disableable, ODataContext', function(assert) {
18081807
.always(done);
18091808
});
18101809

1810+
QUnit.test('Dates, processDatesAsUtc = true, ODataContext', function(assert) {
1811+
assert.expect(4);
1812+
1813+
const done = assert.async();
1814+
1815+
ajaxMock.setup({
1816+
url: 'odata.org/name',
1817+
responseText: { value: [{ dateProperty: '1945-05-09T14:25:12.1234567Z' }] }
1818+
});
1819+
1820+
ajaxMock.setup({
1821+
url: 'odata.org/function()',
1822+
responseText: { dateProperty: '1945-05-09T14:25:12.1234567Z' }
1823+
});
1824+
1825+
ajaxMock.setup({
1826+
url: 'odata.org/action',
1827+
responseText: { dateProperty: '1945-05-09T14:25:12.1234567Z' }
1828+
});
1829+
1830+
const ctx = new ODataContext({
1831+
version: 4,
1832+
url: 'odata.org',
1833+
processDatesAsUtc: true,
1834+
entities: {
1835+
'X': { name: 'name' },
1836+
'Y': { name: 'name' }
1837+
}
1838+
});
1839+
1840+
const promises = [
1841+
ctx.get('function')
1842+
.done(function(r) {
1843+
assert.ok(isDate(r.dateProperty));
1844+
}),
1845+
1846+
ctx.invoke('action')
1847+
.done(function(r) {
1848+
assert.ok(isDate(r.dateProperty));
1849+
}),
1850+
1851+
ctx.X.load()
1852+
.done(function(r) {
1853+
assert.ok(isDate(r[0].dateProperty));
1854+
}),
1855+
1856+
ctx.Y.load()
1857+
.done(function(r) {
1858+
assert.ok(isDate(r[0].dateProperty));
1859+
})
1860+
];
1861+
1862+
$.when.apply($, promises)
1863+
.fail(function() { assert.ok(false, MUST_NOT_REACH_MESSAGE); })
1864+
.always(done);
1865+
});
1866+
18111867
QUnit.module('JSONP support', moduleConfig);
18121868
QUnit.test('load()', function(assert) {
18131869
const done = assert.async();

packages/devextreme/ts/dx.all.d.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3801,8 +3801,13 @@ declare module DevExpress.common.data {
38013801
}) => void;
38023802
/**
38033803
* [descr:ODataContextOptions.deserializeDates]
3804+
* @deprecated [depNote:ODataContextOptions.deserializeDates]
38043805
*/
38053806
deserializeDates?: boolean;
3807+
/**
3808+
* [descr:ODataContextOptions.processDatesAsUtc]
3809+
*/
3810+
processDatesAsUtc?: boolean;
38063811
/**
38073812
* [descr:ODataContextOptions.entities]
38083813
*/
@@ -3884,8 +3889,13 @@ declare module DevExpress.common.data {
38843889
}) => void;
38853890
/**
38863891
* [descr:ODataStoreOptions.deserializeDates]
3892+
* @deprecated [depNote:ODataStoreOptions.deserializeDates]
38873893
*/
38883894
deserializeDates?: boolean;
3895+
/**
3896+
* [descr:ODataStoreOptions.processDatesAsUtc]
3897+
*/
3898+
processDatesAsUtc?: boolean;
38893899
/**
38903900
* [descr:ODataStoreOptions.errorHandler]
38913901
*/
@@ -3897,7 +3907,18 @@ declare module DevExpress.common.data {
38973907
/**
38983908
* [descr:ODataStoreOptions.fieldTypes]
38993909
*/
3900-
fieldTypes?: any;
3910+
fieldTypes?: {
3911+
[fieldName: string]:
3912+
| 'String'
3913+
| 'Int32'
3914+
| 'Int64'
3915+
| 'Guid'
3916+
| 'Boolean'
3917+
| 'Single'
3918+
| 'Decimal'
3919+
| 'Date'
3920+
| 'DateTimeOffset';
3921+
};
39013922
/**
39023923
* [descr:ODataStoreOptions.filterToLower]
39033924
*/

0 commit comments

Comments
 (0)