@taiga-ui/cdk
Version:
Base library for creating Angular components and applications using Taiga UI principles regarding of actual visual appearance
901 lines (886 loc) • 30.6 kB
JavaScript
import { CHAR_NO_BREAK_SPACE, CHAR_EN_DASH } from '@taiga-ui/cdk/constants';
import { tuiInRange, tuiNormalizeToIntNumber } from '@taiga-ui/cdk/utils/math';
function tuiDateClamp(date, min, max) {
if (max && max < date) {
return max;
}
return min && min > date ? min : date;
}
const DAYS_IN_WEEK = 7;
const DAYS_IN_NORMAL_YEAR = 365;
const DAYS_IN_LEAP_YEAR = 366;
const MONTHS_IN_YEAR = 12;
const MIN_DAY = 1;
const MIN_MONTH = 0;
const MAX_MONTH = 11;
const MIN_YEAR = 0;
const MAX_YEAR = 9999;
const MAX_DISPLAYED_YEAR = 2099;
const RANGE_SEPARATOR_CHAR = `${CHAR_NO_BREAK_SPACE}${CHAR_EN_DASH}${CHAR_NO_BREAK_SPACE}`;
const MILLISECONDS_IN_SECOND = 1000;
const SECONDS_IN_MINUTE = 60;
const MINUTES_IN_HOUR = 60;
const HOURS_IN_DAY = 24;
const MILLISECONDS_IN_MINUTE = MILLISECONDS_IN_SECOND * SECONDS_IN_MINUTE;
const MILLISECONDS_IN_HOUR = MILLISECONDS_IN_MINUTE * MINUTES_IN_HOUR;
const MILLISECONDS_IN_DAY = MILLISECONDS_IN_HOUR * HOURS_IN_DAY;
/**
* @internal 'dd/mm/yyyy'.length
* Used in:
* - {@link TuiInputDate}
* - {@link TuiInputDateRange}
* - {@link TuiInputDateTime}
*/
const DATE_FILLER_LENGTH = 10;
/**
* @internal
* Used in {@link TuiInputDateRange}
*/
const DATE_RANGE_FILLER_LENGTH = 2 * DATE_FILLER_LENGTH + RANGE_SEPARATOR_CHAR.length;
const TuiDayOfWeek = {
Sunday: 0,
Monday: 1,
Tuesday: 2,
Wednesday: 3,
Thursday: 4,
Friday: 5,
Saturday: 6,
};
const TuiMonthNumber = {
January: 0,
February: 1,
March: 2,
April: 3,
May: 4,
June: 5,
July: 6,
August: 7,
September: 8,
October: 9,
November: 10,
December: 11,
};
/**
* Immutable year object
* @nosideeffects
*/
class TuiYear {
constructor(year) {
this.year = year;
ngDevMode && console.assert(TuiYear.isValidYear(year));
}
/**
* Checks year for validity
*/
static isValidYear(year) {
return Number.isInteger(year) && tuiInRange(year, MIN_YEAR, MAX_YEAR + 1);
}
/**
* Check if passed year is a leap year
*/
static isLeapYear(year) {
ngDevMode && console.assert(TuiYear.isValidYear(year));
return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0);
}
/**
* Returns amount of leap years from year 0 to the passed one
*/
static getAbsoluteLeapYears(year) {
ngDevMode && console.assert(TuiYear.isValidYear(year));
return Math.ceil(year / 400) + (Math.ceil(year / 4) - Math.ceil(year / 100));
}
static lengthBetween(from, to) {
return to.year - from.year;
}
/**
* Normalizes year by clamping it between min and max years
*/
static normalizeYearPart(year) {
return tuiNormalizeToIntNumber(year, MIN_YEAR, MAX_YEAR);
}
get formattedYear() {
return String(this.year).padStart(4, '0');
}
get isLeapYear() {
return TuiYear.isLeapYear(this.year);
}
/**
* Returns amount of leap years from year 0 to current
*/
get absoluteLeapYears() {
return TuiYear.getAbsoluteLeapYears(this.year);
}
/**
* Passed year is after current
*/
yearBefore({ year }) {
return this.year < year;
}
/**
* Passed year is the same or after current
*/
yearSameOrBefore({ year }) {
return this.year <= year;
}
/**
* Passed year is the same as current
*/
yearSame({ year }) {
return this.year === year;
}
/**
* Passed year is either the same of before the current
*/
yearSameOrAfter({ year }) {
return this.year >= year;
}
/**
* Passed year is before current
*/
yearAfter({ year }) {
return this.year > year;
}
/**
* Immutably offsets year
*/
append({ year = 0 }) {
ngDevMode && console.assert(Number.isInteger(year));
const resultYear = this.year + year;
ngDevMode && console.assert(TuiYear.isValidYear(resultYear));
return new TuiYear(resultYear);
}
toString() {
return this.formattedYear;
}
valueOf() {
return this.year;
}
/**
* Returns the primitive value of the given Date object.
* Depending on the argument, the method can return either a string or a number.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/@@toPrimitive
*/
[Symbol.toPrimitive](hint) {
return Date.prototype[Symbol.toPrimitive].call(this, hint);
}
toJSON() {
return this.formattedYear;
}
}
/**
* Immutable object consisting of year and month
*/
class TuiMonth extends TuiYear {
/**
* @param year
* @param month (starting with 0)
*/
constructor(year, month) {
super(year);
this.month = month;
ngDevMode && console.assert(TuiMonth.isValidMonth(year, month));
}
/**
* Tests month and year for validity
*/
static isValidMonth(year, month) {
return TuiYear.isValidYear(year) && TuiMonth.isValidMonthPart(month);
}
/**
* Returns number of days in a month
*/
static getMonthDaysCount(month, isLeapYear) {
ngDevMode && console.assert(TuiMonth.isValidMonthPart(month));
switch (month) {
case TuiMonthNumber.April:
case TuiMonthNumber.June:
case TuiMonthNumber.November:
case TuiMonthNumber.September:
return 30;
case TuiMonthNumber.February:
return isLeapYear ? 29 : 28;
default:
return 31;
}
}
/**
* Returns current month and year based on local time zone
* @nosideeffects
*/
static currentLocal() {
const nativeDate = new Date();
return new TuiMonth(nativeDate.getFullYear(), nativeDate.getMonth());
}
/**
* Returns current month and year based on UTC
*/
static currentUtc() {
const nativeDate = new Date();
return new TuiMonth(nativeDate.getUTCFullYear(), nativeDate.getUTCMonth());
}
static lengthBetween(from, to) {
const absoluteFrom = from.month + from.year * 12;
const absoluteTo = to.month + to.year * 12;
return absoluteTo - absoluteFrom;
}
/**
* Normalizes number by clamping it between min and max month
*/
static normalizeMonthPart(month) {
return tuiNormalizeToIntNumber(month, MIN_MONTH, MAX_MONTH);
}
/**
* Tests month for validity
*/
static isValidMonthPart(month) {
return Number.isInteger(month) && tuiInRange(month, MIN_MONTH, MAX_MONTH + 1);
}
get formattedMonthPart() {
return String(this.month + 1).padStart(2, '0');
}
/**
* Returns days in a month
*/
get daysCount() {
return TuiMonth.getMonthDaysCount(this.month, this.isLeapYear);
}
/**
* Passed month and year are after current
*/
monthBefore(another) {
return (this.yearBefore(another) ||
(this.yearSame(another) && this.month < another.month));
}
/**
* Passed month and year are after or the same as current
*/
monthSameOrBefore(another) {
return (this.yearBefore(another) ||
(this.yearSame(another) && this.month <= another.month));
}
/**
* Passed month and year are the same as current
*/
monthSame(another) {
return this.yearSame(another) && this.month === another.month;
}
/**
* Passed month and year are either before or equal to current
*/
monthSameOrAfter(another) {
return (this.yearAfter(another) ||
(this.yearSame(another) && this.month >= another.month));
}
/**
* Passed month and year are before current
*/
monthAfter(another) {
return (this.yearAfter(another) ||
(this.yearSame(another) && this.month > another.month));
}
/**
* Immutably alters current month and year by passed offset
*
* @param offset
* @return new month and year object as a result of offsetting current
*/
append({ year = 0, month = 0 }) {
const totalMonths = (this.year + year) * MONTHS_IN_YEAR + this.month + month;
return new TuiMonth(Math.floor(totalMonths / MONTHS_IN_YEAR), totalMonths % MONTHS_IN_YEAR);
}
toString() {
return `${this.formattedMonthPart}.${this.formattedYear}`;
}
valueOf() {
return this.toLocalNativeDate().valueOf();
}
toJSON() {
return `${super.toJSON()}-${this.formattedMonthPart}`;
}
/**
* Returns native {@link Date} based on local time zone
*/
toLocalNativeDate() {
const date = new Date(this.year, this.month);
// for years less than 1900
date.setFullYear(this.year);
return date;
}
/**
* Returns native {@link Date} based on UTC
*/
toUtcNativeDate() {
return new Date(Date.UTC(this.year, this.month));
}
}
/**
* Immutable date object, consisting of day, month and year
*/
class TuiDay extends TuiMonth {
/**
* @param year
* @param month (starting with 0)
* @param day
*/
constructor(year, month, day) {
super(year, month);
this.day = day;
ngDevMode && console.assert(TuiDay.isValidDay(year, month, day));
}
/**
* Creates {@link TuiDay} from native {@link Date} based on local time zone
*/
static fromLocalNativeDate(date) {
return new TuiDay(date.getFullYear(), date.getMonth(), date.getDate());
}
/**
* Creates {@link TuiDay} from native {@link Date} using UTC
*/
static fromUtcNativeDate(date) {
return new TuiDay(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
}
/**
* Check validity of year, month and day
*
* @param year
* @param month
* @param day
* @return boolean validity
*/
static isValidDay(year, month, day) {
return (TuiMonth.isValidMonth(year, month) &&
Number.isInteger(day) &&
tuiInRange(day, MIN_DAY, TuiMonth.getMonthDaysCount(month, TuiYear.isLeapYear(year)) + 1));
}
/**
* Current day based on local time zone
*/
static currentLocal() {
const nativeDate = new Date();
const year = nativeDate.getFullYear();
const month = nativeDate.getMonth();
const day = nativeDate.getDate();
return new TuiDay(year, month, day);
}
/**
* Returns current day based on UTC
*/
static currentUtc() {
const nativeDate = new Date();
const year = nativeDate.getUTCFullYear();
const month = nativeDate.getUTCMonth();
const day = nativeDate.getUTCDate();
return new TuiDay(year, month, day);
}
/**
* Calculates {@link TuiDay} normalizing year, month and day. {@link NaN} is turned into minimal value.
*
* @param year any year value, including invalid
* @param month any month value, including invalid (months start with 0)
* @param day any day value, including invalid
* @return normalized date
*/
static normalizeOf(year, month, day) {
const normalizedYear = TuiYear.normalizeYearPart(year);
const normalizedMonth = TuiMonth.normalizeMonthPart(month);
const normalizedDay = TuiDay.normalizeDayPart(day, normalizedMonth, normalizedYear);
return new TuiDay(normalizedYear, normalizedMonth, normalizedDay);
}
static lengthBetween(from, to) {
return Math.round((to.toLocalNativeDate().getTime() - from.toLocalNativeDate().getTime()) /
(1000 * 60 * 60 * 24));
}
static parseRawDateString(date, dateMode = 'dd/mm/yyyy') {
ngDevMode &&
console.assert(date.length === DATE_FILLER_LENGTH, '[parseRawDateString]: wrong date string length');
switch (dateMode) {
case 'mm/dd/yyyy':
return {
day: Number.parseInt(date.slice(3, 5), 10),
month: Number.parseInt(date.slice(0, 2), 10) - 1,
year: Number.parseInt(date.slice(6, 10), 10),
};
case 'yyyy/mm/dd':
return {
day: Number.parseInt(date.slice(8, 10), 10),
month: Number.parseInt(date.slice(5, 7), 10) - 1,
year: Number.parseInt(date.slice(0, 4), 10),
};
case 'dd/mm/yyyy':
default:
return {
day: Number.parseInt(date.slice(0, 2), 10),
month: Number.parseInt(date.slice(3, 5), 10) - 1,
year: Number.parseInt(date.slice(6, 10), 10),
};
}
}
// TODO: Move month and year related code corresponding classes
/**
* Parsing a string with date with normalization
*
* @param rawDate date string
* @param dateMode date format of the date string (dd/mm/yyyy | mm/dd/yyyy | yyyy/mm/dd)
* @return normalized date
*/
static normalizeParse(rawDate, dateMode = 'dd/mm/yyyy') {
const { day, month, year } = this.parseRawDateString(rawDate, dateMode);
return TuiDay.normalizeOf(year, month, day);
}
/**
* Parsing a date stringified in a toJSON format
* @param ymdString date string in format of YYYY-MM-DD
* @return date
* @throws exceptions if any part of the date is invalid
*/
static jsonParse(ymdString) {
const { day, month, year } = this.parseRawDateString(ymdString, 'yyyy/mm/dd');
if (!TuiMonth.isValidMonth(year, month) ||
!Number.isInteger(day) ||
!tuiInRange(day, MIN_DAY, TuiMonth.getMonthDaysCount(month, TuiYear.isLeapYear(year)) + 1)) {
throw new TuiInvalidDayException(year, month, day);
}
return new TuiDay(year, month, day);
}
static normalizeDayPart(day, month, year) {
ngDevMode && console.assert(TuiMonth.isValidMonth(year, month));
const monthDaysCount = TuiMonth.getMonthDaysCount(month, TuiYear.isLeapYear(year));
return tuiNormalizeToIntNumber(day, 1, monthDaysCount);
}
get formattedDayPart() {
return String(this.day).padStart(2, '0');
}
get isWeekend() {
const dayOfWeek = this.dayOfWeek(false);
return dayOfWeek === TuiDayOfWeek.Saturday || dayOfWeek === TuiDayOfWeek.Sunday;
}
/**
* Returns day of week
*
* @param startFromMonday whether week starts from Monday and not from Sunday
* @return day of week (from 0 to 6)
*/
dayOfWeek(startFromMonday = true) {
const dayOfWeek = startFromMonday
? this.toLocalNativeDate().getDay() - 1
: this.toLocalNativeDate().getDay();
return dayOfWeek < 0 ? 6 : dayOfWeek;
}
/**
* Passed date is after current
*/
dayBefore(another) {
return (this.monthBefore(another) ||
(this.monthSame(another) && this.day < another.day));
}
/**
* Passed date is after or equal to current
*/
daySameOrBefore(another) {
return (this.monthBefore(another) ||
(this.monthSame(another) && this.day <= another.day));
}
/**
* Passed date is the same as current
*/
daySame(another) {
return this.monthSame(another) && this.day === another.day;
}
/**
* Passed date is either before or the same as current
*/
daySameOrAfter(another) {
return (this.monthAfter(another) ||
(this.monthSame(another) && this.day >= another.day));
}
/**
* Passed date is before current
*/
dayAfter(another) {
return (this.monthAfter(another) ||
(this.monthSame(another) && this.day > another.day));
}
/**
* Clamping date between two limits
*
* @param min
* @param max
* @return clamped date
*/
dayLimit(min, max) {
if (min !== null && this.dayBefore(min)) {
return min;
}
return max !== null && this.dayAfter(max) ? max : this;
}
/**
* Immutably alters current day by passed offset
*
* If resulting month has more days than original one, date is rounded to the maximum day
* in the resulting month. Offset of days will be calculated based on the resulted year and month
* to not interfere with parent classes methods
*
* @param offset
* @return new date object as a result of offsetting current
*/
append({ year = 0, month = 0, day = 0 }) {
const totalMonths = (this.year + year) * MONTHS_IN_YEAR + this.month + month;
let years = Math.floor(totalMonths / MONTHS_IN_YEAR);
let months = totalMonths % MONTHS_IN_YEAR;
const monthDaysCount = TuiMonth.getMonthDaysCount(months, TuiYear.isLeapYear(years));
const currentMonthDaysCount = TuiMonth.getMonthDaysCount(this.month, TuiYear.isLeapYear(years));
let days = day;
if (this.day >= monthDaysCount) {
days += this.day - (currentMonthDaysCount - monthDaysCount);
}
else if (currentMonthDaysCount < monthDaysCount &&
this.day === currentMonthDaysCount) {
days += this.day + (monthDaysCount - currentMonthDaysCount);
}
else {
days += this.day;
}
while (days > TuiMonth.getMonthDaysCount(months, TuiYear.isLeapYear(years))) {
days -= TuiMonth.getMonthDaysCount(months, TuiYear.isLeapYear(years));
if (months === TuiMonthNumber.December) {
years++;
months = TuiMonthNumber.January;
}
else {
months++;
}
}
while (days < MIN_DAY) {
if (months === TuiMonthNumber.January) {
years--;
months = TuiMonthNumber.December;
}
else {
months--;
}
days += TuiMonth.getMonthDaysCount(months, TuiYear.isLeapYear(years));
}
return new TuiDay(years, months, days);
}
/**
* Returns formatted whole date
*/
getFormattedDay(dateFormat, separator) {
ngDevMode &&
console.assert(separator.length === 1, 'Separator should consist of only 1 symbol');
const dd = this.formattedDayPart;
const mm = this.formattedMonthPart;
const yyyy = this.formattedYear;
switch (dateFormat) {
case 'mm/dd/yyyy':
return `${mm}${separator}${dd}${separator}${yyyy}`;
case 'yyyy/mm/dd':
return `${yyyy}${separator}${mm}${separator}${dd}`;
case 'dd/mm/yyyy':
default:
return `${dd}${separator}${mm}${separator}${yyyy}`;
}
}
toString(dateFormat = 'dd/mm/yyyy', separator = '.') {
return this.getFormattedDay(dateFormat, separator);
}
toJSON() {
return `${super.toJSON()}-${this.formattedDayPart}`;
}
/**
* Returns native {@link Date} based on local time zone
*/
toLocalNativeDate() {
const date = super.toLocalNativeDate();
date.setDate(this.day);
return date;
}
/**
* Returns native {@link Date} based on UTC
*/
toUtcNativeDate() {
return new Date(Date.UTC(this.year, this.month, this.day));
}
}
class TuiInvalidDayException extends Error {
constructor(year, month, day) {
super(ngDevMode ? `Invalid day: ${year}-${month}-${day}` : '');
}
}
/**
* An immutable range of two {@link TuiMonth} objects
*/
class TuiMonthRange {
constructor(from, to) {
this.from = from;
this.to = to;
ngDevMode && console.assert(from.monthSameOrBefore(to));
}
static sort(month1, month2) {
return month1.monthSameOrBefore(month2)
? new TuiMonthRange(month1, month2)
: new TuiMonthRange(month2, month1);
}
get isSingleMonth() {
return this.from.monthSame(this.to);
}
monthSame(another) {
return this.from.monthSame(another.from) && this.to.monthSame(another.to);
}
monthInRange(month) {
return this.from.monthSameOrBefore(month) && this.to.monthSameOrAfter(month);
}
toString() {
return `${this.from}${RANGE_SEPARATOR_CHAR}${this.to}`;
}
}
/**
* An immutable range of two {@link TuiDay} objects
*/
class TuiDayRange extends TuiMonthRange {
constructor(from, to) {
super(from, to);
this.from = from;
this.to = to;
ngDevMode && console.assert(from.daySameOrBefore(to));
}
/**
* Creates range from two days after sorting them
*
* @param day1
* @param day2
* @return new range with sorted days
*/
static sort(day1, day2) {
return day1.daySameOrBefore(day2)
? new TuiDayRange(day1, day2)
: new TuiDayRange(day2, day1);
}
/**
* Parse and correct a day range in string format
*
* @param rangeString a string of dates in a format dd/mm/yyyy - dd/mm/yyyy
* @param dateMode {@link TuiDateMode}
* @return normalized day range object
*/
static normalizeParse(rangeString, dateMode = 'dd/mm/yyyy') {
const leftDay = TuiDay.normalizeParse(rangeString.slice(0, DATE_FILLER_LENGTH), dateMode);
return rangeString.length < DATE_RANGE_FILLER_LENGTH
? new TuiDayRange(leftDay, leftDay)
: TuiDayRange.sort(leftDay, TuiDay.normalizeParse(rangeString.slice(DATE_FILLER_LENGTH + RANGE_SEPARATOR_CHAR.length), dateMode));
}
get isSingleDay() {
return this.from.daySame(this.to);
}
/**
* Tests ranges for identity
*
* @param another second range to test against current
* @return `true` if days are identical
*/
daySame(another) {
return this.from.daySame(another.from) && this.to.daySame(another.to);
}
dayInRange(day) {
return this.from.daySameOrBefore(day) && this.to.daySameOrAfter(day);
}
/**
* Locks range between two days included, or limits from one side if the other is null
*
* @param min
* @param max
* @return range — clamped range
*/
dayLimit(min, max) {
return new TuiDayRange(this.from.dayLimit(min, max), this.to.dayLimit(min, max));
}
/**
* Human readable format.
*/
getFormattedDayRange(dateFormat, dateSeparator) {
const from = this.from.getFormattedDay(dateFormat, dateSeparator);
const to = this.to.getFormattedDay(dateFormat, dateSeparator);
return `${from}${RANGE_SEPARATOR_CHAR}${to}`;
}
toString(dateFormat = 'dd/mm/yyyy', dateSeparator = '.') {
return this.getFormattedDayRange(dateFormat, dateSeparator);
}
toArray() {
const { from, to } = this;
const arr = [];
for (const day = from.toUtcNativeDate(); day <= to.toUtcNativeDate(); day.setDate(day.getDate() + 1)) {
arr.push(TuiDay.fromLocalNativeDate(day));
}
return arr;
}
}
const TUI_FIRST_DAY = new TuiDay(MIN_YEAR, MIN_MONTH, MIN_DAY);
const TUI_LAST_DAY = new TuiDay(MAX_YEAR, MAX_MONTH, 31);
const TUI_LAST_DISPLAYED_DAY = new TuiDay(MAX_DISPLAYED_YEAR, MAX_MONTH, 31);
/**
* Immutable time object with hours, minutes, seconds and ms
*/
class TuiTime {
constructor(hours, minutes, seconds = 0, ms = 0) {
this.hours = hours;
this.minutes = minutes;
this.seconds = seconds;
this.ms = ms;
ngDevMode &&
console.assert(
// Currently `TuiTime` could have hours more than 23
// in order to not break current behaviour of `isValidTime` the logic is duplicated
Number.isInteger(hours) &&
tuiInRange(hours, 0, Infinity) &&
Number.isInteger(minutes) &&
tuiInRange(minutes, 0, MINUTES_IN_HOUR) &&
Number.isInteger(seconds) &&
tuiInRange(seconds, 0, SECONDS_IN_MINUTE) &&
Number.isInteger(ms) &&
tuiInRange(ms, 0, 1000), 'Time must be real, but got:', hours, minutes, seconds, ms);
}
/**
* Checks if time is valid
*/
static isValidTime(hours, minutes, seconds = 0, ms = 0) {
return (Number.isInteger(hours) &&
tuiInRange(hours, 0, HOURS_IN_DAY) &&
Number.isInteger(minutes) &&
tuiInRange(minutes, 0, MINUTES_IN_HOUR) &&
Number.isInteger(seconds) &&
tuiInRange(seconds, 0, SECONDS_IN_MINUTE) &&
Number.isInteger(ms) &&
tuiInRange(ms, 0, 1000));
}
/**
* Current UTC time.
*/
static current() {
return TuiTime.fromAbsoluteMilliseconds(Date.now() % MILLISECONDS_IN_DAY);
}
/**
* Current time in local timezone
*/
static currentLocal() {
const date = new Date();
return TuiTime.fromAbsoluteMilliseconds((Date.now() - date.getTimezoneOffset() * MILLISECONDS_IN_MINUTE) %
MILLISECONDS_IN_DAY);
}
/**
* Calculates TuiTime from milliseconds
*/
static fromAbsoluteMilliseconds(milliseconds) {
ngDevMode &&
console.assert(Number.isInteger(milliseconds) && milliseconds >= 0, 'Milliseconds must be a non-negative integer.');
const hours = Math.floor(milliseconds / MILLISECONDS_IN_HOUR);
const minutes = Math.floor((milliseconds % MILLISECONDS_IN_HOUR) / MILLISECONDS_IN_MINUTE);
const seconds = Math.floor(((milliseconds % MILLISECONDS_IN_HOUR) % MILLISECONDS_IN_MINUTE) / 1000) || 0;
const ms = Math.floor(((milliseconds % MILLISECONDS_IN_HOUR) % MILLISECONDS_IN_MINUTE) % 1000) || 0;
return new TuiTime(hours, minutes, seconds, ms);
}
/**
* @deprecated Use `TuiTime.fromAbsoluteMilliseconds` + `maskitoParseTime` instead
* ```ts
* const params: MaskitoTimeParams = {mode: '...'};
*
* TuiTime.fromAbsoluteMilliseconds(
* maskitoParseTime(x, params)
* ) // TuiTime
* ```
* TODO(v6): delete
*/
static fromString(time) {
const hours = this.parseHours(time);
const minutes = Number(time.slice(3, 5)) || 0;
const seconds = Number(time.slice(6, 8)) || 0;
const ms = Number(time.slice(9, 12)) || 0;
return new TuiTime(hours, minutes, seconds, ms);
}
/**
* Converts Date object into TuiTime
* @param date
*/
static fromLocalNativeDate(date) {
return new TuiTime(date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds());
}
static parseMeridiemPeriod(time) {
return (/[AP]M/.exec(time.toUpperCase().replaceAll(/\W/g, ''))?.[0] || null);
}
static parseHours(time) {
const hours = Number(time.slice(0, 2));
const meridiem = this.parseMeridiemPeriod(time);
if (!meridiem) {
return hours;
}
if (hours === 12) {
return meridiem === 'AM' ? 0 : 12;
}
return meridiem === 'PM' ? hours + 12 : hours;
}
/**
* Shifts time by hours and minutes
*/
shift({ hours = 0, minutes = 0, seconds = 0, ms = 0 }) {
const totalMs = this.toAbsoluteMilliseconds() +
hours * MILLISECONDS_IN_HOUR +
minutes * MILLISECONDS_IN_MINUTE +
seconds * MILLISECONDS_IN_SECOND +
ms;
const totalSeconds = Math.floor(totalMs / MILLISECONDS_IN_SECOND);
const totalMinutes = Math.floor(totalSeconds / SECONDS_IN_MINUTE);
const totalHours = Math.floor(totalMinutes / MINUTES_IN_HOUR);
return new TuiTime(this.normalizeToRange(totalHours, HOURS_IN_DAY), this.normalizeToRange(totalMinutes, MINUTES_IN_HOUR), this.normalizeToRange(totalSeconds, SECONDS_IN_MINUTE), this.normalizeToRange(totalMs, MILLISECONDS_IN_SECOND));
}
/**
* Converts TuiTime to string
*/
toString(mode) {
const needAddMs = mode?.startsWith('HH:MM:SS.MSS') || (!mode && this.ms > 0);
const needAddSeconds = needAddMs || mode?.startsWith('HH:MM:SS') || (!mode && this.seconds > 0);
const { hours = this.hours, meridiem = '' } = mode?.includes('AA')
? this.toTwelveHour(this.hours)
: {};
const hhMm = `${this.formatTime(hours)}:${this.formatTime(this.minutes)}`;
const ss = needAddSeconds ? `:${this.formatTime(this.seconds)}` : '';
const mss = needAddMs ? `.${this.formatTime(this.ms, 3)}` : '';
const aa = meridiem && `${CHAR_NO_BREAK_SPACE}${meridiem}`;
return `${hhMm}${ss}${mss}${aa}`;
}
valueOf() {
return this.toAbsoluteMilliseconds();
}
/**
* Returns the primitive value of the given Date object.
* Depending on the argument, the method can return either a string or a number.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/@@toPrimitive
*/
[Symbol.toPrimitive](hint) {
return Date.prototype[Symbol.toPrimitive].call(this, hint);
}
/**
* Converts TuiTime to milliseconds
*/
toAbsoluteMilliseconds() {
return (this.hours * MILLISECONDS_IN_HOUR +
this.minutes * MILLISECONDS_IN_MINUTE +
this.seconds * 1000 +
this.ms);
}
formatTime(time, digits = 2) {
return String(time).padStart(digits, '0');
}
toTwelveHour(hours) {
const meridiem = hours >= 12 ? 'PM' : 'AM';
return hours === 0 || hours === 12
? { meridiem, hours: 12 }
: { meridiem, hours: hours % 12 };
}
normalizeToRange(value, modulus) {
return ((value % modulus) + modulus) % modulus;
}
}
/**
* Generated bundle index. Do not edit.
*/
export { DATE_FILLER_LENGTH, DATE_RANGE_FILLER_LENGTH, DAYS_IN_LEAP_YEAR, DAYS_IN_NORMAL_YEAR, DAYS_IN_WEEK, HOURS_IN_DAY, MAX_DISPLAYED_YEAR, MAX_MONTH, MAX_YEAR, MILLISECONDS_IN_DAY, MILLISECONDS_IN_HOUR, MILLISECONDS_IN_MINUTE, MILLISECONDS_IN_SECOND, MINUTES_IN_HOUR, MIN_DAY, MIN_MONTH, MIN_YEAR, MONTHS_IN_YEAR, RANGE_SEPARATOR_CHAR, SECONDS_IN_MINUTE, TUI_FIRST_DAY, TUI_LAST_DAY, TUI_LAST_DISPLAYED_DAY, TuiDay, TuiDayOfWeek, TuiDayRange, TuiInvalidDayException, TuiMonth, TuiMonthNumber, TuiMonthRange, TuiTime, TuiYear, tuiDateClamp };
//# sourceMappingURL=taiga-ui-cdk-date-time.mjs.map