v-calendar
Version:
A calendar and date picker plugin for Vue.js.
276 lines (253 loc) • 7.26 kB
text/typescript
import {
type PageConfig,
type CachedPage,
getPageKey,
getCachedPage,
getPage,
} from './page';
import {
type DateSource,
type DateOptions,
type DayOfWeek,
type MonthParts,
type MonthInYear,
type SimpleDateParts,
type TimeNames,
DatePatchKeys,
applyRulesForDateParts,
daysInWeek,
formatDate,
parseDate,
getDateParts,
getDateFromParts,
getDayNames,
getMonthNames,
getMonthParts,
getMonthPartsKey,
getHourDates,
getRelativeTimeNames,
isDateParts,
} from './date/helpers';
import Cache from './cache';
import { type DateRangeSource, DateRange } from './date/range';
import { defaultLocales } from './defaults';
import {
isString,
isNumber,
isDate,
isObject,
has,
pick,
clamp,
defaultsDeep,
} from './helpers';
export interface LocaleConfig {
id: string;
firstDayOfWeek: number;
masks: any;
monthCacheSize: number;
pageCacheSize: number;
}
const DEFAULT_MONTH_CACHE_SIZE = 12;
const DEFAULT_PAGE_CACHE_SIZE = 5;
export function resolveConfig(
config: string | Partial<LocaleConfig> | undefined,
locales: any,
) {
// Get the detected locale string
const detLocale = new Intl.DateTimeFormat().resolvedOptions().locale;
// Resolve the locale id
let id;
if (isString(config)) {
id = config;
} else if (has(config, 'id')) {
id = config!.id;
}
id = (id || detLocale).toLowerCase();
const localeKeys = Object.keys(locales);
const validKey = (k: string) => localeKeys.find(lk => lk.toLowerCase() === k);
id = validKey(id) || validKey(id.substring(0, 2)) || detLocale;
// Add fallback and spread default locale to prevent repetitive update loops
const defLocale: LocaleConfig = {
...locales['en-IE'],
...locales[id],
id,
monthCacheSize: DEFAULT_MONTH_CACHE_SIZE,
pageCacheSize: DEFAULT_PAGE_CACHE_SIZE,
};
// Assign or merge defaults with provided config
const result: LocaleConfig = isObject(config)
? defaultsDeep(config, defLocale)
: defLocale;
// Return resolved config
return result;
}
export default class Locale {
id: any;
daysInWeek: number;
firstDayOfWeek: DayOfWeek;
masks: any;
timezone: string | undefined;
hourLabels: string[];
dayNames: string[];
dayNamesShort: string[];
dayNamesShorter: string[];
dayNamesNarrow: string[];
monthNames: string[];
monthNamesShort: string[];
relativeTimeNames: TimeNames;
amPm: [string, string] = ['am', 'pm'];
monthCache: Cache<MonthParts>;
pageCache: Cache<CachedPage>;
constructor(
config: Partial<LocaleConfig> | string | undefined = undefined,
timezone?: string,
) {
const { id, firstDayOfWeek, masks, monthCacheSize, pageCacheSize } =
resolveConfig(config, defaultLocales.value);
this.monthCache = new Cache(
monthCacheSize,
getMonthPartsKey,
getMonthParts,
);
this.pageCache = new Cache(pageCacheSize, getPageKey, getCachedPage);
this.id = id;
this.daysInWeek = daysInWeek;
this.firstDayOfWeek = clamp(firstDayOfWeek, 1, daysInWeek) as DayOfWeek;
this.masks = masks;
this.timezone = timezone || undefined;
this.hourLabels = this.getHourLabels();
this.dayNames = getDayNames('long', this.id);
this.dayNamesShort = getDayNames('short', this.id);
this.dayNamesShorter = this.dayNamesShort.map(s => s.substring(0, 2));
this.dayNamesNarrow = getDayNames('narrow', this.id);
this.monthNames = getMonthNames('long', this.id);
this.monthNamesShort = getMonthNames('short', this.id);
this.relativeTimeNames = getRelativeTimeNames(this.id);
}
formatDate(date: Date, masks: string | string[]) {
return formatDate(date, masks, this);
}
parseDate(dateString: string, mask: string | string[]) {
return parseDate(dateString, mask, this);
}
toDate(
d: DateSource | Partial<SimpleDateParts>,
opts: Partial<DateOptions> = {},
): Date {
const nullDate = new Date(NaN);
let result = nullDate;
const { fillDate, mask, patch, rules } = opts;
if (isNumber(d)) {
opts.type = 'number';
result = new Date(+d);
} else if (isString(d)) {
opts.type = 'string';
result = d ? parseDate(d, mask || 'iso', this) : nullDate;
} else if (isDate(d)) {
opts.type = 'date';
result = new Date(d.getTime());
} else if (isDateParts(d)) {
opts.type = 'object';
result = this.getDateFromParts(d);
}
// Patch parts or apply rules if needed
if (result && (patch || rules)) {
let parts = this.getDateParts(result);
// Patch date parts
if (patch && fillDate != null) {
const fillParts = this.getDateParts(this.toDate(fillDate));
parts = this.getDateParts(
this.toDate({ ...fillParts, ...pick(parts, DatePatchKeys[patch]) }),
);
}
// Apply date part rules
if (rules) {
parts = applyRulesForDateParts(parts, rules);
}
result = this.getDateFromParts(parts);
}
return result || nullDate;
}
toDateOrNull(
d: DateSource | Partial<SimpleDateParts>,
opts: Partial<DateOptions> = {},
): Date | null {
const dte = this.toDate(d, opts);
return isNaN(dte.getTime()) ? null : dte;
}
fromDate(date: Date, { type, mask }: Partial<DateOptions> = {}) {
switch (type) {
case 'number':
return date ? date.getTime() : NaN;
case 'string':
return date ? this.formatDate(date, mask || 'iso') : '';
case 'object':
return date ? this.getDateParts(date) : null;
default:
return date ? new Date(date) : null;
}
}
range(source: DateRangeSource) {
return DateRange.from(source, this);
}
ranges(ranges: DateRangeSource | DateRangeSource[]) {
return DateRange.fromMany(ranges, this);
}
getDateParts(date: Date) {
return getDateParts(date, this);
}
getDateFromParts(parts: Partial<SimpleDateParts>) {
return getDateFromParts(parts, this.timezone);
}
getDateFromParams(
year: number,
month: number,
day: number,
hours: number,
minutes: number,
seconds: number,
milliseconds: number,
) {
return this.getDateFromParts({
year,
month,
day,
hours,
minutes,
seconds,
milliseconds,
});
}
getPage(config: PageConfig) {
const cachedPage = this.pageCache.getOrSet(config, this);
return getPage(config, cachedPage);
}
getMonthParts(month: number, year: number) {
const { firstDayOfWeek } = this;
return this.monthCache.getOrSet(month, year, firstDayOfWeek);
}
getThisMonthParts() {
const date = new Date();
return this.getMonthParts(
<MonthInYear>(date.getMonth() + 1),
date.getFullYear(),
);
}
getPrevMonthParts(month: number, year: number) {
if (month === 1) return this.getMonthParts(12, year - 1);
return this.getMonthParts(month - 1, year);
}
getNextMonthParts(month: number, year: number) {
if (month === 12) return this.getMonthParts(1, year + 1);
return this.getMonthParts(month + 1, year);
}
getHourLabels() {
return getHourDates().map(d => {
return this.formatDate(d, this.masks.hours);
});
}
getDayId(date: Date) {
return this.formatDate(date, 'YYYY-MM-DD');
}
}