Files
zero-fucks-given/packages/services/intl-date/src/main.ts
T

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);
};