Метод Array.prototype.sort() по умолчанию работает как
строковая сортировка, что приводит к некорректным результатам при
обработке дат, если отсутствует явный компаратор. Даже при работе с
объектами Date поведение может быть неоднозначным,
поскольку происходит неявное приведение типов.
Типичный пример проблемного поведения:
const dates = [
new Date('2024-12-01'),
new Date('2023-01-10'),
new Date('2025-05-20')
];
dates.sort();
Результат зависит от преобразования объектов в строки, а не от их числового значения времени.
Корректная сортировка требует явного компаратора, основанного на временных значениях.
В JavaScript объект Date представляет момент времени как
количество миллисекунд, прошедших с 1 января 1970 года (UTC). Это
значение доступно через:
date.getTime()
Именно числовое представление используется для корректного сравнения дат.
Наиболее прямолинейный способ сортировки:
const sorted = dates.sort((a, b) => a.getTime() - b.getTime());
Для обратного порядка:
const sortedDesc = dates.sort((a, b) => b.getTime() - a.getTime());
Этот подход является базовым, но при работе с большим количеством дат или разными форматами становится неудобным.
Библиотека Date-fns предоставляет специализированные функции для сравнения дат, упрощающие создание компараторов.
Функция compareAsc выполняет сравнение двух дат в
порядке возрастания:
import { compareAsc } from 'date-fns';
const result = compareAsc(
new Date('2024-01-01'),
new Date('2023-01-01')
);
Возвращаемые значения:
-1 — первая дата меньше второй1 — первая дата больше второй0 — даты равныИспользование в сортировке:
import { compareAsc } from 'date-fns';
const sorted = dates.sort(compareAsc);
Функция compareDesc реализует обратный порядок:
import { compareDesc } from 'date-fns';
const sorted = dates.sort(compareDesc);
Эквивалент ручного компаратора:
(b, a) => a.getTime() - b.getTime()
В реальных данных даты часто представлены строками ISO 8601:
const dates = [
'2024-01-10',
'2023-05-20',
'2025-02-15'
];
Date-fns поддерживает корректную обработку через
parseISO:
import { compareAsc, parseISO } from 'date-fns';
const sorted = dates.sort((a, b) =>
compareAsc(parseISO(a), parseISO(b))
);
В массивах могут встречаться строки, Date и числовые
timestamp-значения. Для унификации используется нормализация:
import { compareAsc, parseISO, isDate } from 'date-fns';
function normalizeDate(value) {
if (isDate(value)) return value;
if (typeof value === 'string') return parseISO(value);
return new Date(value);
}
const sorted = mixedDates.sort((a, b) =>
compareAsc(normalizeDate(a), normalizeDate(b))
);
Некорректные даты приводят к Invalid Date, что ломает
сравнение. Проверка осуществляется через isValid:
import { compareAsc, isValid } from 'date-fns';
function safeCompare(a, b) {
const aValid = isValid(a);
const bValid = isValid(b);
if (!aValid && !bValid) return 0;
if (!aValid) return 1;
if (!bValid) return -1;
return compareAsc(a, b);
}
Такой подход позволяет перемещать некорректные значения в конец массива.
Для оптимизации часто используется хранение числовых значений времени:
const timestamps = [
1704067200000,
1672444800000,
1735689600000
];
timestamps.sort((a, b) => a - b);
При необходимости обратного преобразования:
const dates = timestamps.map(t => new Date(t));
Сортировка имеет сложность O(n log n), но основная нагрузка в контексте дат связана с:
DategetTime() или parseISOОптимизация достигается предварительным преобразованием:
const prepared = dates
.map(d => ({
original: d,
time: new Date(d).getTime()
}))
.sort((a, b) => a.time - b.time)
.map(x => x.original);
Современные движки JavaScript реализуют стабильную сортировку, но исторически это не гарантировалось. При работе с датами стабильность важна, если присутствуют одинаковые значения времени и требуется сохранить исходный порядок элементов.
Добавление вторичного ключа:
const sorted = items
.map((item, index) => ({ item, index }))
.sort((a, b) => {
const diff = compareAsc(a.item.date, b.item.date);
return diff !== 0 ? diff : a.index - b.index;
})
.map(x => x.item);
Date-fns оперирует объектами Date, которые всегда хранят
UTC-основанное время. Однако входные строки могут содержать
смещения:
const dates = [
'2024-01-01T10:00:00+03:00',
'2024-01-01T08:00:00Z'
];
После парсинга:
import { compareAsc, parseISO } from 'date-fns';
dates.sort((a, b) =>
compareAsc(parseISO(a), parseISO(b))
);
Сравнение происходит уже в единой временной шкале.
Сортировка часто используется как предварительный этап перед группировкой:
import { compareAsc, parseISO } from 'date-fns';
const sorted = data.sort((a, b) =>
compareAsc(parseISO(a.date), parseISO(b.date))
);
const grouped = sorted.reduce((acc, item) => {
const key = item.date;
if (!acc[key]) acc[key] = [];
acc[key].push(item);
return acc;
}, {});
В высоконагруженных системах применяется стратегия хранения времени в виде числа:
const events = [
{ id: 1, time: 1700000000000 },
{ id: 2, time: 1600000000000 }
];
events.sort((a, b) => a.time - b.time);
Такой подход исключает необходимость повторного парсинга.
При сортировке сложных структур данных применяется многоуровневое сравнение:
import { compareAsc } from 'date-fns';
function compareEvents(a, b) {
const byDate = compareAsc(a.date, b.date);
if (byDate !== 0) return byDate;
return a.priority - b.priority;
}
sort, ухудшающий
производительностьInvalid DateПрактическая модель сортировки данных с датами в Date-fns сводится к трём этапам:
parseISO,
new Date)compareAsc или
compareDescimport { compareDesc, parseISO } from 'date-fns';
const sorted = data.sort((a, b) =>
compareDesc(parseISO(a.date), parseISO(b.date))
);