|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import {AnyCalendarDate, AnyTime, Calendar} from './types'; |
|
|
import {CalendarDate, CalendarDateTime, ZonedDateTime} from './CalendarDate'; |
|
|
import {fromAbsolute, toAbsolute, toCalendar, toCalendarDate} from './conversion'; |
|
|
import {weekStartData} from './weekStartData'; |
|
|
|
|
|
type DateValue = CalendarDate | CalendarDateTime | ZonedDateTime; |
|
|
|
|
|
|
|
|
export function isSameDay(a: DateValue, b: DateValue): boolean { |
|
|
b = toCalendar(b, a.calendar); |
|
|
return a.era === b.era && a.year === b.year && a.month === b.month && a.day === b.day; |
|
|
} |
|
|
|
|
|
|
|
|
export function isSameMonth(a: DateValue, b: DateValue): boolean { |
|
|
b = toCalendar(b, a.calendar); |
|
|
|
|
|
a = startOfMonth(a); |
|
|
b = startOfMonth(b); |
|
|
return a.era === b.era && a.year === b.year && a.month === b.month; |
|
|
} |
|
|
|
|
|
|
|
|
export function isSameYear(a: DateValue, b: DateValue): boolean { |
|
|
b = toCalendar(b, a.calendar); |
|
|
a = startOfYear(a); |
|
|
b = startOfYear(b); |
|
|
return a.era === b.era && a.year === b.year; |
|
|
} |
|
|
|
|
|
|
|
|
export function isEqualDay(a: DateValue, b: DateValue): boolean { |
|
|
return isEqualCalendar(a.calendar, b.calendar) && isSameDay(a, b); |
|
|
} |
|
|
|
|
|
|
|
|
export function isEqualMonth(a: DateValue, b: DateValue): boolean { |
|
|
return isEqualCalendar(a.calendar, b.calendar) && isSameMonth(a, b); |
|
|
} |
|
|
|
|
|
|
|
|
export function isEqualYear(a: DateValue, b: DateValue): boolean { |
|
|
return isEqualCalendar(a.calendar, b.calendar) && isSameYear(a, b); |
|
|
} |
|
|
|
|
|
|
|
|
export function isEqualCalendar(a: Calendar, b: Calendar): boolean { |
|
|
return a.isEqual?.(b) ?? b.isEqual?.(a) ?? a.identifier === b.identifier; |
|
|
} |
|
|
|
|
|
|
|
|
export function isToday(date: DateValue, timeZone: string): boolean { |
|
|
return isSameDay(date, today(timeZone)); |
|
|
} |
|
|
|
|
|
const DAY_MAP = { |
|
|
sun: 0, |
|
|
mon: 1, |
|
|
tue: 2, |
|
|
wed: 3, |
|
|
thu: 4, |
|
|
fri: 5, |
|
|
sat: 6 |
|
|
}; |
|
|
|
|
|
type DayOfWeek = 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getDayOfWeek(date: DateValue, locale: string, firstDayOfWeek?: DayOfWeek): number { |
|
|
let julian = date.calendar.toJulianDay(date); |
|
|
|
|
|
|
|
|
|
|
|
let weekStart = firstDayOfWeek ? DAY_MAP[firstDayOfWeek] : getWeekStart(locale); |
|
|
let dayOfWeek = Math.ceil(julian + 1 - weekStart) % 7; |
|
|
if (dayOfWeek < 0) { |
|
|
dayOfWeek += 7; |
|
|
} |
|
|
|
|
|
return dayOfWeek; |
|
|
} |
|
|
|
|
|
|
|
|
export function now(timeZone: string): ZonedDateTime { |
|
|
return fromAbsolute(Date.now(), timeZone); |
|
|
} |
|
|
|
|
|
|
|
|
export function today(timeZone: string): CalendarDate { |
|
|
return toCalendarDate(now(timeZone)); |
|
|
} |
|
|
|
|
|
export function compareDate(a: AnyCalendarDate, b: AnyCalendarDate): number { |
|
|
return a.calendar.toJulianDay(a) - b.calendar.toJulianDay(b); |
|
|
} |
|
|
|
|
|
export function compareTime(a: AnyTime, b: AnyTime): number { |
|
|
return timeToMs(a) - timeToMs(b); |
|
|
} |
|
|
|
|
|
function timeToMs(a: AnyTime): number { |
|
|
return a.hour * 60 * 60 * 1000 + a.minute * 60 * 1000 + a.second * 1000 + a.millisecond; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getHoursInDay(a: CalendarDate, timeZone: string): number { |
|
|
let ms = toAbsolute(a, timeZone); |
|
|
let tomorrow = a.add({days: 1}); |
|
|
let tomorrowMs = toAbsolute(tomorrow, timeZone); |
|
|
return (tomorrowMs - ms) / 3600000; |
|
|
} |
|
|
|
|
|
let localTimeZone: string | null = null; |
|
|
|
|
|
|
|
|
export function getLocalTimeZone(): string { |
|
|
if (localTimeZone == null) { |
|
|
localTimeZone = new Intl.DateTimeFormat().resolvedOptions().timeZone; |
|
|
} |
|
|
|
|
|
return localTimeZone!; |
|
|
} |
|
|
|
|
|
|
|
|
export function setLocalTimeZone(timeZone: string): void { |
|
|
localTimeZone = timeZone; |
|
|
} |
|
|
|
|
|
|
|
|
export function resetLocalTimeZone(): void { |
|
|
localTimeZone = null; |
|
|
} |
|
|
|
|
|
|
|
|
export function startOfMonth(date: ZonedDateTime): ZonedDateTime; |
|
|
export function startOfMonth(date: CalendarDateTime): CalendarDateTime; |
|
|
export function startOfMonth(date: CalendarDate): CalendarDate; |
|
|
export function startOfMonth(date: DateValue): DateValue; |
|
|
export function startOfMonth(date: DateValue): DateValue { |
|
|
|
|
|
return date.subtract({days: date.day - 1}); |
|
|
} |
|
|
|
|
|
|
|
|
export function endOfMonth(date: ZonedDateTime): ZonedDateTime; |
|
|
export function endOfMonth(date: CalendarDateTime): CalendarDateTime; |
|
|
export function endOfMonth(date: CalendarDate): CalendarDate; |
|
|
export function endOfMonth(date: DateValue): DateValue; |
|
|
export function endOfMonth(date: DateValue): DateValue { |
|
|
return date.add({days: date.calendar.getDaysInMonth(date) - date.day}); |
|
|
} |
|
|
|
|
|
|
|
|
export function startOfYear(date: ZonedDateTime): ZonedDateTime; |
|
|
export function startOfYear(date: CalendarDateTime): CalendarDateTime; |
|
|
export function startOfYear(date: CalendarDate): CalendarDate; |
|
|
export function startOfYear(date: DateValue): DateValue; |
|
|
export function startOfYear(date: DateValue): DateValue { |
|
|
return startOfMonth(date.subtract({months: date.month - 1})); |
|
|
} |
|
|
|
|
|
|
|
|
export function endOfYear(date: ZonedDateTime): ZonedDateTime; |
|
|
export function endOfYear(date: CalendarDateTime): CalendarDateTime; |
|
|
export function endOfYear(date: CalendarDate): CalendarDate; |
|
|
export function endOfYear(date: DateValue): DateValue; |
|
|
export function endOfYear(date: DateValue): DateValue { |
|
|
return endOfMonth(date.add({months: date.calendar.getMonthsInYear(date) - date.month})); |
|
|
} |
|
|
|
|
|
export function getMinimumMonthInYear(date: AnyCalendarDate): number { |
|
|
if (date.calendar.getMinimumMonthInYear) { |
|
|
return date.calendar.getMinimumMonthInYear(date); |
|
|
} |
|
|
|
|
|
return 1; |
|
|
} |
|
|
|
|
|
export function getMinimumDayInMonth(date: AnyCalendarDate): number { |
|
|
if (date.calendar.getMinimumDayInMonth) { |
|
|
return date.calendar.getMinimumDayInMonth(date); |
|
|
} |
|
|
|
|
|
return 1; |
|
|
} |
|
|
|
|
|
|
|
|
export function startOfWeek(date: ZonedDateTime, locale: string, firstDayOfWeek?: DayOfWeek): ZonedDateTime; |
|
|
export function startOfWeek(date: CalendarDateTime, locale: string, firstDayOfWeek?: DayOfWeek): CalendarDateTime; |
|
|
export function startOfWeek(date: CalendarDate, locale: string, firstDayOfWeek?: DayOfWeek): CalendarDate; |
|
|
export function startOfWeek(date: DateValue, locale: string, firstDayOfWeek?: DayOfWeek): DateValue; |
|
|
export function startOfWeek(date: DateValue, locale: string, firstDayOfWeek?: DayOfWeek): DateValue { |
|
|
let dayOfWeek = getDayOfWeek(date, locale, firstDayOfWeek); |
|
|
return date.subtract({days: dayOfWeek}); |
|
|
} |
|
|
|
|
|
|
|
|
export function endOfWeek(date: ZonedDateTime, locale: string, firstDayOfWeek?: DayOfWeek): ZonedDateTime; |
|
|
export function endOfWeek(date: CalendarDateTime, locale: string, firstDayOfWeek?: DayOfWeek): CalendarDateTime; |
|
|
export function endOfWeek(date: CalendarDate, locale: string, firstDayOfWeek?: DayOfWeek): CalendarDate; |
|
|
export function endOfWeek(date: DateValue, locale: string, firstDayOfWeek?: DayOfWeek): DateValue; |
|
|
export function endOfWeek(date: DateValue, locale: string, firstDayOfWeek?: DayOfWeek): DateValue { |
|
|
return startOfWeek(date, locale, firstDayOfWeek).add({days: 6}); |
|
|
} |
|
|
|
|
|
const cachedRegions = new Map<string, string>(); |
|
|
const cachedWeekInfo = new Map<string, {firstDay: number}>(); |
|
|
|
|
|
function getRegion(locale: string): string | undefined { |
|
|
|
|
|
|
|
|
if (Intl.Locale) { |
|
|
|
|
|
let region = cachedRegions.get(locale); |
|
|
if (!region) { |
|
|
|
|
|
region = new Intl.Locale(locale).maximize().region; |
|
|
if (region) { |
|
|
cachedRegions.set(locale, region); |
|
|
} |
|
|
} |
|
|
return region; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let part = locale.split('-')[1]; |
|
|
return part === 'u' ? undefined : part; |
|
|
} |
|
|
|
|
|
function getWeekStart(locale: string): number { |
|
|
|
|
|
|
|
|
let weekInfo = cachedWeekInfo.get(locale); |
|
|
if (!weekInfo) { |
|
|
if (Intl.Locale) { |
|
|
|
|
|
let localeInst = new Intl.Locale(locale); |
|
|
if ('getWeekInfo' in localeInst) { |
|
|
|
|
|
weekInfo = localeInst.getWeekInfo(); |
|
|
if (weekInfo) { |
|
|
cachedWeekInfo.set(locale, weekInfo); |
|
|
return weekInfo.firstDay; |
|
|
} |
|
|
} |
|
|
} |
|
|
let region = getRegion(locale); |
|
|
if (locale.includes('-fw-')) { |
|
|
|
|
|
|
|
|
let day = locale.split('-fw-')[1].split('-')[0]; |
|
|
if (day === 'mon') { |
|
|
weekInfo = {firstDay: 1}; |
|
|
} else if (day === 'tue') { |
|
|
weekInfo = {firstDay: 2}; |
|
|
} else if (day === 'wed') { |
|
|
weekInfo = {firstDay: 3}; |
|
|
} else if (day === 'thu') { |
|
|
weekInfo = {firstDay: 4}; |
|
|
} else if (day === 'fri') { |
|
|
weekInfo = {firstDay: 5}; |
|
|
} else if (day === 'sat') { |
|
|
weekInfo = {firstDay: 6}; |
|
|
} else { |
|
|
weekInfo = {firstDay: 0}; |
|
|
} |
|
|
} else if (locale.includes('-ca-iso8601')) { |
|
|
weekInfo = {firstDay: 1}; |
|
|
} else { |
|
|
weekInfo = {firstDay: region ? weekStartData[region] || 0 : 0}; |
|
|
} |
|
|
cachedWeekInfo.set(locale, weekInfo); |
|
|
} |
|
|
|
|
|
return weekInfo.firstDay; |
|
|
} |
|
|
|
|
|
|
|
|
export function getWeeksInMonth(date: DateValue, locale: string, firstDayOfWeek?: DayOfWeek): number { |
|
|
let days = date.calendar.getDaysInMonth(date); |
|
|
return Math.ceil((getDayOfWeek(startOfMonth(date), locale, firstDayOfWeek) + days) / 7); |
|
|
} |
|
|
|
|
|
|
|
|
export function minDate<A extends DateValue, B extends DateValue>(a?: A | null, b?: B | null): A | B | null | undefined { |
|
|
if (a && b) { |
|
|
return a.compare(b) <= 0 ? a : b; |
|
|
} |
|
|
|
|
|
return a || b; |
|
|
} |
|
|
|
|
|
|
|
|
export function maxDate<A extends DateValue, B extends DateValue>(a?: A | null, b?: B | null): A | B | null | undefined { |
|
|
if (a && b) { |
|
|
return a.compare(b) >= 0 ? a : b; |
|
|
} |
|
|
|
|
|
return a || b; |
|
|
} |
|
|
|
|
|
const WEEKEND_DATA = { |
|
|
AF: [4, 5], |
|
|
AE: [5, 6], |
|
|
BH: [5, 6], |
|
|
DZ: [5, 6], |
|
|
EG: [5, 6], |
|
|
IL: [5, 6], |
|
|
IQ: [5, 6], |
|
|
IR: [5, 5], |
|
|
JO: [5, 6], |
|
|
KW: [5, 6], |
|
|
LY: [5, 6], |
|
|
OM: [5, 6], |
|
|
QA: [5, 6], |
|
|
SA: [5, 6], |
|
|
SD: [5, 6], |
|
|
SY: [5, 6], |
|
|
YE: [5, 6] |
|
|
}; |
|
|
|
|
|
|
|
|
export function isWeekend(date: DateValue, locale: string): boolean { |
|
|
let julian = date.calendar.toJulianDay(date); |
|
|
|
|
|
|
|
|
|
|
|
let dayOfWeek = Math.ceil(julian + 1) % 7; |
|
|
if (dayOfWeek < 0) { |
|
|
dayOfWeek += 7; |
|
|
} |
|
|
|
|
|
let region = getRegion(locale); |
|
|
|
|
|
|
|
|
let [start, end] = WEEKEND_DATA[region!] || [6, 0]; |
|
|
return dayOfWeek === start || dayOfWeek === end; |
|
|
} |
|
|
|
|
|
|
|
|
export function isWeekday(date: DateValue, locale: string): boolean { |
|
|
return !isWeekend(date, locale); |
|
|
} |
|
|
|