169 lines
5.7 KiB
TypeScript
169 lines
5.7 KiB
TypeScript
import { parse, add, sub, type Locale } from 'date-fns';
|
|
import { toZonedTime, fromZonedTime, formatInTimeZone } from 'date-fns-tz';
|
|
import { SYSTEM_TZ, UNITS_MAP, type IntlDateDisplayFormatEnum, type IntlDateSourceFormatEnum } from './constants';
|
|
import { getLocale, createConfig } from './lib';
|
|
import type {
|
|
IntlDateConfig,
|
|
IntlDateInstanceInterface,
|
|
IntlDateMethods,
|
|
IntlDateLocaleMap,
|
|
IntlDateLanguage,
|
|
InitIntlDateServiceConfig,
|
|
} from './lib';
|
|
|
|
class IntlDateService {
|
|
private _locale: Locale | undefined;
|
|
private _date: Date;
|
|
private _config: IntlDateConfig;
|
|
private _logger?: Console;
|
|
|
|
constructor(config: IntlDateConfig, date: Date, logger?: Console) {
|
|
this._config = config;
|
|
this._logger = logger;
|
|
|
|
const initialLang = config.localeMap[config.language];
|
|
|
|
this._locale = initialLang?.locale || (initialLang ? getLocale(initialLang.code) : undefined);
|
|
this._date = fromZonedTime(date, 'UTC');
|
|
}
|
|
|
|
_call(date: Date): this {
|
|
this._date = fromZonedTime(date, 'UTC');
|
|
return this;
|
|
}
|
|
|
|
public setSupportedLanguages(supportedLanguages: IntlDateLocaleMap): void {
|
|
this._logger?.log('Set supported languages: ', supportedLanguages);
|
|
this._config.localeMap = supportedLanguages;
|
|
}
|
|
|
|
public setLocaleGlobally(lang: IntlDateLanguage): void {
|
|
const language = this._config.localeMap[lang];
|
|
|
|
if (!language) {
|
|
this._logger?.error('Language is not supported. Add it to the supported list.');
|
|
return;
|
|
}
|
|
|
|
this._locale = language.locale ?? getLocale(language.code);
|
|
this._config.language = lang;
|
|
this._logger?.log('Set global language: ', lang);
|
|
}
|
|
private _getSourceFormat(fmt: IntlDateSourceFormatEnum): string {
|
|
return this._config.sourceFormat[fmt];
|
|
}
|
|
private _getDisplayFormat(fmt: IntlDateDisplayFormatEnum): string {
|
|
const localeFormat = this._config.localeMap[this._config.language]?.displayFormats?.[fmt];
|
|
if (localeFormat) return localeFormat;
|
|
return this._config.displayFormat[fmt];
|
|
}
|
|
private _parseDate(dateStr: string, formatStr: string, options?: { strict?: boolean; lang?: IntlDateLanguage }): Date {
|
|
if (formatStr === 'UNIVERSAL_ISO') {
|
|
return new Date(dateStr);
|
|
}
|
|
|
|
const locale = this._config.localeMap[options?.lang ?? this._config.language]?.locale;
|
|
|
|
const parsedLocal = parse(dateStr, formatStr, new Date(), {
|
|
locale: locale,
|
|
});
|
|
|
|
return fromZonedTime(parsedLocal, SYSTEM_TZ);
|
|
}
|
|
private _formatDate(date: Date, pattern: string, displayLocal?: boolean): string {
|
|
if (pattern === 'UNIVERSAL_ISO') {
|
|
return displayLocal ? toZonedTime(date, SYSTEM_TZ).toISOString() : date.toISOString();
|
|
}
|
|
|
|
if (!displayLocal) {
|
|
return formatInTimeZone(date, 'UTC', pattern, {
|
|
locale: this._locale,
|
|
});
|
|
}
|
|
|
|
return formatInTimeZone(date, SYSTEM_TZ, pattern, {
|
|
locale: this._locale,
|
|
});
|
|
}
|
|
|
|
public parseSource: IntlDateMethods['parseSource'] = (date, fmt, options) => {
|
|
this._date = this._parseDate(date, this._getSourceFormat(fmt), options);
|
|
return this;
|
|
};
|
|
|
|
public parseDisplay: IntlDateMethods['parseDisplay'] = (date, fmt, options) => {
|
|
this._date = this._parseDate(date, this._getDisplayFormat(fmt), options);
|
|
return this;
|
|
};
|
|
|
|
public formatToDisplay: IntlDateMethods['formatToDisplay'] = (fmt, options) => {
|
|
try {
|
|
const pattern = this._getDisplayFormat(fmt);
|
|
return this._formatDate(this._date, pattern, options?.displayLocal);
|
|
} catch {
|
|
return 'Invalid date';
|
|
}
|
|
};
|
|
|
|
public formatToSource: IntlDateMethods['formatToSource'] = (fmt, options) => {
|
|
try {
|
|
const pattern = this._getSourceFormat(fmt);
|
|
return this._formatDate(this._date, pattern, options?.displayLocal);
|
|
} catch {
|
|
return 'Invalid date';
|
|
}
|
|
};
|
|
|
|
public add: IntlDateMethods['add'] = (value, unit) => {
|
|
const duration = Object.assign(UNITS_MAP, { [unit]: value });
|
|
const result = add(this._date, duration);
|
|
this._date = fromZonedTime(result, 'UTC');
|
|
return this;
|
|
};
|
|
|
|
public subtract: IntlDateMethods['subtract'] = (value, unit) => {
|
|
const duration = Object.assign(UNITS_MAP, { [unit]: value });
|
|
const result = sub(this._date, duration);
|
|
this._date = fromZonedTime(result, 'UTC');
|
|
return this;
|
|
};
|
|
|
|
public toDate = () => this._date;
|
|
|
|
public setNow = () => {
|
|
this._date = fromZonedTime(new Date(), 'UTC');
|
|
return this;
|
|
};
|
|
|
|
public getMonthShort = (index: number): string => {
|
|
const d = new Date(Date.UTC(2020, index, 1));
|
|
return formatInTimeZone(d, 'UTC', 'MMM', { locale: this._locale });
|
|
};
|
|
|
|
public getMonthLong = (index: number): string => {
|
|
const d = new Date(Date.UTC(2020, index, 1));
|
|
return formatInTimeZone(d, 'UTC', 'MMMM', { locale: this._locale });
|
|
};
|
|
}
|
|
|
|
export const intlDateService = (config?: InitIntlDateServiceConfig, logger?: Console): IntlDateInstanceInterface => {
|
|
const instance = new IntlDateService(createConfig(config), new Date(), logger);
|
|
|
|
const methods: IntlDateMethods = {
|
|
parseSource: instance.parseSource.bind(instance),
|
|
parseDisplay: instance.parseDisplay.bind(instance),
|
|
formatToDisplay: instance.formatToDisplay.bind(instance),
|
|
formatToSource: instance.formatToSource.bind(instance),
|
|
toDate: instance.toDate.bind(instance),
|
|
subtract: instance.subtract.bind(instance),
|
|
add: instance.add.bind(instance),
|
|
setNow: instance.setNow.bind(instance),
|
|
setSupportedLanguages: instance.setSupportedLanguages.bind(instance),
|
|
setLocaleGlobally: instance.setLocaleGlobally.bind(instance),
|
|
getMonthShort: instance.getMonthShort.bind(instance),
|
|
getMonthLong: instance.getMonthLong.bind(instance),
|
|
};
|
|
|
|
return Object.assign((date: Date) => instance._call(date), methods);
|
|
};
|