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
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ const NUMBER_SERIALIZATION_FORMAT = 'number';
const DATE_SERIALIZATION_FORMAT = 'yyyy/MM/dd';
const DATETIME_SERIALIZATION_FORMAT = 'yyyy/MM/dd HH:mm:ss';

const ISO_PARTIAL_DATE_PATTERN = /^\d{4,}(-\d{2})?$/;
const ISO8601_PATTERN = /^(\d{4,})(-)?(\d{2})(-)?(\d{2})(?:T(\d{2})(:)?(\d{2})?(:)?(\d{2}(?:\.(\d{1,3})\d*)?)?)?(Z|([+-])(\d{2})(:)?(\d{2})?)?$/;
const ISO8601_TIME_PATTERN = /^(\d{2}):(\d{2})(:(\d{2}))?$/;

const ISO8601_PATTERN_PARTS = ['', 'yyyy', '', 'MM', '', 'dd', 'THH', '', 'mm', '', 'ss', '.SSS'];
const DATE_SERIALIZATION_PATTERN = /^(\d{4})\/(\d{2})\/(\d{2})$/;

Expand All @@ -29,21 +29,43 @@ function getTimePart(part) {
return +part || 0;
}

function parseDate(text) {
const isDefaultSerializationFormat = getDateSerializationFormat(text) === DATE_SERIALIZATION_FORMAT;
function createLocalDateFromUTCTimestamp(timestamp: number): Date {
const utc = new Date(timestamp);

return new Date(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate());
}

function isISOPartialDateString(text: string): boolean {
return ISO_PARTIAL_DATE_PATTERN.test(text);
}

function parseDate(text): string | Date {
const isDefaultSerializationFormat = getDateSerializationFormat(text)
=== DATE_SERIALIZATION_FORMAT;

const parsedValue = !isDate(text) && Date.parse(text);

if (!parsedValue && isDefaultSerializationFormat) {
const parts = text.match(DATE_SERIALIZATION_PATTERN);

if (parts) {
const newDate = new Date(getTimePart(parts[1]), getTimePart(parts[2]), getTimePart(parts[3]));

newDate.setFullYear(getTimePart(parts[1]));
newDate.setMonth(getTimePart(parts[2]) - 1);
newDate.setDate(getTimePart(parts[3]));

return newDate;
}
}

return isNumber(parsedValue) ? new Date(parsedValue) : text;
if (!isNumber(parsedValue)) {
return text;
}

return isISOPartialDateString(text)
? createLocalDateFromUTCTimestamp(parsedValue)
: new Date(parsedValue);
}

function parseISO8601String(text) {
Expand Down Expand Up @@ -173,6 +195,7 @@ const getDateSerializationFormat = function (value) {
};

const dateSerialization = {
createLocalDateFromUTCTimestamp,
dateParser,
deserializeDate,
serializeDate,
Expand Down
15 changes: 12 additions & 3 deletions packages/devextreme/js/__internal/ui/date_box/date_box.base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -835,17 +835,26 @@ class DateBox extends DropDownEditor<DateBoxBaseProperties> {
getDateOption(optionName: 'value' | 'min' | 'max'): Date | null {
const { [optionName]: optionValue } = this.option();

return dateSerialization.deserializeDate(optionValue) as Date | null;
const deserializedDate: Date | null = dateSerialization.deserializeDate(optionValue);

return deserializedDate;
}

setDateOption(optionName: 'value' | 'min' | 'max', value: DateLike | undefined): void {
this.option(optionName, this._serializeDate(value));
const serializedDate = this._serializeDate(value);

this.option(optionName, serializedDate);
}

_serializeDate(date?: DateLike): Date | string | null {
const serializationFormat = this._getSerializationFormat();

return dateSerialization.serializeDate(date, serializationFormat) as Date | string | null;
const serializedDate: Date | string | null = dateSerialization.serializeDate(
date,
serializationFormat,
);

return serializedDate;
}

_clearValue(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ class CalendarStrategy extends DateBoxStrategy {
return;
}

this._widget.option('value', this.dateBoxValue());
const value = this.dateBoxValue();

this._widget.option({ value });
}

textChangedHandler(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,26 @@ QUnit.test('deserializing first date (serialization format is string)', function
assert.deepEqual(result, date, 'date is returned');
});

QUnit.test('deserializing year-only string should return correct year (T1323373)', function(assert) {
const result = dateSerialization.deserializeDate('2026');

assert.ok(result instanceof Date, 'result is a Date');
assert.equal(result.getFullYear(), 2026, 'year is 2026');
assert.equal(result.getMonth(), 0, 'month is January');
assert.equal(result.getDate(), 1, 'day is 1');
});

QUnit.test('createLocalDateFromUTCTimestamp returns local date with UTC components', function(assert) {
const utcTimestamp = Date.UTC(2026, 0, 1);
const result = dateSerialization.createLocalDateFromUTCTimestamp(utcTimestamp);

assert.ok(result instanceof Date, 'result is a Date');
assert.equal(result.getFullYear(), 2026, 'year matches UTC year');
assert.equal(result.getMonth(), 0, 'month matches UTC month');
assert.equal(result.getDate(), 1, 'date matches UTC date');
assert.equal(result.getHours(), 0, 'hours are 0');
});

QUnit.test('serialization ISO8601 dates', function(assert) {
const date = new Date(2015, 3, 5, 6, 7, 25, 125);

Expand Down
Loading