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
text/typescript
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;
}
}