ng-zorro-antd
Version:
An enterprise-class UI components based on Ant Design and Angular
1,476 lines • 54.9 kB
JavaScript
import { startOfWeek, startOfMonth, setYear, addYears, setMonth, addMonths, setDay, getQuarter, setQuarter, isSameDay, isSameSecond, isSameMinute, isSameHour, isSameMonth, isSameQuarter, isSameYear, differenceInCalendarDays, differenceInSeconds, differenceInMinutes, differenceInHours, differenceInCalendarMonths, differenceInCalendarQuarters, differenceInCalendarYears, isToday, isValid, isFirstDayOfMonth, isLastDayOfMonth, getDaysInMonth, format, addDays, parse, startOfQuarter, getISOWeek, addSeconds } from 'date-fns';
import { warn } from 'ng-zorro-antd/core/logger';
import * as i0 from '@angular/core';
import { InjectionToken, makeEnvironmentProviders, inject, Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { getLocaleDayPeriods, FormStyle, TranslationWidth } from '@angular/common';
import { isNotNil } from 'ng-zorro-antd/core/util';
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
/**
* @deprecated Will be removed in v23.
*/
function wrongSortOrder(rangeValue) {
const [start, end] = rangeValue;
return !!start && !!end && end.isBeforeDay(start);
}
/**
* @deprecated Use `NzDateAdapter` directly instead. Will be removed in v23.
*/
function normalizeRangeValue(value, hasTimePicker, type = 'month', activePart = 'left') {
const [start, end] = value;
let newStart = start || new CandyDate();
let newEnd = end || (hasTimePicker ? newStart : newStart.add(1, type));
if (start && !end) {
newStart = start;
newEnd = hasTimePicker ? start : start.add(1, type);
}
else if (!start && end) {
newStart = hasTimePicker ? end : end.add(-1, type);
newEnd = end;
}
else if (start && end && !hasTimePicker) {
if (start.isSame(end, type)) {
newEnd = newStart.add(1, type);
}
else {
if (activePart === 'left') {
newEnd = newStart.add(1, type);
}
else {
newStart = newEnd.add(-1, type);
}
}
}
return [newStart, newEnd];
}
/**
* @deprecated Use `Date` cloning directly instead. Will be removed in v23.
*/
function cloneDate(value) {
if (Array.isArray(value)) {
return value.map(v => (v instanceof CandyDate ? v.clone() : null));
}
else {
return value instanceof CandyDate ? value.clone() : null;
}
}
/**
* Wrapping kind APIs for date operating and unify
* NOTE: every new API return new CandyDate object without side effects to the former Date object
* NOTE: most APIs are based on local time other than customized locale id (this needs tobe support in future)
* TODO: support format() against to angular's core API
*
* @deprecated Use `NzDateAdapter` directly instead. Will be removed in v23.
*/
class CandyDate {
nativeDate;
// locale: string; // Custom specified locale ID
constructor(date) {
if (date) {
if (date instanceof Date) {
this.nativeDate = date;
}
else if (typeof date === 'string' || typeof date === 'number') {
warn('The string type is not recommended for date-picker, use "Date" type');
this.nativeDate = new Date(date);
}
else {
throw new Error('The input date type is not supported ("Date" is now recommended)');
}
}
else {
this.nativeDate = new Date();
}
}
calendarStart(options) {
return new CandyDate(startOfWeek(startOfMonth(this.nativeDate), options));
}
// ---------------------------------------------------------------------
// | Native shortcuts
// -----------------------------------------------------------------------------\
getYear() {
return this.nativeDate.getFullYear();
}
getMonth() {
return this.nativeDate.getMonth();
}
getDay() {
return this.nativeDate.getDay();
}
getTime() {
return this.nativeDate.getTime();
}
getDate() {
return this.nativeDate.getDate();
}
getHours() {
return this.nativeDate.getHours();
}
getMinutes() {
return this.nativeDate.getMinutes();
}
getSeconds() {
return this.nativeDate.getSeconds();
}
getMilliseconds() {
return this.nativeDate.getMilliseconds();
}
// ---------------------------------------------------------------------
// | New implementing APIs
// ---------------------------------------------------------------------
clone() {
return new CandyDate(new Date(this.nativeDate));
}
setHms(hour, minute, second) {
const newDate = new Date(this.nativeDate.setHours(hour, minute, second));
return new CandyDate(newDate);
}
setYear(year) {
return new CandyDate(setYear(this.nativeDate, year));
}
addYears(amount) {
return new CandyDate(addYears(this.nativeDate, amount));
}
// NOTE: month starts from 0
// NOTE: Don't use the native API for month manipulation as it not restrict the date when it overflows, eg. (new Date('2018-7-31')).setMonth(1) will be date of 2018-3-03 instead of 2018-2-28
setMonth(month) {
return new CandyDate(setMonth(this.nativeDate, month));
}
addMonths(amount) {
return new CandyDate(addMonths(this.nativeDate, amount));
}
setDay(day, options) {
return new CandyDate(setDay(this.nativeDate, day, options));
}
setDate(amount) {
const date = new Date(this.nativeDate);
date.setDate(amount);
return new CandyDate(date);
}
getQuarter() {
return getQuarter(this.nativeDate);
}
setQuarter(quarter) {
return new CandyDate(setQuarter(this.nativeDate, quarter));
}
addDays(amount) {
return this.setDate(this.getDate() + amount);
}
add(amount, mode) {
switch (mode) {
case 'decade':
return this.addYears(amount * 10);
case 'year':
return this.addYears(amount);
case 'month':
return this.addMonths(amount);
default:
return this.addMonths(amount);
}
}
isSame(date, grain = 'day') {
if (date == null) {
return false;
}
let fn;
switch (grain) {
case 'decade':
fn = (pre, next) => Math.abs(pre.getFullYear() - next.getFullYear()) < 11;
break;
case 'year':
fn = isSameYear;
break;
case 'quarter':
fn = isSameQuarter;
break;
case 'month':
fn = isSameMonth;
break;
case 'day':
fn = isSameDay;
break;
case 'hour':
fn = isSameHour;
break;
case 'minute':
fn = isSameMinute;
break;
case 'second':
fn = isSameSecond;
break;
default:
fn = isSameDay;
break;
}
return fn(this.nativeDate, this.toNativeDate(date));
}
isSameYear(date) {
return this.isSame(date, 'year');
}
isSameQuarter(date) {
return this.isSame(date, 'quarter');
}
isSameMonth(date) {
return this.isSame(date, 'month');
}
isSameDay(date) {
return this.isSame(date, 'day');
}
isSameHour(date) {
return this.isSame(date, 'hour');
}
isSameMinute(date) {
return this.isSame(date, 'minute');
}
isSameSecond(date) {
return this.isSame(date, 'second');
}
isBefore(date, grain = 'day') {
if (date == null) {
return false;
}
let fn;
switch (grain) {
case 'year':
fn = differenceInCalendarYears;
break;
case 'quarter':
fn = differenceInCalendarQuarters;
break;
case 'month':
fn = differenceInCalendarMonths;
break;
case 'day':
fn = differenceInCalendarDays;
break;
case 'hour':
fn = differenceInHours;
break;
case 'minute':
fn = differenceInMinutes;
break;
case 'second':
fn = differenceInSeconds;
break;
default:
fn = differenceInCalendarDays;
break;
}
return fn(this.nativeDate, this.toNativeDate(date)) < 0;
}
isBeforeYear(date) {
return this.isBefore(date, 'year');
}
isBeforeQuarter(date) {
return this.isBefore(date, 'quarter');
}
isBeforeMonth(date) {
return this.isBefore(date, 'month');
}
isBeforeDay(date) {
return this.isBefore(date, 'day');
}
// Equal to today accurate to "day"
isToday() {
return isToday(this.nativeDate);
}
isValid() {
return isValid(this.nativeDate);
}
isFirstDayOfMonth() {
return isFirstDayOfMonth(this.nativeDate);
}
isLastDayOfMonth() {
return isLastDayOfMonth(this.nativeDate);
}
toNativeDate(date) {
return date instanceof CandyDate ? date.nativeDate : date;
}
}
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
const timeUnits = [
['Y', 1000 * 60 * 60 * 24 * 365], // years
['M', 1000 * 60 * 60 * 24 * 30], // months
['D', 1000 * 60 * 60 * 24], // days
['H', 1000 * 60 * 60], // hours
['m', 1000 * 60], // minutes
['s', 1000], // seconds
['S', 1] // million seconds
];
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
/** Injection token for date configuration. */
const NZ_DATE_CONFIG = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'nz-date-config' : '');
/** Injection token for the date locale used by the configured date adapter. */
const NZ_DATE_LOCALE = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'nz-date-locale' : '');
/** Default date configuration. */
const NZ_DATE_CONFIG_DEFAULT = {
firstDayOfWeek: undefined
};
/** Merges user config with default config. */
function mergeDateConfig(config) {
return { ...NZ_DATE_CONFIG_DEFAULT, ...config };
}
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
const NOT_IMPLEMENTED = 'NzDateAdapter: method not implemented. Override this method in your adapter to opt in.';
/**
* Injection token for providing a custom NzDateAdapter implementation.
*/
const NZ_DATE_ADAPTER = new InjectionToken(typeof ngDevMode !== 'undefined' && ngDevMode ? 'nz-date-adapter-type' : '');
/**
* Provides a custom NzDateAdapter implementation.
* Use this when you want to provide your own adapter implementation.
*
* @param adapterClass The adapter class to use (must extend NzDateAdapter)
* @param config Optional configuration for the adapter
* @returns EnvironmentProviders for the adapter
*
* @example
* ```typescript
* export const appConfig: ApplicationConfig = {
* providers: [provideNzDateAdapter(JalaliDateAdapter, { locale: faIR, firstDayOfWeek: 6 })]
* };
* ```
*/
function provideNzDateAdapter(adapterClass, config) {
const { locale, ...dateConfig } = config ?? {};
return makeEnvironmentProviders([
adapterClass,
{ provide: NzDateAdapter, useExisting: adapterClass },
{ provide: NZ_DATE_CONFIG, useValue: { ...NZ_DATE_CONFIG_DEFAULT, ...dateConfig } },
...(locale !== undefined ? [{ provide: NZ_DATE_LOCALE, useValue: locale }] : [])
]);
}
/**
* NzDateAdapter is the abstraction boundary between ng-zorro-antd and any date library.
*
* CONTRACT FOR SUBCLASS AUTHORS:
*
* - You MUST implement all abstract methods.
* - You MAY override derived methods for efficiency.
* - You MAY override optional methods to opt into features.
*
* @see https://github.com/angular/components/blob/main/src/material/core/datetime/date-adapter.ts
*/
class NzDateAdapter {
/** The current locale. */
locale;
_localeChanges = new Subject();
/** Stream that emits when the locale changes. */
localeChanges = this._localeChanges;
// =============================================================
// MATERIAL DERIVED: IMPLEMENTED METHODS (MAY OVERRIDE)
// =============================================================
/** Sets the locale used for formatting and parsing. */
setLocale(locale) {
this.locale = locale;
this._localeChanges.next();
}
/**
* Attempts to deserialize a value to a valid date object.
* Accepts ISO 8601 strings, Date objects, or null/undefined.
*/
deserialize(value) {
if (value == null || this.isDateInstance(value)) {
return value;
}
return this.invalid();
}
/** Gets a valid date object if possible, otherwise returns null. */
getValidDateOrNull(obj) {
if (this.isDateInstance(obj)) {
const date = obj;
return this.isValid(date) ? date : null;
}
return null;
}
/** Compares two dates, returning a number indicating their relative order. */
compareDate(first, second) {
return (this.getYear(first) - this.getYear(second) ||
this.getMonth(first) - this.getMonth(second) ||
this.getDate(first) - this.getDate(second));
}
/** Checks whether two dates represent the same calendar day. */
sameDate(first, second) {
if (first && second) {
const firstValid = this.isValid(first);
const secondValid = this.isValid(second);
if (firstValid && secondValid) {
return this.compareDate(first, second) === 0;
}
return firstValid === secondValid;
}
return first === second;
}
/** Clamps a date between min and max bounds. */
clampDate(date, min, max) {
if (min && this.compareDate(min, date) > 0) {
return this.clone(min);
}
if (max && this.compareDate(date, max) > 0) {
return this.clone(max);
}
return date;
}
/** Gets the calendar's start of month (first day at midnight). */
calendarStartOfMonth(date) {
return this.createDate(this.getYear(date), this.getMonth(date), 1);
}
/** Gets the calendar's start of week. */
calendarStartOfWeek(date) {
const dayOfWeek = this.getDayOfWeek(date);
const firstDayOfWeek = this.getFirstDayOfWeek();
const diff = (dayOfWeek - firstDayOfWeek + 7) % 7;
return this.addCalendarDays(date, -diff);
}
/** Checks whether the given date is the first day of its month. */
isFirstDayOfMonth(date) {
return this.getDate(date) === 1;
}
/** Checks whether the given date is the last day of its month. */
isLastDayOfMonth(date) {
return this.getDate(date) === this.getNumDaysInMonth(date);
}
/** Checks whether the given date is today. */
isToday(date) {
return this.sameDate(date, this.today());
}
// =============================================================
// MATERIAL OPTIONAL: TIME METHODS (THROW BY DEFAULT)
// =============================================================
/** Sets the time on the given date. */
setTime(_date, _hours, _minutes, _seconds) {
throw new Error(NOT_IMPLEMENTED);
}
/** Gets the hours component of the given date. */
getHours(_date) {
throw new Error(NOT_IMPLEMENTED);
}
/** Gets the minutes component of the given date. */
getMinutes(_date) {
throw new Error(NOT_IMPLEMENTED);
}
/** Gets the seconds component of the given date. */
getSeconds(_date) {
throw new Error(NOT_IMPLEMENTED);
}
/** Parses a time value into a date. */
parseTime(_value, _parseFormat) {
throw new Error(NOT_IMPLEMENTED);
}
/** Adds the specified number of seconds to the given date. */
addSeconds(_date, _amount) {
throw new Error(NOT_IMPLEMENTED);
}
/** Compares two times, returning a number indicating their relative order. */
compareTime(first, second) {
return (this.getHours(first) - this.getHours(second) ||
this.getMinutes(first) - this.getMinutes(second) ||
this.getSeconds(first) - this.getSeconds(second));
}
/** Checks whether two dates represent the same time (same hour, minute, second). */
sameTime(first, second) {
if (first && second) {
return (!this.isValid(first) && !this.isValid(second)) || this.compareTime(first, second) === 0;
}
return first === second;
}
// =============================================================
// NG-ZORRO OPTIONAL: EXTENDED METHODS (THROW BY DEFAULT)
// =============================================================
/** Gets the milliseconds component of the given date. */
getMilliseconds(_date) {
throw new Error(NOT_IMPLEMENTED);
}
/** Gets the timestamp (milliseconds since epoch) of the given date. */
getTime(_date) {
throw new Error(NOT_IMPLEMENTED);
}
/** Gets the calendar system identifier for the given date. */
getCalendarId(_date) {
throw new Error(NOT_IMPLEMENTED);
}
/** Gets the timezone offset for the given date. */
getTimezoneOffset(_date) {
throw new Error(NOT_IMPLEMENTED);
}
// --- Legacy aliases (deprecated, use Material naming instead) ---
/**
* @deprecated Use `addCalendarYears` instead. Will be removed in v23.
*/
addYears(date, amount) {
return this.addCalendarYears(date, amount);
}
/**
* @deprecated Use `addCalendarMonths` instead. Will be removed in v23.
*/
addMonths(date, amount) {
return this.addCalendarMonths(date, amount);
}
/**
* @deprecated Use `addCalendarDays` instead. Will be removed in v23.
*/
addDays(date, amount) {
return this.addCalendarDays(date, amount);
}
/**
* @deprecated Use `getNumDaysInMonth` instead. Will be removed in v23.
*/
getDaysInMonth(date) {
return this.getNumDaysInMonth(date);
}
/**
* @deprecated Use `getDayOfWeek` instead. Will be removed in v23.
*/
getDay(date) {
return this.getDayOfWeek(date);
}
}
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
/**
* Date adapter for date-fns.
*
* To use this adapter, add `provideNzDateFnsAdapter()` to your application providers.
*
* @note Requires date-fns as a peer dependency.
*/
class DateFnsDateAdapter extends NzDateAdapter {
dateLocale = inject(NZ_DATE_LOCALE, { optional: true });
dateConfig = inject(NZ_DATE_CONFIG, { optional: true });
constructor() {
super();
if (this.dateLocale) {
super.setLocale(this.dateLocale);
}
}
// =============================================================
// MATERIAL CORE: ABSTRACT METHODS
// =============================================================
today() {
return new Date();
}
createDate(year, month, date) {
return new Date(year, month, date);
}
clone(date) {
return new Date(date);
}
// --- Date Getters ---
getYear(date) {
return date.getFullYear();
}
getMonth(date) {
return date.getMonth();
}
getDate(date) {
return date.getDate();
}
getDayOfWeek(date) {
return date.getDay();
}
getNumDaysInMonth(date) {
return getDaysInMonth(date);
}
// --- Date Names ---
getYearName(date) {
return format(date, 'yyyy', { locale: this.locale });
}
getMonthNames(style) {
const format$1 = style === 'narrow' ? 'MMMMM' : style === 'short' ? 'MMM' : 'MMMM';
return Array.from({ length: 12 }, (_, i) => format(new Date(2024, i, 1), format$1, { locale: this.locale }));
}
getDateNames() {
return Array.from({ length: 31 }, (_, i) => String(i + 1));
}
getDayOfWeekNames(style) {
const format$1 = style === 'narrow' ? 'EEEEEE' : style === 'short' ? 'EEE' : 'EEEE';
return Array.from({ length: 7 }, (_, i) => format(new Date(2024, 0, i + 1), format$1, { locale: this.locale }));
}
// --- Week ---
getFirstDayOfWeek() {
if (this.dateConfig?.firstDayOfWeek != null) {
return this.dateConfig.firstDayOfWeek;
}
else {
return this.locale?.options?.weekStartsOn ?? 1;
}
}
// --- Date Math ---
addCalendarYears(date, years) {
return addYears(date, years);
}
addCalendarMonths(date, months) {
return addMonths(date, months);
}
addCalendarDays(date, days) {
return addDays(date, days);
}
// --- Format / Parse ---
format(date, displayFormat) {
if (!date) {
return '';
}
if (!this.isValid(date)) {
throw new Error('DateFnsDateAdapter: Cannot format invalid date.');
}
// Convert Angular-style bracket literals to date-fns single-quote literals
// e.g., 'yyyy-[Q]Q' → 'yyyy-'Q'Q'
const formatString = displayFormat.replace(/\[(.*?)\]/g, "'$1'");
return format(date, formatString, {
locale: this.locale,
useAdditionalWeekYearTokens: true,
useAdditionalDayOfYearTokens: true
});
}
parse(value, parseFormat) {
if (typeof value === 'string' && value.length > 0) {
const formats = Array.isArray(parseFormat) ? parseFormat : [parseFormat];
if (!formats.length) {
throw new Error('Formats array must not be empty.');
}
for (const currentFormat of formats) {
// Convert Angular-style bracket literals to date-fns single-quote literals
const formatString = currentFormat.replace(/\[(.*?)\]/g, "'$1'");
const fromFormat = parse(value, formatString, new Date(), {
locale: this.locale,
weekStartsOn: this.getFirstDayOfWeek(),
useAdditionalWeekYearTokens: true,
useAdditionalDayOfYearTokens: true
});
if (this.isValid(fromFormat)) {
return fromFormat;
}
}
return this.invalid();
}
else if (typeof value === 'number') {
return new Date(value);
}
else if (value instanceof Date) {
return this.clone(value);
}
return null;
}
// --- Validation ---
isDateInstance(obj) {
return obj instanceof Date;
}
isValid(date) {
return isValid(date);
}
invalid() {
return new Date(NaN);
}
// =============================================================
// NG-ZORRO CORE: ABSTRACT METHODS
// =============================================================
getQuarter(date) {
return getQuarter(date);
}
setQuarter(date, quarter) {
return setQuarter(date, quarter);
}
startOfQuarter(date) {
return startOfQuarter(date);
}
getISOWeek(date) {
return getISOWeek(date);
}
// --- NG-ZORRO Date Setters ---
setYear(date, year) {
return setYear(date, year);
}
setMonth(date, month) {
return setMonth(date, month);
}
setDate(date, day) {
const result = new Date(date);
result.setDate(day);
return result;
}
// =============================================================
// MATERIAL OPTIONAL: TIME METHODS
// =============================================================
setTime(date, hours, minutes, seconds) {
const result = new Date(date);
result.setHours(hours, minutes, seconds, 0);
return result;
}
getHours(date) {
return date.getHours();
}
getMinutes(date) {
return date.getMinutes();
}
getSeconds(date) {
return date.getSeconds();
}
parseTime(value, parseFormat) {
return this.parse(value, parseFormat);
}
addSeconds(date, amount) {
return addSeconds(date, amount);
}
// =============================================================
// NG-ZORRO OPTIONAL: EXTENDED METHODS
// =============================================================
getMilliseconds(date) {
return date.getMilliseconds();
}
getTime(date) {
return date.getTime();
}
isFirstDayOfMonth(date) {
return isFirstDayOfMonth(date);
}
isLastDayOfMonth(date) {
return isLastDayOfMonth(date);
}
isToday(date) {
return isToday(date);
}
calendarStartOfMonth(date) {
return startOfMonth(date);
}
calendarStartOfWeek(date) {
return startOfWeek(date, { weekStartsOn: this.getFirstDayOfWeek() });
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: DateFnsDateAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: DateFnsDateAdapter, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: DateFnsDateAdapter, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [] });
/**
* Provides the DateFnsDateAdapter as the NzDateAdapter implementation.
* DateFnsDateAdapter uses date-fns library for date operations.
*
* @param config Optional configuration for the adapter
* @returns EnvironmentProviders for the DateFnsDateAdapter
*
* @example
* ```typescript
* export const appConfig: ApplicationConfig = {
* providers: [provideNzDateFnsAdapter({ locale: enUS, firstDayOfWeek: 1 })]
* };
* ```
*
* @note Requires date-fns as a peer dependency.
*/
function provideNzDateFnsAdapter(config) {
return provideNzDateAdapter(DateFnsDateAdapter, config);
}
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
/** Matches strings that look like ISO 8601 dates (e.g. 2024-01-15, 2024-01-15T10:30:00). */
const ISO_8601_REGEX = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|(?:(?:\+|-)\d{2}:\d{2}))?)?$/;
/** Matches time strings in formats like: 10:30, 10:30:45, 10:30 AM, 10.30.45 PM. */
const TIME_REGEX = /^(\d?\d)[:.](\d?\d)(?:[:.](\d?\d))?\s*(AM|PM)?$/i;
/** Creates an array of length `length` with values derived from `valueFunction`. */
function range(length, valueFunction) {
const valuesArray = Array(length);
for (let i = 0; i < length; i++) {
valuesArray[i] = valueFunction(i);
}
return valuesArray;
}
/** Checks if a value is within a specified numeric range. */
function inRange(value, min, max) {
return !isNaN(value) && value >= min && value <= max;
}
/**
* Date adapter using native Date and Intl.DateTimeFormat.
* Fully aligned with Angular Material's NativeDateAdapter implementation.
*/
class NativeDateAdapter extends NzDateAdapter {
dateLocale = inject(NZ_DATE_LOCALE, { optional: true });
dateConfig = inject(NZ_DATE_CONFIG, { optional: true });
constructor() {
super();
if (this.dateLocale !== undefined && typeof this.dateLocale === 'string') {
this.setLocale(this.dateLocale);
}
else {
this.setLocale('en-US');
}
}
// =============================================================
// MATERIAL CORE: ABSTRACT METHODS
// =============================================================
getYear(date) {
return date.getFullYear();
}
getMonth(date) {
return date.getMonth();
}
getDate(date) {
return date.getDate();
}
getDayOfWeek(date) {
return date.getDay();
}
getMonthNames(style) {
const dtf = new Intl.DateTimeFormat(this.locale, { month: style, timeZone: 'utc' });
return range(12, i => this._format(dtf, new Date(2017, i, 1)));
}
getDateNames() {
const dtf = new Intl.DateTimeFormat(this.locale, { day: 'numeric', timeZone: 'utc' });
return range(31, i => this._format(dtf, new Date(2017, 0, i + 1)));
}
getDayOfWeekNames(style) {
const dtf = new Intl.DateTimeFormat(this.locale, { weekday: style, timeZone: 'utc' });
return range(7, i => this._format(dtf, new Date(2017, 0, i + 1)));
}
getYearName(date) {
const dtf = new Intl.DateTimeFormat(this.locale, { year: 'numeric', timeZone: 'utc' });
return this._format(dtf, date);
}
getFirstDayOfWeek() {
// Check for configured override first
if (this.dateConfig?.firstDayOfWeek != null) {
return this.dateConfig.firstDayOfWeek;
}
// Use Intl.Locale API if available (same as Material)
if (typeof Intl !== 'undefined' && Intl.Locale) {
const locale = new Intl.Locale(this.locale);
const firstDay = (locale.getWeekInfo?.() || locale.weekInfo)?.firstDay ?? 0;
// weekInfo.firstDay is 1-7 (Mon-Sun), we need 0-6 (Sun-Sat)
return firstDay === 7 ? 0 : firstDay;
}
return 0;
}
getNumDaysInMonth(date) {
return this.getDate(this._createDateWithOverflow(this.getYear(date), this.getMonth(date) + 1, 0));
}
clone(date) {
return new Date(date.getTime());
}
createDate(year, month, date) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (month < 0 || month > 11) {
throw Error(`Invalid month index "${month}". Month index has to be between 0 and 11.`);
}
if (date < 1) {
throw Error(`Invalid date "${date}". Date has to be greater than 0.`);
}
}
const result = this._createDateWithOverflow(year, month, date);
if (result.getMonth() !== month && (typeof ngDevMode === 'undefined' || ngDevMode)) {
throw Error(`Invalid date "${date}" for month with index "${month}".`);
}
return result;
}
today() {
return new Date();
}
parse(value, parseFormat) {
if (typeof value === 'number') {
return new Date(value);
}
if (value instanceof Date) {
return this.clone(value);
}
if (typeof value !== 'string') {
return null;
}
const text = value.trim();
if (!text) {
return null;
}
if (typeof parseFormat === 'string') {
const parsedByFormat = this.parseByToken(text, parseFormat);
if (parsedByFormat) {
return parsedByFormat;
}
}
const dateOnly = text.match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (dateOnly) {
return this.createDateOrInvalid(Number(dateOnly[1]), Number(dateOnly[2]) - 1, Number(dateOnly[3]));
}
const timestamp = Date.parse(text);
return isNaN(timestamp) ? this.invalid() : new Date(timestamp);
}
format(date, displayFormat) {
// NG-ZORRO extension: return empty string for null (Material throws error)
if (!date) {
return '';
}
if (!this.isValid(date)) {
throw Error('NativeDateAdapter: Cannot format invalid date.');
}
if (typeof displayFormat === 'object' && displayFormat !== null) {
const dtf = new Intl.DateTimeFormat(this.locale, {
...displayFormat,
timeZone: 'utc'
});
return this._format(dtf, date);
}
// String format: NG-ZORRO extension for date-fns token compatibility
return this.formatByToken(date, displayFormat);
}
addCalendarYears(date, years) {
return this.addCalendarMonths(date, years * 12);
}
addCalendarMonths(date, months) {
const newDate = this._createDateWithOverflow(this.getYear(date), this.getMonth(date) + months, this.getDate(date));
// Adjust for month overflow (e.g., Jan 31 + 1 month should become Feb 28/29, not Mar 3)
if (this.getMonth(newDate) !== (((this.getMonth(date) + months) % 12) + 12) % 12) {
newDate.setDate(0);
}
return newDate;
}
addCalendarDays(date, days) {
return this._createDateWithOverflow(this.getYear(date), this.getMonth(date), this.getDate(date) + days);
}
deserialize(value) {
if (typeof value === 'string') {
if (!value) {
return null;
}
// Strict ISO 8601 validation (same as Material)
if (ISO_8601_REGEX.test(value)) {
const date = new Date(value);
if (this.isValid(date)) {
return date;
}
}
}
return super.deserialize(value);
}
isDateInstance(obj) {
return obj instanceof Date;
}
isValid(date) {
return !isNaN(date.getTime());
}
invalid() {
return new Date(NaN);
}
// =============================================================
// NG-ZORRO CORE: ABSTRACT METHODS
// =============================================================
getQuarter(date) {
return Math.floor(date.getMonth() / 3) + 1;
}
setQuarter(date, quarter) {
const currentQuarter = this.getQuarter(date);
const monthOffset = (quarter - currentQuarter) * 3;
return this.addCalendarMonths(date, monthOffset);
}
startOfQuarter(date) {
const quarter = this.getQuarter(date);
const month = (quarter - 1) * 3;
return this.createDate(this.getYear(date), month, 1);
}
getISOWeek(date) {
// ISO week calculation (same algorithm as Material's getISOWeek if available)
const target = new Date(date.valueOf());
const dayNr = (date.getDay() + 6) % 7;
target.setDate(target.getDate() - dayNr + 3);
const firstThursday = target.valueOf();
target.setMonth(0, 1);
if (target.getDay() !== 4) {
target.setMonth(0, 1 + ((4 - target.getDay() + 7) % 7));
}
return 1 + Math.ceil((firstThursday - target.valueOf()) / 604800000);
}
setYear(date, year) {
const result = this.clone(date);
result.setFullYear(year);
return result;
}
setMonth(date, month) {
const result = this.clone(date);
result.setMonth(month);
return result;
}
setDate(date, day) {
const result = this.clone(date);
result.setDate(day);
return result;
}
// =============================================================
// MATERIAL OPTIONAL: TIME METHODS
// =============================================================
setTime(target, hours, minutes, seconds) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (!inRange(hours, 0, 23)) {
throw Error(`Invalid hours "${hours}". Hours value must be between 0 and 23.`);
}
if (!inRange(minutes, 0, 59)) {
throw Error(`Invalid minutes "${minutes}". Minutes value must be between 0 and 59.`);
}
if (!inRange(seconds, 0, 59)) {
throw Error(`Invalid seconds "${seconds}". Seconds value must be between 0 and 59.`);
}
}
const clone = this.clone(target);
clone.setHours(hours, minutes, seconds, 0);
return clone;
}
getHours(date) {
return date.getHours();
}
getMinutes(date) {
return date.getMinutes();
}
getSeconds(date) {
return date.getSeconds();
}
parseTime(userValue, parseFormat) {
if (typeof userValue !== 'string') {
return userValue instanceof Date ? new Date(userValue.getTime()) : null;
}
const value = userValue.trim();
if (value.length === 0) {
return null;
}
// Attempt to parse the value directly.
let result = this._parseTimeString(value);
// Some locales add extra characters around the time, but are otherwise parseable
// (e.g. `00:05 ч.` in bg-BG). Try replacing all non-number and non-colon characters.
if (result === null) {
// Try stripping non-essential characters
const withoutExtras = value.replace(/[^0-9:(AM|PM)]/gi, '').trim();
if (withoutExtras.length > 0) {
result = this._parseTimeString(withoutExtras);
}
}
return result || this.invalid();
}
addSeconds(date, amount) {
return new Date(date.getTime() + amount * 1000);
}
// =============================================================
// NG-ZORRO OPTIONAL: EXTENDED METHODS
// =============================================================
getMilliseconds(date) {
return date.getMilliseconds();
}
getTime(date) {
return date.getTime();
}
getCalendarId(_date) {
// Native Date only supports Gregorian calendar
return 'gregory';
}
isFirstDayOfMonth(date) {
return this.getDate(date) === 1;
}
isLastDayOfMonth(date) {
return this.getDate(date) === this.getNumDaysInMonth(date);
}
isToday(date) {
return this.sameDate(date, this.today());
}
calendarStartOfMonth(date) {
return this.createDate(this.getYear(date), this.getMonth(date), 1);
}
calendarStartOfWeek(date) {
const dayOfWeek = this.getDayOfWeek(date);
const firstDayOfWeek = this.getFirstDayOfWeek();
const diff = (dayOfWeek - firstDayOfWeek + 7) % 7;
const result = this.addCalendarDays(date, -diff);
result.setHours(0, 0, 0, 0);
return result;
}
// =============================================================
// PRIVATE: MATERIAL-STYLE HELPERS
// =============================================================
/**
* Creates a date allowing for month/date overflow (e.g., Jan 32 becomes Feb 1).
* This is how Material handles date creation internally.
*/
_createDateWithOverflow(year, month, date) {
const result = new Date();
result.setFullYear(year, month, date);
result.setHours(0, 0, 0, 0);
return result;
}
/** Pads a number to two digits for ISO formatting. */
_2digit(n) {
return `00${n}`.slice(-2);
}
/**
* Formats a date using Intl.DateTimeFormat while avoiding DST issues.
* Uses UTC internally to ensure consistent formatting across timezones.
*/
_format(dtf, date) {
const d = new Date();
d.setUTCFullYear(date.getFullYear(), date.getMonth(), date.getDate());
d.setUTCHours(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
return dtf.format(d);
}
/**
* Parses time strings in various formats (10:30, 10:30:45, 10:30 AM, etc.)
* Returns null if the string cannot be parsed.
*/
_parseTimeString(value) {
const parsed = value.toUpperCase().match(TIME_REGEX);
if (parsed) {
let hours = parseInt(parsed[1]);
const minutes = parseInt(parsed[2]);
const seconds = parsed[3] == null ? undefined : parseInt(parsed[3]);
const amPm = parsed[4];
// Handle 12-hour format
if (hours === 12) {
hours = amPm === 'AM' ? 0 : hours;
}
else if (amPm === 'PM') {
hours += 12;
}
if (inRange(hours, 0, 23) && inRange(minutes, 0, 59) && (seconds == null || inRange(seconds, 0, 59))) {
return this.setTime(this.today(), hours, minutes, seconds || 0);
}
}
return null;
}
/**
* Parses the date-fns-style token subset used by NG-ZORRO default formats.
* This intentionally stays small; complex calendar parsing belongs in custom adapters.
*/
parseByToken(value, formatStr) {
const tokenPatterns = {
yyyy: '(?<year>\\d{4})',
yy: '(?<year2>\\d{2})',
MM: '(?<month>\\d{2})',
M: '(?<month>\\d{1,2})',
dd: '(?<day>\\d{2})',
d: '(?<day>\\d{1,2})',
HH: '(?<hour>\\d{2})',
H: '(?<hour>\\d{1,2})',
mm: '(?<minute>\\d{2})',
m: '(?<minute>\\d{1,2})',
ss: '(?<second>\\d{2})',
s: '(?<second>\\d{1,2})'
};
const tokens = Object.keys(tokenPatterns).sort((a, b) => b.length - a.length);
let source = '';
for (let i = 0; i < formatStr.length;) {
if (formatStr[i] === '[') {
const end = formatStr.indexOf(']', i + 1);
const literal = end === -1 ? formatStr.slice(i + 1) : formatStr.slice(i + 1, end);
source += this.escapeRegex(literal);
i = end === -1 ? formatStr.length : end + 1;
continue;
}
const token = tokens.find(item => formatStr.startsWith(item, i));
if (token) {
source += tokenPatterns[token];
i += token.length;
}
else {
source += this.escapeRegex(formatStr[i]);
i++;
}
}
let match;
try {
match = value.match(new RegExp(`^${source}$`));
}
catch {
return null;
}
if (!match?.groups) {
return null;
}
const year = match.groups['year']
? Number(match.groups['year'])
: match.groups['year2']
? 2000 + Number(match.groups['year2'])
: this.today().getFullYear();
const month = match.groups['month'] ? Number(match.groups['month']) - 1 : 0;
const day = match.groups['day'] ? Number(match.groups['day']) : 1;
const hour = match.groups['hour'] ? Number(match.groups['hour']) : 0;
const minute = match.groups['minute'] ? Number(match.groups['minute']) : 0;
const second = match.groups['second'] ? Number(match.groups['second']) : 0;
if (!inRange(month, 0, 11) ||
!inRange(day, 1, 31) ||
!inRange(hour, 0, 23) ||
!inRange(minute, 0, 59) ||
!inRange(second, 0, 59)) {
return this.invalid();
}
const result = this.createDateOrInvalid(year, month, day);
if (!this.isValid(result)) {
return result;
}
return this.setTime(result, hour, minute, second);
}
createDateOrInvalid(year, month, date) {
try {
const result = this.createDate(year, month, date);
return this.getYear(result) === year && this.getMonth(result) === month && this.getDate(result) === date
? result
: this.invalid();
}
catch {
return this.invalid();
}
}
escapeRegex(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// =============================================================
// PRIVATE: NG-ZORRO TOKEN-BASED FORMATTING
// =============================================================
/**
* Formats a date using date-fns-style token strings.
* This is a NG-ZORRO extension for compatibility with existing formats.
*/
formatByToken(date, formatStr) {
if (!formatStr) {
return '';
}
const locale = this.locale;
const dtfMonthShort = new Intl.DateTimeFormat(locale, { month: 'short', timeZone: 'utc' });
const dtfMonthLong = new Intl.DateTimeFormat(locale, { month: 'long', timeZone: 'utc' });
const dtfWeekdayNarrow = new Intl.DateTimeFormat(locale, { weekday: 'narrow', timeZone: 'utc' });
const dtfWeekdayShort = new Intl.DateTimeFormat(locale, { weekday: 'short', timeZone: 'utc' });
const dtfWeekdayLong = new Intl.DateTimeFormat(locale, { weekday: 'long', timeZone: 'utc' });
const quarter = this.getQuarter(date);
const hour12 = date.getHours() % 12 || 12;
const tokenValues = {
yyyy: date.getFullYear().toString().padStart(4, '0'),
yy: this._2digit(date.getFullYear() % 100),
MMMM: this._format(dtfMonthLong, date),
MMM: this._format(dtfMonthShort, date),
MM: this._2digit(date.getMonth() + 1),
M: (date.getMonth() + 1).toString(),
dd: this._2digit(date.getDate()),
d: date.getDate().toString(),
EEEEEE: this._format(dtfWeekdayNarrow, date),
EEEEE: this._format(dtfWeekdayNarrow, date),
EEEE: this._format(dtfWeekdayLong, date),
EEE: this._format(dtfWeekdayShort, date),
EE: this._format(dtfWeekdayShort, date),
E: this._format(dtfWeekdayShort, date),
HH: this._2digit(date.getHours()),
H: date.getHours().toString(),
hh: this._2digit(hour12),
h: hour12.toString(),
mm: this._2digit(date.getMinutes()),
m: date.getMinutes().toString(),
ss: this._2digit(date.getSeconds()),
s: date.getSeconds().toString(),
ww: this._2digit(this.getISOWeek(date)),
w: this.getISOWeek(date).toString(),
QQQ: `Q${quarter}`,
QQ: this._2digit(quarter),
Q: quarter.toString(),
a: date.getHours() < 12 ? 'AM' : 'PM'
};
const tokens = Object.keys(tokenValues).sort((a, b) => b.length - a.length);
let result = '';
for (let i = 0; i < formatStr.length;) {
if (formatStr[i] === '[') {
const end = formatStr.indexOf(']', i + 1);
result += end === -1 ? formatStr.slice(i + 1) : formatStr.slice(i + 1, end);
i = end === -1 ? formatStr.length : end + 1;
continue;
}
if (formatStr[i] === 'Q') {
let end = i + 1;
while (formatStr[end] === 'Q') {
end++;
}
const length = end - i;
result += length >= 4 ? quarter.toString() : tokenValues['Q'.repeat(length)];
i = end;
continue;
}
const token = tokens.find(item => formatStr.startsWith(item, i));
if (token) {
result += tokenValues[token];
i += token.length;
}
else {
result += formatStr[i];
i++;
}
}
return result;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NativeDateAdapter, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NativeDateAdapter });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: NativeDateAdapter, decorators: [{
type: Injectable
}], ctorParameters: () => [] });
/**
* Provides the NativeDateAdapter as the NzDateAdapter implementation.
* NativeDateAdapter uses native Date and Intl.DateTimeFormat.
*
* @param config Optional configuration for the adapter
* @returns EnvironmentProviders for the NativeDateAdapter
*
* @example
* ```typescript
* export const appConfig: ApplicationConfig = {
* providers: [provideNzNativeDateAdapter({ locale: 'en-US', firstDayOfWeek: 0 })]
* };
* ```
*/
function provideNzNativeDateAdapter(config) {
return provideNzDateAdapter(NativeDateAdapter, config);
}
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
// from https://github.com/hsuanxyz/ng-time-parser
class NgTimeParser {
format;
localeId;
regex = null;
matchMap = {
hour: null,
minute: null,
second: null,
periodNarrow: null,
periodWide: null,
periodAbbreviated: null
};
constructor(format, localeId) {
this.format = format;
this.localeId = localeId;
this.genRegexp();
}
toDate(str) {
const result = this.getTimeResult(str);
const time = new Date();
if (isNotNil(result?.hour)) {
time.setHours(result.hour);
}
if (isNotNil(result?.minute)) {
time.setMinutes(result.minute);
}
if (isNotNil(result?.second)) {
time.setSeconds(result.second);
}
if (result?.period === 1 && time.getHours() < 12) {
time.setHours(time.getHours() + 12);
}
return time;
}
getTimeResult(str) {
const match = this.regex.exec(str);
let period = null;
if (match) {
if (isNotNil(this.matchMap.periodNarrow)) {
period = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Narrow).indexOf(match[this.matchMap.periodNarrow + 1]);
}
if (isNotNil(this.matchMap.periodWide)) {
period = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Wide).indexOf(match[this.matchMap.periodWide + 1]);
}
if (isNotNil(this.matchMap.periodAbbreviated)) {
period = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Abbreviated).indexOf(match[this.matchMap.periodAbbreviated + 1]);
}
return {
hour: isNotNil(this.matchMap.hour) ? Number.parseInt(match[this.matchMap.hour + 1], 10) : null,
minute: isNotNil(this.matchMap.minute) ? Number.parseInt(match[this.matchMap.minute + 1], 10) : null,
second: isNotNil(this.matchMap.second) ? Number.parseInt(match[this.matchMap.second + 1], 10) : null,
period
};
}
else {
return null;
}
}
genRegexp() {
let regexStr = this.format.replace(/([.*+?^=!:${}()|[\]/\\])/g, '\\$&');
const hourRegex = /h{1,2}/i;
const minuteRegex = /m{1,2}/;
const secondRegex = /s{1,2}/;
const periodNarrow = /aaaaa/;
const periodWide = /aaaa/;
const periodAbbreviated = /a{1,3}/;
const hourMatch = hourRegex.exec(this.format);
const minuteMatch = minuteRegex.exec(this.format);
const secondMatch = secondRegex.exec(this.format);
const periodNarrowMatch = periodNarrow.exec(this.format);
let periodWideMatch = null;
let periodAbbreviatedMatch = null;
if (!periodNarrowMatch) {
periodWideMatch = periodWide.exec(this.format);
}
if (!periodWideMatch && !periodNarrowMatch) {
periodAbbreviatedMatch = periodAbbreviated.exec(this.format);
}
const matchs = [hourMatch, minuteMatch, secondMatch, periodNarrowMatch, periodWideMatch, periodAbbreviatedMatch]
.filter(m => !!m)
.sort((a, b) => a.index - b.index);
matchs.forEach((match, index) => {
switch (match) {
case hourMatch:
this.matchMap.hour = index;
regexStr = regexStr.replace(hourRegex, '(\\d{1,2})');
break;
case minuteMatch:
this.matchMap.minute = index;
regexStr = regexStr.replace(minuteRegex, '(\\d{1,2})');
break;
case secondMatch:
this.matchMap.second = index;
regexStr = regexStr.replace(secondRegex, '(\\d{1,2})');
break;
case periodNarrowMatch: {
this.matchMap.periodNarrow = index;
const periodsNarrow = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Narrow).join('|');
regexStr = regexStr.replace(periodNarrow, `(${periodsNarrow})`);
break;
}
case periodWideMatch: {
this.matchMap.periodWide = index;
const periodsWide = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Wide).join('|');
regexStr = regexStr.replace(periodWide, `(${periodsWide})`);
break;
}
case periodAbbreviatedMatch: {
this.matchMap.periodAbbreviated = index;
const periodsAbbreviated = getLocaleDayPeriods(this.localeId, FormStyle.Format, TranslationWidth.Abbreviated).join('|');
regexStr = regexStr.replace(periodAbbreviated, `(${periodsAbbreviated})`);
break;
}
}
});
this.regex = new RegExp(regexStr);
}
}
/**
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE
*/
/**
* Generated bundle index. Do not edit.
*/
export { CandyDate, DateFnsDateAdapter, NZ_DATE_ADAPTER, NZ_DATE_CONFIG, NZ_DATE_CONFIG_DEFAULT, NZ_DATE_LOCALE, NativeDateAdapter, NzDateAdapter, cloneDate, mergeDateConfig, normalizeRangeValue, provideNzDateAdapter, provideNzDateFnsAdapter, provideNzNativeDateAdapter, timeUnits, wrongSortOrder, NgTimeParser as ɵNgTimeParser };
//# sourceMappingURL=ng-zorro-antd-core-time.mjs.map