Skip to content

Instantly share code, notes, and snippets.

@sebilasse
Created September 7, 2025 08:59
Show Gist options
  • Select an option

  • Save sebilasse/e748d5869396b2512f22dc2a83c241a0 to your computer and use it in GitHub Desktop.

Select an option

Save sebilasse/e748d5869396b2512f22dc2a83c241a0 to your computer and use it in GitHub Desktop.
import { custom, extend, localeFormats, regexPattern } from './constants.ts';
const timeZone = 'Europe/Berlin';
const base = [
{ l: [ 'ar-AE','ar-BH','ar-DZ','ar-EG','ar-IQ','ar-JO','ar-KW','ar-LB','ar-LY','ar-MA','ar-OM','ar-QA','ar-SA','ar-SY','ar-TN','ar-YE'],
o: { am: 'ص', pm: 'م' } },
{ l: [ 'cy-GB','en-NZ','es-AR','es-BO','es-CO','es-CR','es-DO','es-GT','es-HN','es-MX','es-NI',
'es-PA','es-PE','es-PR','es-PY','es-SV','es-UY','es-VE','gl-ES','mi-NZ','quz-BO','quz-PE' ],
o: { am: 'a.m.', pm: 'p.m.' } },
{ l: [ 'en-029','en-AU','en-BZ','en-CA','en-GB','en-JM','en-PH','en-TT','en-US','en-ZA',
'en-ZW','he-IL','mt-MT','ns-ZA','sw-KE','th-TH','tn-ZA','ur-PK','xh-ZA', 'zh-SG','zu-ZA' ],
o: { am: 'AM', pm: 'PM' } },
{ l: [ 'zh-CN','zh-TW' ], o: { am: '上午', pm: '下午' } },
{ l: [ 'hi-IN','sa-IN' ], o: { am: 'पूर्वाह्न', pm: 'अपराह्न' } },
{ l: [ 'kok-IN','mr-IN' ], o: { am: 'म.पू.', pm: 'म.नं.' } }
].reduce((r, o) => { for (const k of o.l) { r[k] = o.o; } return r; }, {
_: {am:"", pm:""},
'af-ZA': { am: '', pm: 'nm' },
'cs-CZ': { am: 'dop.', pm: 'odp.' },
'dv-MV': { am: 'މކ', pm: 'މފ' },
'el-GR': { am: 'πμ', pm: 'μμ' },
'et-EE': { am: 'EL', pm: 'PL' },
'fa-IR': { am: 'ق.ظ', pm: 'ب.ظ' },
'gu-IN': { am: 'પૂર્વ મધ્યાહ્ન', pm: 'ઉત્તર મધ્યાહ્ન' },
'hu-HU': { am: 'de.', pm: 'du.' },
'ja-JP': { am: '午前', pm: '午後' },
'kn-IN': { am: 'ಪೂರ್ವಾಹ್ನ', pm: 'ಅಪರಾಹ್ನ' },
'ko-KR': { am: '오전', pm: '오후' },
'pa-IN': { am: 'ਸਵੇਰੇ', pm: 'ਸ਼ਾਮ' },
'sq-AL': { am: 'PD', pm: 'MD' },
'syr-SY': { am: 'ܩ.ܛ', pm: 'ܒ.ܛ' },
'ta-IN': { am: 'காலை', pm: 'மாலை' },
'te-IN': { am: 'పూర్వాహ్న', pm: 'అపరాహ్న' },
'vi-VN': { am: 'SA', pm: 'CH' }
});
[
'az-Cyrl-AZ', 'az-Latn-AZ', 'be-BY', 'bg-BG', 'bs-Latn-BA', 'ca-ES', 'da-DK', 'de-AT', 'de-CH', 'de-DE',
'de-LI', 'de-LU', 'en-IE', 'es-CL', 'es-EC', 'es-ES', 'eu-ES', 'fi-FI', 'fo-FO', 'fr-BE', 'fr-CA', 'fr-CH',
'fr-FR', 'fr-LU', 'fr-MC', 'hr-BA', 'hr-HR', 'hy-AM', 'id-ID', 'is-IS', 'it-CH', 'it-IT', 'ka-GE', 'kk-KZ',
'ky-KG', 'lt-LT', 'lv-LV', 'mk-MK', 'mn-MN', 'ms-BN', 'ms-MY', 'nb-NO', 'nl-BE', 'nl-NL', 'nn-NO', 'pl-PL',
'pt-BR', 'pt-PT', 'quz-EC', 'ro-RO', 'ru-RU', 'se-FI', 'se-NO', 'se-SE', 'sk-SK', 'sl-SI', 'sma-NO', 'sma-SE',
'smj-NO', 'smj-SE', 'smn-FI', 'sms-FI', 'sr-Cyrl-BA', 'sr-Cyrl-CS', 'sr-Latn-BA', 'sr-Latn-CS', 'sv-FI',
'sv-SE', 'tr-TR', 'tt-RU', 'uk-UA', 'uz-Cyrl-UZ', 'uz-Latn-UZ', 'zh-HK', 'zh-MO'
].forEach((k) => { base[k] = base._; });
const ar1 = {'ar-LB':1,'ar-MA':1,'ar-TN':1};
const f0set = new Set(['af-ZA', 'dv-MV', 'en-BZ', 'en-CA','en-JM', 'en-PH', 'en-TT', 'en-US','en-ZA', 'en-ZA2', 'en-ZW', 'es-AR',
'es-BO', 'es-CL', 'es-CO', 'es-CR','es-DO', 'es-EC', 'es-GT', 'es-HN','es-MX', 'es-NI', 'es-PA', 'es-PE',
'es-PR', 'es-SV', 'es-VE', 'fa-IR', 'fr-CA', 'he-IL', 'ja-JP', 'ko-KR', 'ns-ZA', 'pt-BR', 'quz-BO', 'quz-EC',
'quz-PE', 'sa-IN', 'sw-KE', 'tn-ZA', 'xh-ZA', 'zh-CN', 'zh-HK', 'zh-MO', 'zh-SG', 'zh-TW', 'zu-ZA']);
for (const k in base) {
if (k.startsWith('ar-') || k.startsWith('syr-')) { base[k].firstDayOfWeek = ar1[k] ? 1 : 6; continue; }
if (f0set.has(k)) { base[k].firstDayOfWeek = 0; continue; }
base[k].firstDayOfWeek = 1;
}
const ymdset = new Set(['af-ZA', 'en-ZA', 'en-ZA2', 'eu-ES', 'fr-CA', 'hu-HU', 'ja-JP', 'ko-KR', 'lt-LT', 'lv-LV', 'mn-MN', 'ns-ZA',
'pl-PL', 'se-SE', 'sma-SE', 'smj-SE', 'sq-AL', 'sv-SE', 'tn-ZA', 'xh-ZA', 'zh-CN', 'zh-TW', 'zu-ZA']);
const mdyset = new Set(['en-029', 'en-PH', 'en-US', 'en-ZW', 'es-PA', 'fa-IR', 'sw-KE']);
const y20 = parseInt(new Intl.DateTimeFormat('en', {year: 'numeric', timeZone}).formatToParts(new Date())[0].value, 10)+20;
const y20ar = new Intl.DateTimeFormat('ar-SA', {year: 'numeric'}).formatToParts(new Date())[0].value;
const twoDigitYearMax = parseInt(`${y20}`.slice(0,-1)+'9', 10);
const arNr = Array.from(`٠١٢٣٤٥٦٧٨٩`).reduce((r, k, i) => { r[k] = `${i}`; return r; }, {});
const twoDigitYearMaxAr = parseInt(`${parseInt(Array.from(`${y20ar}`).map((k) => arNr[k]).join(''), 10)+20}`.slice(0,-1)+'9', 10);
for (const k in base) {
base[k].twoDigitYearMax = (k === 'ar-SA' || k === 'dv-MV') ? twoDigitYearMaxAr : twoDigitYearMax;
if (ymdset.has(k)) { base[k].dateElementOrder = 'ymd'; continue; }
if (mdyset.has(k)) { base[k].dateElementOrder = 'mdy'; continue; }
base[k].dateElementOrder = 'dmy';
}
for (const k in custom) { custom[k] = {...base[k], ...custom[k]}; }
const intlLocales = [
'af', 'ar', 'az', 'be', 'bg', 'bs', 'ca', 'cs', 'da', 'de', 'dv', 'el', 'en', 'es',
'et', 'eu', 'fa', 'fi', 'fo', 'fr', 'gu', 'he', 'hi', 'hr', 'hu', 'hy', 'id', 'it',
'ja', 'kn', 'ko', 'lt', 'lv', 'mr', 'ms', 'nb', 'nl', 'pa', 'pl', 'pt', 'ro', 'ru',
'sk', 'sl', 'sr', 'sv', 'sw', 'ta', 'te', 'th', 'tr', 'uk', 'ur', 'uz', 'vi', 'zh',
'sr-Cyrl-CS', 'sr-Latn-BA', 'sr-Latn-CS', 'cy-GB'
];
const supportedLocales = Object.keys(base).filter((k) => (k !== '_'))
const [ SUNDAY_OPTION, SUNDAY_OPTION_SHORT ] = [ {weekday:'long', timeZone}, {weekday:'short', timeZone} ];
const [ JANUARY_OPTION, JANUARY_OPTION_SHORT ] = [ {month:'long', timeZone}, {month:'short', timeZone} ];
const o = {};
const addDateConstant = (i: number, l: string, type: 'day'|'month' = 'day') => {
const d = new Date(type === 'day' ? Date.UTC(0, 0, i, 0, 0, 0) : Date.UTC(0, i, 0, 0, 0, 0));
let [r, rShort] = ['', ''];
try { r = new Intl.DateTimeFormat(l, (type === 'day' ? SUNDAY_OPTION : JANUARY_OPTION)).formatToParts(d)[0].value } catch(e) {}
try { rShort = new Intl.DateTimeFormat(l, (type === 'day' ? SUNDAY_OPTION_SHORT : JANUARY_OPTION_SHORT)).formatToParts(d)[0].value } catch(e) {}
// console.log( l, day );
o[l][type].push(r);
o[l][`${type}Short`].push(rShort);
if (type === 'day') o[l].dayChar.push(r.charAt(0));
}
const reduceMonthDigit = (r, m, i) => { r[m] = (i+1); return r; }
for (const l of intlLocales) {
o[l] = { day: [], dayShort: [], dayChar: [], month: [], monthShort: [] };
for (let i = 0; i <= 6; i++) { addDateConstant(i, l); }
}
for (const l of intlLocales) {
for (let i = 1; i <= 12; i++) { addDateConstant(i, l, 'month'); }
}
//console.log(JSON.stringify(o));
// af_ZA: {name:'af-ZA',englishName:'Afrikaans (South Africa)',nativeName
const intlBase = {};
const EN_NAME = new Intl.DisplayNames('en', { type: "language" });
for (const id of supportedLocales) {
const dayPeriods = {};
try {
const LOC_PERIOD1 = new Intl.DateTimeFormat(id, { dayPeriod: "short" });
const LOC_PERIOD2 = new Intl.DateTimeFormat(id, { dayPeriod: "long" });
const [nameEn, nameNative] = [EN_NAME.of(id), (new Intl.DisplayNames(id, { type: "language" })).of(id)];
for (const [i,k] of [[8,'morning'],[11,'noon'],[15,'afternoon'],[18,'evening'],[22,'night']]) {
const date = new Date(Date.UTC(0, 0, 0, i, 0, 0) );
dayPeriods[k] = Array.from(new Set([
LOC_PERIOD1.formatToParts(date)[0]?.value,
LOC_PERIOD2.formatToParts(date)[0]?.value
].filter((s) => s)));
}
intlBase[id] = { id, nameEn, nameNative, dayPeriods };
} catch(e) { }
}
for (const k in intlBase) {
let intlO = (Object.hasOwnProperty.call(o, k) ? o[k] : o[(k.split('-')[0])]||custom[k]);
if (!intlO) { intlO = o.en; }
intlBase[k] = {
...intlBase[k], ...base[k], ...intlO, ...localeFormats[k],
regexPattern: {
shortMeridian:/^(a|p)/i,
longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,
timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,
ordinalSuffix:/^\s*(st|nd|rd|th)/i,
timeContext:/^\s*(\:|a(?!u|p)|p)/i
}
};
const fallback = intlBase['en-US'];
['jan','feb','mar','apr','jun','jul','aug','sep','oct','nov','dec'].forEach((m, i) => {
if (!intlBase[k]?.month) { intlBase[k].month = fallback.month; intlBase[k].monthShort = fallback.monthShort; }
//console.log(k, intlBase[k])
const { month, monthShort } = intlBase[k];
const mEqual = month.join('') === monthShort.join('');
const shortmonth = (monthShort && monthShort[i]) ? monthShort[i] : month[i].slice(0,3);
intlBase[k].regexPattern[m] = shortmonth !== month[i]
? new RegExp(`^(?:${shortmonth}|${month[i]})`, 'i')
: new RegExp(`^(?:${shortmonth})`, 'i');
intlBase[k].monthDigit = month.reduce(reduceMonthDigit, {});
if (!mEqual) { intlBase[k].monthDigit = {...intlBase[k].monthDigit, ...monthShort.reduce(reduceMonthDigit, {})} }
});
['sun', 'mon','tue','wed','thu','fri','sat'].forEach((d, i) => {
if (!intlBase[k]?.day) { intlBase[k].day = fallback.day; intlBase[k].dayShort = fallback.dayShort; intlBase[k].dayChar = fallback.dayChar; }
const { day, dayShort, dayChar } = intlBase[k];
intlBase[k].regexPattern[d] = dayShort[i] !== day[i]
? new RegExp(`^((?:${dayShort[i]}[.]?)|${day[i]}|(?:${dayChar[i]}[.]?))`, 'i')
: new RegExp(`^((?:${dayShort[i]}[.]?)|(?:${dayChar[i]}[.]?))`, 'i')
});
}
const res = {};
const unit = ["second", "minute", "hour", "day", "week", "month", "quarter", "year"];
const mapR = (s) => `(?:${s.replace(/[.]/g, '[.]').replace(/\d/, '\\d*')})`;
for (const l of supportedLocales) {
res[l] = {};
const rtf1 = new Intl.RelativeTimeFormat(l, { style: "long" });
const rtf2 = new Intl.RelativeTimeFormat(l, { style: "short" });
const rtf3 = new Intl.RelativeTimeFormat(l, { style: "narrow" });
for (const u of unit) {
const [prev, prev2, next, next2] = [rtf1.format(-2,u),rtf1.format(-1,u),rtf1.format(1,u),rtf1.format(2,u)];
const [_prev, _prev2, _next, _next2] = [rtf2.format(-2,u),rtf2.format(-1,u),rtf2.format(1,u),rtf2.format(2,u)];
const [__prev, __prev2, __next, __next2] = [rtf3.format(-2,u),rtf3.format(-1,u),rtf3.format(1,u),rtf3.format(2,u)];
res[l][u] = {
substract: Array.from(new Set([prev, prev2, _prev, _prev2, __prev, __prev2 ])).map(mapR).join('|'),
add: Array.from(new Set([next, next2, _next, _next2, __next, __next2 ])).map(mapR).join('|')
};
}
}
for (const k in intlBase) {
intlBase[k].regexPattern.units = res[k]||{};
const extO = (Object.hasOwnProperty.call(extend, k) ? extend[k] : extend[(k.split('-')[0])]) || extend.en;
if (extO?.regexPattern) { intlBase[k].regexPattern = {...intlBase[k].regexPattern, ...extO.regexPattern}; }
if (extO?.monthDigit) { intlBase[k].monthDigit = {...extO.monthDigit, ...intlBase[k].monthDigit}; }
}
// TODO regexPattern import
export const intlDate = intlBase;
// console.log(JSON.stringify(intlDate));
//console.log(intlDate['de-DE']);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment