Created
September 7, 2025 08:59
-
-
Save sebilasse/e748d5869396b2512f22dc2a83c241a0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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