UNPKG

igniteui-angular-sovn

Version:

Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps

423 lines (363 loc) 12.3 kB
import { DateRangeDescriptor, DateRangeType } from '../core/dates'; import { mkenum } from '../core/utils'; /** * Sets the selection type - single, multi or range. */ export const CalendarSelection = mkenum({ SINGLE: 'single', MULTI: 'multi', RANGE: 'range' }); export type CalendarSelection = (typeof CalendarSelection)[keyof typeof CalendarSelection]; export enum ScrollMonth { PREV = 'prev', NEXT = 'next', NONE = 'none' } export interface IViewDateChangeEventArgs { previousValue: Date; currentValue: Date; } export const IgxCalendarView = mkenum({ Month: 'month', Year: 'year', Decade: 'decade' }); /** * Determines the Calendar active view - days, months or years. */ export type IgxCalendarView = (typeof IgxCalendarView)[keyof typeof IgxCalendarView]; /** * @hidden */ enum TimeDeltaInterval { Month = 'month', Year = 'year' } const MDAYS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; const FEBRUARY = 1; export const range = (start = 0, stop, step = 1) => { const res = []; const cur = (stop === undefined) ? 0 : start; const max = (stop === undefined) ? start : stop; for (let i = cur; step < 0 ? i > max : i < max; i += step) { res.push(i); } return res; }; /** * Returns true for leap years, false for non-leap years. * * @export * @param year * @returns */ export const isLeap = (year: number): boolean => (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0)); export const weekDay = (year: number, month: number, day: number): number => new Date(year, month, day).getDay(); /** * Return weekday and number of days for year, month. * * @export * @param year * @param month * @returns */ export const monthRange = (year: number, month: number): number[] => { if ((month < 0) || (month > 11)) { throw new Error('Invalid month specified'); } const day = weekDay(year, month, 1); let nDays = MDAYS[month]; if ((month === FEBRUARY) && (isLeap(year))) { nDays++; } return [day, nDays]; }; export const isDateInRanges = (date: Date, ranges: DateRangeDescriptor[]): boolean => { date = new Date(date.getFullYear(), date.getMonth(), date.getDate()); const dateInMs = date.getTime(); if (!ranges) { return false; } for (const descriptor of ranges) { const dRanges = descriptor.dateRange ? descriptor.dateRange.map( r => new Date(r.getFullYear(), r.getMonth(), r.getDate())) : undefined; switch (descriptor.type) { case (DateRangeType.After): if (dateInMs > dRanges[0].getTime()) { return true; } break; case (DateRangeType.Before): if (dateInMs < dRanges[0].getTime()) { return true; } break; case (DateRangeType.Between): const dRange = dRanges.map(d => d.getTime()); const min = Math.min(dRange[0], dRange[1]); const max = Math.max(dRange[0], dRange[1]); if (dateInMs >= min && dateInMs <= max) { return true; } break; case (DateRangeType.Specific): const datesInMs = dRanges.map(d => d.getTime()); for (const specificDateInMs of datesInMs) { if (dateInMs === specificDateInMs) { return true; } } break; case (DateRangeType.Weekdays): const day = date.getDay(); if (day % 6 !== 0) { return true; } break; case (DateRangeType.Weekends): const weekday = date.getDay(); if (weekday % 6 === 0) { return true; } break; default: return false; } } return false; }; export interface ICalendarDate { date: Date; isCurrentMonth: boolean; isPrevMonth: boolean; isNextMonth: boolean; } export interface IFormattedParts { value: string; literal?: string; combined: string; } export interface IFormattingOptions { day?: 'numeric' | '2-digit'; month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow'; weekday?: 'long' | 'short' | 'narrow'; year?: 'numeric' | '2-digit'; } export interface IFormattingViews { day?: boolean; month?: boolean; year?: boolean; } export enum WEEKDAYS { SUNDAY = 0, MONDAY = 1, TUESDAY = 2, WEDNESDAY = 3, THURSDAY = 4, FRIDAY = 5, SATURDAY = 6 } export class Calendar { private _firstWeekDay: WEEKDAYS | number; constructor(firstWeekDay: WEEKDAYS = WEEKDAYS.SUNDAY) { this._firstWeekDay = firstWeekDay; } public get firstWeekDay(): number { return this._firstWeekDay % 7; } public set firstWeekDay(value: number) { this._firstWeekDay = value; } /** * Returns an array of weekdays for one week starting * with the currently set `firstWeekDay` * * this.firstWeekDay = 0 (Sunday) --> [0, 1, 2, 3, 4, 5, 6] * this.firstWeekDay = 1 (Monday) --> [1, 2, 3, 4, 5, 6, 0] * * @returns * * @memberof Calendar */ public weekdays(): number[] { const res = []; for (const i of range(this.firstWeekDay, this.firstWeekDay + 7)) { res.push(i % 7); } return res; } /** * Returns the date values for one month. It will always iterate throught * complete weeks, so it will contain dates outside the specified month. * * @param year * @param month * @param boolean * @returns * * @memberof Calendar */ public monthdates(year: number, month: number, extraWeek = false): ICalendarDate[] { let date = new Date(year, month, 1); let days = (date.getDay() - this.firstWeekDay) % 7; if (days < 0) { days = 7 - Math.abs(days); } date = this.timedelta(date, 'day', -days); const res = []; let value: ICalendarDate; // eslint-disable-next-line no-constant-condition while (true) { value = this.generateICalendarDate(date, year, month); res.push(value); date = this.timedelta(date, 'day', 1); if ((date.getMonth() !== month) && (date.getDay() === this.firstWeekDay)) { if (extraWeek && res.length <= 35) { for (const _ of range(0, 7)) { value = this.generateICalendarDate(date, year, month); res.push(value); date = this.timedelta(date, 'day', 1); } } break; } } return res; } /** * Returns a matrix (array of arrays) representing a month's calendar. * Each row represents a full week; week entries are ICalendarDate objects. * * @param year * @param month * @returns * * @memberof Calendar */ public monthdatescalendar(year: number, month: number, extraWeek = false): ICalendarDate[][] { const dates = this.monthdates(year, month, extraWeek); const res = []; for (const i of range(0, dates.length, 7)) { res.push(dates.slice(i, i + 7)); } return res; } public timedelta(date: Date, interval: string, units: number): Date { const ret = new Date(date); const checkRollover = () => { if (ret.getDate() !== date.getDate()) { ret.setDate(0); } }; switch (interval.toLowerCase()) { case 'year': ret.setFullYear(ret.getFullYear() + units); checkRollover(); break; case 'quarter': ret.setMonth(ret.getMonth() + 3 * units); checkRollover(); break; case 'month': ret.setMonth(ret.getMonth() + units); checkRollover(); break; case 'week': ret.setDate(ret.getDate() + 7 * units); break; case 'day': ret.setDate(ret.getDate() + units); break; case 'hour': ret.setTime(ret.getTime() + units * 3600000); break; case 'minute': ret.setTime(ret.getTime() + units * 60000); break; case 'second': ret.setTime(ret.getTime() + units * 1000); break; default: throw new Error('Invalid interval specifier'); } return ret; } public formatToParts(date: Date, locale: string, options: any, parts: string[]) { const formatter = new Intl.DateTimeFormat(locale, options); const result = { date, full: formatter.format(date) }; if ((formatter as any).formatToParts) { const formattedParts = (formatter as any).formatToParts(date); const toType = (partType: string) => { const index = formattedParts.findIndex(({ type }) => type === partType); const o: IFormattedParts = { value: '', literal: '', combined: '' }; if (partType === 'era' && index > -1) { o.value = formattedParts[index].value; return o; } else if (partType === 'era' && index === -1) { return o; } o.value = formattedParts[index].value; o.literal = formattedParts[index + 1] ? formattedParts[index + 1].value : ''; o.combined = [o.value, o.literal].join(''); return o; }; for (const each of parts) { result[each] = toType(each); } } else { for (const each of parts) { result[each] = { value: '', literal: '', combined: '' }; } } return result; } public getFirstViewDate(date: Date, interval: string, activeViewIdx: number) { return this.timedelta(date, interval, -activeViewIdx); } public getDateByView(date: Date, interval: string, activeViewIdx: number) { return this.timedelta(date, interval, activeViewIdx); } public getNextMonth(date: Date) { return this.timedelta(date, TimeDeltaInterval.Month, 1); } public getPrevMonth(date: Date) { return this.timedelta(date, TimeDeltaInterval.Month, -1); } public getNextYear(date: Date) { return this.timedelta(date, TimeDeltaInterval.Year, 1); } public getPrevYear(date: Date) { return this.timedelta(date, TimeDeltaInterval.Year, -1); } public getWeekNumber(date: Date) { const firstJan = new Date(date.getFullYear(), 0, 1).getTime(); const today = new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime(); const dayInMilSeconds = 86400000; const dayOfYear = ((today - firstJan + 1) / dayInMilSeconds); return Math.ceil(dayOfYear / 7); } private generateICalendarDate(date: Date, year: number, month: number): ICalendarDate { return { date, isCurrentMonth: date.getFullYear() === year && date.getMonth() === month, isNextMonth: this.isNextMonth(date, year, month), isPrevMonth: this.isPreviousMonth(date, year, month) }; } private isPreviousMonth(date: Date, year: number, month: number): boolean { if (date.getFullYear() === year) { return date.getMonth() < month; } return date.getFullYear() < year; } private isNextMonth(date: Date, year: number, month: number): boolean { if (date.getFullYear() === year) { return date.getMonth() > month; } return date.getFullYear() > year; } }