DateFormatter — унифицированная система форматирования дат и времени. Она обеспечивает единообразное отображение во всех частях приложения и соответствует корпоративным стандартам.

DateFormatter автоматически отслеживает изменения локали через KbqLocaleService и обновляет форматы при смене языка интерфейса.

Методы DateFormatter позволяют форматировать дату и время непосредственно в TypeScript-коде:

const formattedStringOfDate = this.formatter.absoluteLongDate(this.adapter.today());

Для форматирования в HTML-шаблонах предназначены pipe, названия которых соответствуют методам DateFormatter:

<div>{{ adapter.today() | kbqAbsoluteLongDate }}</div>

Чтобы pipe работали, нужно импортировать KbqFormattersModule — он предоставляет DateFormatter и экспортирует все pipe. Примеры использования

Один и тот же формат доступен в трёх вариантах:

СемействоПримерПоведение
kbq* — рекомендуетсяkbqAbsoluteLongDateПересчитывается при смене локали через KbqLocaleService, результат кэшируется по значению, аргументам и локали
Без префиксаabsoluteLongDatePure pipe. Пересчитывается только при изменении входного значения — при смене локали строка не обновится
С суффиксом ImpurePipeabsoluteLongDateImpurePipeImpure pipe без кэша: форматирует заново на каждом цикле проверки изменений

Беспрефиксные и ImpurePipe-варианты устарели и будут удалены в 22.0.0: каждому соответствует пайп kbq* того же формата с теми же аргументами.

PipeВходное значениеАргументыМетод DateFormatter
kbqAbsoluteShortDateдатаcurrYear?: booleanabsoluteShortDate
kbqAbsoluteLongDateдатаcurrYear?: booleanabsoluteLongDate
kbqAbsoluteShortDateTimeдатаoptions?: DateTimeOptionsabsoluteShortDateTime
kbqAbsoluteLongDateTimeдатаoptions?: DateTimeOptionsabsoluteLongDateTime
kbqRelativeShortDateдата—relativeShortDate
kbqRelativeLongDateдата—relativeLongDate
kbqRelativeShortDateTimeдатаoptions?: DateTimeOptionsrelativeShortDateTime
kbqRelativeLongDateTimeдатаoptions?: DateTimeOptionsrelativeLongDateTime
kbqRangeShortDate[от, до]—rangeShortDate
kbqRangeLongDate[от, до]—rangeLongDate
kbqRangeShortDateTime[от, до]options?: DateTimeOptionsrangeShortDateTime
kbqRangeMiddleDateTime[от, до]options?: DateTimeOptionsrangeMiddleDateTime
kbqRangeLongDateTime[от, до]options?: DateTimeOptionsrangeLongDateTime
kbqDurationShortest[от, до]options?: DateTimeOptionsdurationShortest
kbqDurationShort[от, до]units?: DurationUnit[], fraction?: booleandurationShort
kbqDurationLong[от, до]units?: DurationUnit[], fraction?: booleandurationLong

DateTimeOptions — это { seconds?: boolean; milliseconds?: boolean; currYear?: boolean }. kbqDurationShortest использует из него только seconds (по умолчанию true) и milliseconds.

<div>{{ [task.startedAt, task.finishedAt] | kbqDurationShortest }}</div>
<div>{{ [task.startedAt, task.finishedAt] | kbqDurationLong: ['hours', 'minutes'] }}</div>

Передайте null вместо одной из границ — pipe диапазона сам переключится на формат открытого диапазона («С 15 января», «По 20 июня»). Отдельного pipe для этого не требуется.

<div>{{ [filter.from, filter.to] | kbqRangeLongDate }}</div>

Исключение — kbqRangeMiddleDateTime: у среднего формата нет шаблона открытого диапазона, поэтому он требует обе границы.

Если дату не удалось разобрать или она отсутствует, pipe выводит пустую строку. Это отражено в типах: ещё не заполненная привязка — это null или undefined, а не дата, и для pipe диапазонов и продолжительности это относится как к самому кортежу [от, до], так и к отдельной границе в нём.

Pipe диапазонов, поддерживающие открытый диапазон, — kbqRangeShortDate, kbqRangeLongDate, kbqRangeShortDateTime, kbqRangeLongDateTime — выводят пустую строку, только если обе границы отсутствуют или некорректны; если задана хотя бы одна граница, они переключаются на формат открытого диапазона. kbqRangeMiddleDateTime и pipe продолжительности требуют обе границы, поэтому выводят пустую строку, если отсутствует или некорректна хотя бы одна из них; pipe продолжительности, кроме того, выводят пустую строку, если начало позже конца.

Если нужно узнать об ошибке, а не скрыть её, вызывайте методы DateFormatter напрямую — они бросают исключение.

Форматы, которых нет среди pipe, доступны через DateFormatter: у него есть публичное поле config с шаблонами текущей локали.

private readonly formatter = inject<DateFormatter<DateTime>>(DateFormatter);
format(from: DateTime, to: DateTime): string {
return this.formatter.rangeDate(from, to, this.formatter.config.rangeTemplates.closedRange.middle);
}

Так же работают absoluteDate, relativeDate, rangeDateTime, duration и openedRangeDate — они принимают шаблон аргументом.

По умолчанию даты показываются в часовом поясе среды, где выполняется код: в браузере — в поясе пользователя, при серверном рендеринге — в поясе сервера. Токен KBQ_DATE_TIMEZONE задаёт пояс один раз для всего приложения — в нём начинают работать pipe, DateFormatter, календарь и поля ввода дат, поэтому передавать смещение в каждый pipe не нужно.

bootstrapApplication(App, {
providers: [kbqDateTimezoneProvider('Europe/Moscow')]
});

Токен принимает:

ЗначениеПримерКомментарий
Имя пояса IANA'Europe/Moscow'Учитывает переходы на летнее время
Смещение в минутах от UTC180, -330Фиксированное, без перехода на летнее время
Смещение строкой'+03:00', 'UTC+3', 'GMT+05:30'То же, в текстовом виде
'utc'
'system'Значение по умолчанию — пояс среды

Неизвестное имя пояса не ломает отрисовку: даты остаются в поясе среды, а в режиме разработки выводится предупреждение. То же касается смещения за пределами ±14:00 — самого широкого из существовавших; дробное смещение округляется до целых минут.

KbqDateTimezoneService.setTimezone() меняет пояс без перезагрузки страницы, kbq* pipe перерисовываются:

private readonly timezoneService = inject(KbqDateTimezoneService);
setUserTimezone(timezone: string) {
this.timezoneService.setTimezone(timezone);
}

Pure pipe (без префикса kbq) на смену пояса не реагируют — ровно так же, как и на смену локали.

Пояс приходит к pipe через адаптер дат и DateFormatter, а те берут KbqDateTimezoneService из инжектора, в котором были созданы. kbqDateTimezoneProvider объявляет этот сервис вместе с токеном, поэтому для поддерева его достаточно перечислить рядом с адаптером и форматтером:

@Component({
providers: [
kbqDateTimezoneProvider('Asia/Tokyo'),
{ provide: DateAdapter, useClass: LuxonDateAdapter },
DateFormatter
]
})

При SSR страница отрисовывается в поясе сервера, а после гидратации — в поясе браузера. Если значения различаются, все даты на странице пересчитываются, и это видно как мигание. Чтобы этого не было, сервер и клиент должны получить одно и то же значение KBQ_DATE_TIMEZONE.

Надёжнее всего передать пояс через TransferState: сервер берёт его из куки (или из профиля пользователя) и кладёт в состояние, а клиент читает готовое значение — тогда оно совпадает даже в том случае, когда куки нет.

// конфигурация сервера
export const serverConfig = mergeApplicationConfig(appConfig, {
providers: [
provideServerRendering(),
{
provide: KBQ_DATE_TIMEZONE,
useFactory: () => {
const timezone = readTimezoneCookie(inject(REQUEST, { optional: true })) ?? 'utc';
inject(TransferState).set(TIMEZONE_KEY, timezone);
return timezone;
}
}
]
});
// конфигурация браузера — берём ровно то, чем рендерил сервер
export const appConfig: ApplicationConfig = {
providers: [
{
provide: KBQ_DATE_TIMEZONE,
useFactory: () => inject(TransferState).get(TIMEZONE_KEY, 'utc')
}
]
};

На первом визите куки ещё нет, и страница отрисуется с запасным значением ('utc' в примере). После гидратации сравните его с Intl.DateTimeFormat().resolvedOptions().timeZone, запишите куку и, если пояс нужно применить сразу, вызовите setTimezone() — это одна перерисовка и только на первом визите. Если пояс хранится в профиле пользователя на сервере, первый визит тоже обойдётся без неё.

Long format

Name
Value
absoluteLongDate (current year)
October 28
absoluteLongDate (not current year)
October 28, 2025
absoluteLongDateTime (current year)
October 28, 14:29
absoluteLongDateTime (current year) (with seconds)
October 28, 14:29:54
absoluteLongDateTime (current year) (with milliseconds)
October 28, 14:29:54.628
absoluteLongDateTime (not current year)
October 28, 2025, 14:29
absoluteLongDateTime (not current year) (with seconds)
October 28, 2025, 14:29:54
absoluteLongDateTime (not current year) (with milliseconds)
October 28, 2025, 14:29:54.628

Short format

Name
Value
absoluteShortDate (current year)
Oct 28
absoluteShortDate (not current year)
Oct 28, 2025
absoluteShortDateTime (current year)
Oct 28, 14:29
absoluteShortDateTime (current year) (with seconds)
Oct 28, 14:29:54
absoluteShortDateTime (current year) (with milliseconds)
Oct 28, 14:29:54.631
absoluteShortDateTime (not current year)
Oct 28, 2025, 14:29
absoluteShortDateTime (not current year) (with seconds)
Oct 28, 2025, 14:29:54
absoluteShortDateTime (not current year) (with milliseconds)
Oct 28, 2025, 14:29:54.631

Long format

Name
Value
Before yesterday (not current year)
October 26, 2025
Before yesterday (current year)
October 26, 14:29
Before yesterday (current year) (with seconds)
October 26, 14:29:54
Before yesterday (current year) (with milliseconds)
October 26, 14:29:54.637
Yesterday
Yesterday, 14:29
Yesterday (with seconds)
Yesterday, 14:29:54
Yesterday (with milliseconds)
Yesterday, 14:29:54.637
Today
Today, 13:29
Today (with seconds)
Today, 13:29:54
Today (with milliseconds)
Today, 13:29:54.637
Tomorrow
Tomorrow, 15:29
Tomorrow (with seconds)
Tomorrow, 15:29:54
Tomorrow (with milliseconds)
Tomorrow, 15:29:54.637
After tomorrow (current year)
October 30, 14:29
After tomorrow (current year) (with seconds)
October 30, 14:29:54
After tomorrow (current year) (with milliseconds)
October 30, 14:29:54.637
After tomorrow (not current year)
October 30, 2027

Short format

Name
Value
Before yesterday (not current year)
Oct 26, 2025
Before yesterday (current year)
Oct 26, 14:29
Before yesterday (current year) (with seconds)
Oct 26, 14:29:54
Before yesterday (current year) (with milliseconds)
Oct 26, 14:29:54.653
Yesterday
Yesterday, 14:29
Yesterday (with seconds)
Yesterday, 14:29:54
Yesterday (with milliseconds)
Yesterday, 14:29:54.653
Today
Today, 13:29
Today (with seconds)
Today, 13:29:54
Today (with milliseconds)
Today, 13:29:54.653
Tomorrow
Tomorrow, 15:29
Tomorrow (with seconds)
Tomorrow, 15:29:54
Tomorrow (with milliseconds)
Tomorrow, 15:29:54.653
After tomorrow (current year)
Oct 30, 14:29
After tomorrow (current year) (with seconds)
Oct 30, 14:29:54
After tomorrow (current year) (with milliseconds)
Oct 30, 14:29:54.653
After tomorrow (not current year)
Oct 30, 2027

Closed range

Long format

Name
Value
rangeLongDate (current month)
October 1–10
rangeLongDate (not current month)
January 1 – February 10
rangeLongDate (start date is not in current year)
January 1, 2025 – February 10, 2026
rangeLongDate (end date is not in current year)
January 1, 2026 – February 10, 2027
rangeLongDateTime (the same day, current year)
October 10, from 10:14 to 11:28
rangeLongDateTime (the same day, current year) (with seconds)
October 10, from 10:14:54 to 11:28:54
rangeLongDateTime (the same day, current year) (with milliseconds)
October 10, from 10:14:54.676 to 11:28:54.676
rangeLongDateTime (the same day, not current year)
January 11, 2025, from 10:14 to 11:28
rangeLongDateTime (the same day, not current year) (with seconds)
January 11, 2025, from 10:14:54 to 11:28:54
rangeLongDateTime (the same day, not current year) (with milliseconds)
January 11, 2025, from 10:14:54.676 to 11:28:54.676
rangeLongDateTime (not current month)
From January 1, 10:14 to February 1, 11:28
rangeLongDateTime (not current month) (with seconds)
From January 1, 10:14:54 to February 1, 11:28:54
rangeLongDateTime (not current month) (with milliseconds)
From January 1, 10:14:54.676 to February 1, 11:28:54.676
rangeLongDateTime (start date is not in current year)
From January 1, 2025, 10:14 to February 1, 2026, 11:28
rangeLongDateTime (start date is not in current year) (with seconds)
From January 1, 2025, 10:14:54 to February 1, 2026, 11:28:54
rangeLongDateTime (start date is not in current year) (with milliseconds)
From January 1, 2025, 10:14:54.676 to February 1, 2026, 11:28:54.676
rangeLongDateTime (end date is not in current year)
From January 1, 2026, 10:14 to February 1, 2025, 11:28
rangeLongDateTime (end date is not in current year) (with seconds)
From January 1, 2026, 10:14:54 to February 1, 2025, 11:28:54
rangeLongDateTime (end date is not in current year) (with milliseconds)
From January 1, 2026, 10:14:54.676 to February 1, 2025, 11:28:54.676

Middle format

Name
Value
rangeMiddleDateTime
October 1, 14:29 – October 10, 14:29
rangeMiddleDateTime (with seconds)
October 1, 14:29:54 – October 10, 14:29:54
rangeMiddleDateTime (with milliseconds)
October 1, 14:29:54.700 – October 10, 14:29:54.700
rangeMiddleDateTime (the same day)
10:14–10:28, October 10
rangeMiddleDateTime (the same day) (with seconds)
10:14:54–10:28:54, October 10
rangeMiddleDateTime (the same day) (with milliseconds)
10:14:54.700–10:28:54.700, October 10
rangeMiddleDateTime (the same day, not current year)
10:14–11:28, January 11, 2025
rangeMiddleDateTime (the same day, not current year) (with seconds)
10:14:54–11:28:54, January 11, 2025
rangeMiddleDateTime (the same day, not current year) (with milliseconds)
10:14:54.700–11:28:54.700, January 11, 2025
rangeMiddleDateTime (not current month)
January 1, 10:14 – February 1, 11:28
rangeMiddleDateTime (not current month) (with seconds)
January 1, 10:14:54 – February 1, 11:28:54
rangeMiddleDateTime (not current month) (with milliseconds)
January 1, 10:14:54.700 – February 1, 11:28:54.700
rangeMiddleDateTime (start date is not in current year)
January 1, 2025, 10:14 – January 1, 2026, 11:28
rangeMiddleDateTime (start date is not in current year) (with seconds)
January 1, 2025, 10:14:54 – January 1, 2026, 11:28:54
rangeMiddleDateTime (start date is not in current year) (with milliseconds)
January 1, 2025, 10:14:54.700 – January 1, 2026, 11:28:54.700
rangeMiddleDateTime (end date is not in current year)
January 1, 2026, 10:14 – January 1, 2027, 11:28
rangeMiddleDateTime (end date is not in current year) (with seconds)
January 1, 2026, 10:14:54 – January 1, 2027, 11:28:54
rangeMiddleDateTime (end date is not in current year) (with milliseconds)
January 1, 2026, 10:14:54.700 – January 1, 2027, 11:28:54.700

Short format

Name
Value
rangeShortDate (current month)
Oct 1–10
rangeShortDate (not current month)
Jan 1 – Feb 10
rangeShortDate (start date is not in current year)
Jan 1, 2025 – Feb 10, 2026
rangeShortDate (end date is not in current year)
Jan 1, 2026 – Feb 10, 2027
rangeShortDateTime (the same day, current year)
10:14–11:28, Oct 10
rangeShortDateTime (the same day, current year) (with seconds)
10:14:54–11:28:54, Oct 10
rangeShortDateTime (the same day, current year) (with milliseconds)
10:14:54.718–11:28:54.718, Oct 10
rangeShortDateTime (the same day, not current year)
10:14–11:28, Jan 11, 2025
rangeShortDateTime (the same day, not current year) (with seconds)
10:14:54–11:28:54, Jan 11, 2025
rangeShortDateTime (the same day, not current year) (with milliseconds)
10:14:54.718–11:28:54.718, Jan 11, 2025
rangeShortDateTime (not current month)
Jan 1, 10:14 – Feb 1, 11:28
rangeShortDateTime (not current month) (with seconds)
Jan 1, 10:14:54 – Feb 1, 11:28:54
rangeShortDateTime (not current month) (with milliseconds)
Jan 1, 10:14:54.718 – Feb 1, 11:28:54.718
rangeShortDateTime (start date is not in current year)
Jan 1, 2025, 10:14 – Feb 1, 2026, 11:28
rangeShortDateTime (start date is not in current year) (with seconds)
Jan 1, 2025, 10:14:54 – Feb 1, 2026, 11:28:54
rangeShortDateTime (start date is not in current year) (with milliseconds)
Jan 1, 2025, 10:14:54.718 – Feb 1, 2026, 11:28:54.718
rangeShortDateTime (end date is not in current year)
Jan 1, 2026, 10:14 – Feb 1, 2027, 11:28
rangeShortDateTime (end date is not in current year) (with seconds)
Jan 1, 2026, 10:14:54 – Feb 1, 2027, 11:28:54
rangeShortDateTime (end date is not in current year) (with milliseconds)
Jan 1, 2026, 10:14:54.718 – Feb 1, 2027, 11:28:54.718

Opened range

Long format

Name
Value
openedRangeLongDate (only start)
From October 28
openedRangeLongDate (only end)
Until October 28
openedRangeLongDate (only start, start date is not in current year)
From October 28 2027
openedRangeLongDate (only end, end date is not in current year)
Until October 28 2027
openedRangeLongDateTime (only start)
From October 28, 14:29
openedRangeLongDateTime (only start) (with seconds)
From October 28, 14:29:54
openedRangeLongDateTime (only start) (with milliseconds)
From October 28, 14:29:54.749
openedRangeLongDateTime (only start, start date is not in current year)
From October 28 2027, 14:29
openedRangeLongDateTime (only start, start date is not in current year) (with seconds)
From October 28 2027, 14:29:54
openedRangeLongDateTime (only start, start date is not in current year) (with milliseconds)
From October 28 2027, 14:29:54.749
openedRangeLongDateTime (only end)
Until October 28, 14:29
openedRangeLongDateTime (only end) (with seconds)
Until October 28, 14:29:54
openedRangeLongDateTime (only end) (with milliseconds)
Until October 28, 14:29:54.749
openedRangeLongDateTime (only end, end date is not in current year)
Until October 28 2027, 14:29
openedRangeLongDateTime (only end, end date is not in current year) (with seconds)
Until October 28 2027, 14:29:54
openedRangeLongDateTime (only end, end date is not in current year) (with milliseconds)
Until October 28 2027, 14:29:54.749

Short format

Name
Value
openedRangeShortDate (only start)
From Oct 28
openedRangeShortDate (only end)
Until Oct 28
openedRangeShortDate (only start, start date is not in current year)
From Oct 28 2027
openedRangeShortDate (only end, end date is not in current year)
Until Oct 28 2027
openedRangeShortDateTime (only start)
From Oct 28, 14:29
openedRangeShortDateTime (only start) (with seconds)
From Oct 28, 14:29:54
openedRangeShortDateTime (only start) (with milliseconds)
From Oct 28, 14:29:54.756
openedRangeShortDateTime (only start, start date is not in current year)
From Oct 28 2027, 14:29
openedRangeShortDateTime (only start, start date is not in current year) (with seconds)
From Oct 28 2027, 14:29:54
openedRangeShortDateTime (only start, start date is not in current year) (with milliseconds)
From Oct 28 2027, 14:29:54.756
openedRangeShortDateTime (only end)
Until Oct 28, 14:29
openedRangeShortDateTime (only end) (with seconds)
Until Oct 28, 14:29:54
openedRangeShortDateTime (only end) (with milliseconds)
Until Oct 28, 14:29:54.756
openedRangeShortDateTime (only end, end date is not in current year)
Until Oct 28 2027, 14:29
openedRangeShortDateTime (only end, end date is not in current year) (with seconds)
Until Oct 28 2027, 14:29:54
openedRangeShortDateTime (only end, end date is not in current year) (with milliseconds)
Until Oct 28 2027, 14:29:54.756

The shortest format

Name
Seconds
Only minutes
Seconds
0:25
Seconds and milliseconds
0:25,125
Minutes and seconds
2:25
0:02
Hours, minutes and seconds
48:02:25
48:02

Long format

Name
Value
Seconds
21 seconds
Minutes and seconds
1 minute 25 seconds
Minutes
22 minutes
Minutes (more than hour)
120 minutes
Hours and minutes
1 hour 21 minutes
Hours and minutes (more than day)
32 hours 20 minutes
Hours
32 hours
Days and hours
1 day 8 hours
Days
2 days
Days (more then week)
21 days
Weeks and days
2 weeks 1 day
Weeks
2 weeks
Months and weeks
1 month 3 weeks
Months
2 months
Months with fraction
1.5 months
Years and months
3 years 11 months
Years
5 years
Years with fraction
5.5 years

Short format

Name
Value
Seconds and milliseconds
21.365 s
Seconds
21 s
Minutes and seconds
1 min 25 s
Minutes
22 min
Minutes (more than hour)
120 min
Hours and minutes
1 h 21 min
Hours and minutes (more than day)
32 h 20 min
Hours
32 h
Days and hours
1 d 8 h
Days
2 d
Days (more then week)
15 d
Weeks and days
2 w 1 d
Weeks
2 w
Months and weeks
1 mo 3 w
Months
2 mo
Months with fraction
2.5 mo
Years and months
3 y 11 mo
Years
5 y
Years with fraction
5.5 y
Предложения по улучшению
Если вы нашли ошибку или хотите доработать статью, создайте запрос на GitHub.