При работе с массивами дат в JavaScript дубликаты возникают чаще, чем
в обычных структурах данных. Причина — различие в представлении одного и
того же момента времени: разные экземпляры Date могут
соответствовать одному и тому же значению, но не быть строго равными по
ссылке.
new Date('2024-01-01') !== new Date('2024-01-01') // true
В контексте обработки временных данных это приводит к необходимости нормализации и применения специальных алгоритмов сравнения.
Объекты Date нельзя корректно сравнивать через
=== или ==, так как сравнение идёт по ссылке.
Для работы с временными значениями используется сравнение по числовому
представлению времени:
date.getTime()
Однако в прикладных сценариях часто требуется не абсолютное
сравнение, а логическое: по дню, месяцу, неделе или произвольному
интервалу. Именно здесь используется функциональность
date-fns.
Перед устранением повторов массив дат приводится к единому виду. Наиболее распространённая стратегия — приведение к timestamp:
const timestamps = dates.map(d => d.getTime())
После этого становится возможным использование структур данных, основанных на уникальности значений.
Самый прямолинейный способ удаления дубликатов основан на
Set:
const uniqueDates = Array.from(
new Set(dates.map(date => date.getTime()))
).map(time => new Date(time))
Преимущество метода — высокая производительность. Недостаток — отсутствие гибкости при сравнении по частям даты (например, только день без времени).
Библиотека date-fns предоставляет функции для
семантического сравнения дат. Основной инструмент —
isEqual:
import { isEqual } from 'date-fns'
const unique = []
for (const date of dates) {
if (!unique.some(d => isEqual(d, date))) {
unique.push(date)
}
}
isEqual учитывает точное совпадение времени до
миллисекунд, что делает его подходящим для строгой дедупликации.
Часто дубликаты определяются не по точному времени, а по дню. В этом
случае используется isSameDay:
import { isSameDay } from 'date-fns'
const uniqueByDay = []
for (const date of dates) {
if (!uniqueByDay.some(d => isSameDay(d, date))) {
uniqueByDay.push(date)
}
}
Такой подход игнорирует часы, минуты и секунды, оставляя только календарную идентичность.
Аналогичные стратегии применяются для более крупных временных интервалов:
import { isSameWeek, isSameMonth } from 'date-fns'
const uniqueByWeek = []
for (const date of dates) {
if (!uniqueByWeek.some(d => isSameWeek(d, date))) {
uniqueByWeek.push(date)
}
}
const uniqueByMonth = []
for (const date of dates) {
if (!uniqueByMonth.some(d => isSameMonth(d, date))) {
uniqueByMonth.push(date)
}
}
Альтернативный подход — предварительное приведение дат к началу дня:
import { startOfDay } from 'date-fns'
const unique = Array.from(
new Map(
dates.map(date => [startOfDay(date).getTime(), date])
).values()
)
Здесь ключом становится нормализованное значение времени, что позволяет эффективно устранять дубликаты без вложенных циклов.
При работе с большими массивами сортировка упрощает алгоритм дедупликации:
import { compareAsc } from 'date-fns'
const sorted = [...dates].sort(compareAsc)
После сортировки можно удалять дубликаты за один проход:
const result = []
for (let i = 0; i < sorted.length; i++) {
if (i === 0 || sorted[i].getTime() !== sorted[i - 1].getTime()) {
result.push(sorted[i])
}
}
Сложность такого подхода — O(n log n) из-за сортировки, но последующая обработка линейна.
Дедупликация может быть выражена через reduce, что
повышает читаемость в функциональных цепочках:
import { isEqual } from 'date-fns'
const unique = dates.reduce((acc, date) => {
if (!acc.some(d => isEqual(d, date))) {
acc.push(date)
}
return acc
}, [])
Этот подход сохраняет гибкость, позволяя легко менять критерий сравнения.
Map используется как структура индексации уникальных значений:
import { startOfDay } from 'date-fns'
const map = new Map()
for (const date of dates) {
const key = startOfDay(date).getTime()
map.set(key, date)
}
const unique = Array.from(map.values())
При таком подходе последнее значение с одинаковым ключом перезаписывает предыдущее, что иногда используется как полезное поведение при обработке событий.
Дубликаты могут возникать из-за различий в временных зонах:
new Date('2024-01-01T00:00:00Z')
new Date('2023-12-31T23:00:00-01:00')
Обе даты представляют один момент времени, но визуально различаются.
В таких случаях используется строгое сравнение через
getTime() или isEqual, исключающее влияние
локального представления времени.
В реальных сценариях часто применяется комбинация подходов:
compareAsc)startOfDay,
startOfMonth)isEqual, isSameDay)Map, Set)Пример гибридного подхода:
import { compareAsc, startOfDay, isEqual } from 'date-fns'
const sorted = [...dates].sort(compareAsc)
const unique = []
for (const date of sorted) {
const normalized = startOfDay(date)
if (!unique.some(d => isEqual(startOfDay(d), normalized))) {
unique.push(date)
}
}
Логика удаления дубликатов определяется уровнем абстракции временных данных:
date-fns предоставляет функции для каждого из этих
уровней, позволяя строить единый алгоритм обработки без ручного разбора
дат.