Skip to content

Instantly share code, notes, and snippets.

@sebilasse
Last active October 22, 2025 02:03
Show Gist options
  • Save sebilasse/77ab52bb4ad82a9665f910a174c6ad90 to your computer and use it in GitHub Desktop.
Save sebilasse/77ab52bb4ad82a9665f910a174c6ad90 to your computer and use it in GitHub Desktop.
IntlToActivitypub - for reference - converts JS Intl to Regexes which are used in the ActivityPub Parser
// see below
import { intlLocales, cardinalSuffix, isS, isNr, ok, calcTime, toTimeInt } from './shared.ts';
import { custom, dateCasual, dateCasualKeys } from './local.ts';
const IS_SERVER = (typeof document === 'undefined');
let m49regionMissing; let m49regionMap;
if (IS_SERVER) {
m49regionMissing = (await import('./m49.ts')).m49regionMissing;
m49regionMap = (await import('./m49.ts')).m49regionMap;
}
export const alias = {
GB: ['UK', 'United Kingdom'],
Edmonton: ['Alberta'],
Dawson: ['Yukon'],
Winnipeg: ['Manitoba'],
Toronto: ['Montreal','Ottawa'],
Edmonton: ['Calgary','Banff'],
Moncton: ['New Brunswick','Nova Scotia', 'Prince Edward Island'],
'St Johns': ['Newfoundland'],
McMurdo: ['Amundsen–Scott', 'South Pole'],
'Rio Branco': ['Acre'],
'Punta Arenas': ['Magallanes','Chilean Antarctica'],
Easter: ['Easter Island'],
Jakarta: ['Sumatra', 'Java', 'Madura'],
Jayapura: ['Western New Guinea ', 'Maluku'],
Makassar: ['Sulawesi', 'Bali'],
Pontianak: ['Borneo'],
Guayaquil: ['mainland'],
Galapagos: ['Galápagos'],
Detroit: ['DC', 'Washington D.C.', 'Washington DC'],
Moscow: ['St. Petersburg']
};
export const capitalAlias = {
AU: 'Canberra', BR: 'Brasilia', CA: 'Ottawa', EC: 'Quito', FM: 'Palikir', NZ: 'Wellington', US: 'Washington, D.C.',
AE: "Abu Dhabi", AG: "Saint John's", AI: "The Valley", AW: "Oranjestad", BB: "Bridgetown", BH: "Manama", BI: "Gitega",
BJ: "Porto-Novo", BL: "Gustavia", BM: "Hamilton", BN: "Bandar Seri Begawan", BO: "Sucre", BZ: "Belmopan", CC: "West Island",
CH: "Bern", CI: "Yamoussoukro", CK: "Avarua", CM: "Yaoundé", CN: "Beijing", CR: "San José", CV: "Praia", CW: "Willemstad",
CX: "Flying Fish Cove", DM: "Roseau", FJ: "Suva", FO: "Tórshavn", GD: "St. George's", GG: "St. Peter Port", GL: "Nuuk",
GP: "Basse-Terre", GS: "King Edward Point", GU: "Hagåtña", GY: "Georgetown", IM: "Douglas", IN: "New Delhi",
IO: "Diego Garcia", JE: "Saint Helier", JM: "Kingston", KM: "Moroni", KN: "Basseterre", KY: "George Town", KZ: "Astana",
LC: "Castries", MA: "Rabat", MD: "Chișinău", MM: "Naypyidaw", MN: "Ulan Bator", MO: '-', MQ: "Fort-de-France", MS: "Plymouth",
MT: "Valletta", MU: "Port Louis", MV: "Malé", MW: "Lilongwe", NC: "Nouméa", NF: "Kingston", NG: "Abuja", NP: "Kathmandu",
NR: "Yaren", NU: "Alofi", PF: "Papeetē", PK: "Islamabad", PM: "Saint-Pierre", PN: "Adamstown", PR: "San Juan", PS: "Ramallah",
PW: "Ngerulmud", PY: "Asunción", QA: "Doha", RE: "Saint-Denis", SB: "Honiara", SC: "Victoria", SH: "Jamestown", SM: "City of San Marino",
SV: "San Salvador", SX: "Philipsburg", SZ: "Lobamba", TC: "Cockburn Town", TD: "N'Djamena", TF: "Port-aux-Français", TG: "Lomé",
TO: "Nuku'alofa", TR: "Ankara", TT: "Port of Spain", TZ: "Dodoma", UA: "Kyiv", UM: '-', VA: "Vatican City", VC: "Kingstown",
VG: "Road Town", VI: "Charlotte Amalie", VN: "Hanoi", VU: "Port Vila", WF: "Mata-Utu", YE: "Sana'a", YT: "Mamoudzou", ZA: "Pretoria"
};
export const countryOlson = [
/* always multiple tz, can have an [] with region aliases */
['AQ',['Casey','Dav','Dum','Maw','Mc','Palmr','Rot','Syo','Tro','Vos','Riy','Sin','Au','Porty'],1],
['AU',['Syd','Adele','Bri','Bro','Darwn','Eu','Hob','Lin','Lor','Mel','Per','Macqe','Tok',['HM','CC','CX','NF']],1],
['BR',['Sao o','Rio o','Ara','Bahia','Belem','Boa','Campe','Cui','Ei','Forta','Maceo','Manas','Noroa','Porto','Rec','Santm'],1],
['CA',[
'Toroo','Van','Ed','Hal','Camby','Corar','Cr','Dawsn','Dawsk','Fortn','Gl','Goo','Inu','Iq','Moncn',
'Pan','Pho','Pue','Rankt','Reg','Res','St Js','Sw','Wh','Winng'
],1],
['CD',['Kin','Lag','Lub','Map'],1],
['CL',['Santiago','Pun','Ea'],1],
['DK',['Cop','Berln',['GL','FO']],1],
['EC',['Guayl','Gal'],1],
['ES',['Madrd','Ceu','Canay'],1],
['FM',['Ponae','Guadl','Ko','Porty','Tru'],1],
['FR',['Paris',['BL','GP','MF','MQ','GF','PM','YT','RE','TF','NC','WF']],1],
['GB',['Londn',['GG','IM','JE','GI','IO','PN','KY','TC','AI','BM','MS','VG','FK','GS']],1],
['ID',['Jak','Jay','Mak','Pontk'],1],
['KI',['Tar','En','Kirii'],1],
['MN',['Ula','Cho','Hov'],1],
['MX',['Mex','Bahis','Cancn','Chiha','Ci','Her','Mat','Maz','Mer','Monty','Oj','Tij'],1],
['NL',['Ams','Bruss',['BQ','AW','CW','SX']],1],
['NZ',['Au','Chatm',['NU','CK','TK']],1],
['PG',['Porty','Bou'],1],
['PT',['Lis','Az','Madea'],1],
['RU',[
'Mos','Samaa','Yek','Om','Krask','Ir','Yakuk','Vl','Mag','Kamca','Ana','Barnl','Chita','Khana',
'Novokuznetsk','Novosibirsk','Sak','Sr','Tom','Ast','Kal','Kirov','Sarav','Sim','Uly','Vol'
],1],
['US',[
'Det','Ada','Anc','Boi','Chico','Den','Ind','Jun','Los','Lou','Menoe','Met','New k','Nom','Pho','Sit','Yakut','Honou',
['GU', 'MP', 'AS', 'PR', 'VI']
],1],
/* usually single tz */
['AD',['And']], ['AE',['Dubai']], ['AF',['Kab']], ['AG',['Antia','Pue']], ['AI',['Ang','Pue']], ['AL',['Tir']], ['AM',['Yer']],
['AO',['Lua','Lag']], ['AR',['Bue','Cat','Corda','Juj','Menda']], ['AS',['Pag']], ['AT',['Viena']], ['AW',['Aru','Pue']],
['AX',['Marin','Hel']], ['AZ',['Bak']], ['BA',['Sarao','Belge']], ['BB',['Barbs']], ['BD',['Dh']], ['BE',['Bruss']],
['BF',['Ou','Ab']], ['BG',['Sof']], ['BH',['Bahrn','Qa']], ['BI',['Buj','Map']], ['BJ',['Lag']], ['BL',['Pue','St By']],
['BM',['Berma']], ['CV',['Cap']], ['BN',['Bruni','Kuc']], ['BO',['La Pz']], ['PE',['Lim']], ['BQ',['Kralk','Pue']],
['BS',['Nas','Toroo']], ['BT',['Thi']], ['BW',['Gab','Map']], ['BY',['Min']], ['BZ',['Belie']], ['CI',['Ab']], ['CC',['Coc']],
['CF',['Bangi','Lag']], ['CG',['Braze','Lag']], ['CH',['Zu']], ['CK',['Rar']], ['CM',['Dou','Lag']], ['CN',['Sh','Ur']],
['CO',['Bog']], ['CR',['Cos']], ['CU',['Hav']], ['CW',['Cur','Pue']], ['CX',['Bangk','Chr']], ['CY',['Nic','Fam']], ['CZ',['Pr']],
['DE',['Berln','Bus','Zu']], ['DJ',['Dj','Nai']], ['DM',['Dom','Pue']], ['DO',['Santo Domingo']], ['DZ',['Alg']], ['EE',['Tal']],
['EG',['Cai']], ['EH',['El An']], ['ER',['Asm','Nai']], ['ET',['Add','Nai']], ['FI',['Hel']], ['FJ',['Fi']], ['FO',['Fae']],
['FK',['Sta']], ['GA',['Lib','Lag']], ['GD',['Gre','Pue']], ['GE',['Tb']], ['GF',['Cayee']], ['GG',['Gue','Londn']],
['GH',['Ac','Ab']], ['GI',['Gi']], ['GL',['Dan','God','Sc','Thu']], ['GM',['Banjl','Ab']], ['GN',['Con','Ab']], ['GP',['Guade','Pue']],
['GQ',['Malao','Lag']], ['GR',['Ath']], ['GS',['Sou']], ['GT',['Guata']], ['GW',['Bissu']], ['GU',['Guamm']], ['GY',['Guy']],
['HK',['Hongg']], ['HN',['Teg']], ['HR',['Za','Belge']], ['HU',['Bud']], ['IE',['Dubln']], ['IL',['Jerum']], ['IM',['Isl','Londn']],
['IN',['Cal']], ['IO',['Chags']], ['IQ',['Bag']], ['IR',['Teh']], ['IS',['Rey','Ab']], ['IT',['Rom']], ['JE',['Jersy','Londn']],
['JM',['Jam']], ['JO',['Amm']], ['JP',['Tok']], ['KE',['Nai']], ['KG',['Bishk']], ['KH',['Phn','Bangk']], ['KM',['Nai','Com']],
['KN',['Pue','St Ks']], ['KY',['Caymn','Pan']], ['KP',['Py']], ['KR',['Se']], ['KW',['Kuw','Riy']],
['KZ',['Alm','Aqtau','Aqtoe','Aty','Or','Qo','Qy']], ['LA',['Viene','Bangk']], ['LB',['Bei']], ['LC',['Pue','St La']], ['LI',['Vad','Zu']],
['LK',['Col']], ['LR',['Monra']], ['LS',['Mas','Jo']], ['LT',['Vil']], ['LU',['Lux','Bruss']], ['LV',['Rig']], ['LY',['Tri']],
['MA',['Casaa']], ['MC',['Monao','Paris']],['MD',['Chisu']], ['ME',['Pod','Belge']], ['MF',['Marit','Pue']], ['MG',['Antao','Nai']],
['MH',['Maj','Kw','Tar']], ['MK',['Sk','Belge']], ['ML',['Bam','Ab']], ['MM',['Rangn']], ['MO',['Macau']], ['MP',['Saipn','Guamm']],
['MR',['Nouat','Ab']], ['MQ',['Marte']], ['MS',['Montt','Pue']], ['MT',['Malta']], ['MU',['Mau']], ['MV',['Malds']], ['MW',['Blane','Map']],
['MY',['Kua','Kuc','Sin']], ['MZ',['Map']], ['NA',['Windk']], ['NC',['Nouma']], ['NE',['Nia','Lag']], ['NF',['Norfk']], ['NG',['Lag']],
['NI',['Manaa']], ['NO',['Os','Berln']], ['NP',['Kat']], ['NR',['Nau']], ['NU',['Niu']], ['OM',['Mu','Dubai']], ['PA',['Pan']],
['PF',['Gam','Marqs','Tah']], ['PH',['Mania']], ['PK',['Kar']], ['PL',['War']], ['PM',['Miq']], ['PN',['Pi']], ['PR',['Pue']],
['PS',['Gaz','Heb']], ['PW',['Palau']], ['PY',['Asu']], ['QA',['Qa']], ['RO',['Buc']], ['RE',['Dubai','Reu']], ['RS',['Belge']],
['RW',['Kig','Map']], ['SA',['Riy']], ['SB',['Guadl']], ['SC',['Dubai','Mah']], ['SD',['Kharm']], ['SE',['Sto','Berln']],
['SG',['Sin']], ['SH',['Ab','St Ha']], ['SI',['Lj','Belge']], ['SJ',['Longn','Berln']], ['SK',['Brata','Pr']], ['SL',['Fr','Ab']],
['SM',['Rom','San o']], ['SN',['Dak','Ab']], ['SO',['Mog','Nai']], ['SR',['Parao']], ['SS',['Jub']], ['ST',['Sao e']], ['SV',['El Sr']],
['SX',['Low','Pue']], ['SY',['Dam']], ['SZ',['Jo','Mb']], ['TC',['Gra']], ['TD',['Nd']], ['TF',['Dubai','Ke','Malds']], ['TH',['Bangk']],
['TG',['Ab','Lom']], ['TJ',['Dus']], ['TK',['Fak']], ['TL',['Di']], ['TM',['Ash']], ['TN',['Tun']], ['TO',['Ton']], ['TR',['Ist']],
['TT',['Portn','Pue']], ['TV',['Fu','Tar']], ['TW',['Tai']], ['TZ',['Dar m','Nai']], ['UA',['Kie','Sim']], ['UG',['Kampa','Nai']],
['UM',['Mid','Pag','Tar','Wak']], ['UY',['Monto']], ['UZ',['Tas','Samad']], ['VA',['Rom','Vat']], ['VC',['Pue','St Vt']],
['VE',['Car']], ['VG',['Pue','Torta']], ['VI',['Pue','St Ts']], ['VN',['Bangk','Saign']], ['VU',['Ef']], ['WF',['Tar','Wal']],
['WS',['Ap']], ['YE',['Adenn','Riy']], ['YT',['Nai','May']], ['ZM',['Lus','Map']], ['ZW',['Har','Map']], ['ZA',['Jo']]
];
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 timeZone = 'Europe/Berlin';
export const [ SUNDAY_OPTION, SUNDAY_OPTION_SHORT ] = [ {weekday:'long', timeZone}, {weekday:'short', timeZone} ];
export const [ JANUARY_OPTION, JANUARY_OPTION_SHORT ] = [ {month:'long', timeZone}, {month:'short', timeZone} ];
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';
}
export const supportedLocales = Object.keys(base).filter((k) => (k !== '_'));
const countryLocalesCustom = {
AD: ['ca','fr-AD'], AE: ['ar-AE'], AF: ['fa-AF', 'ps', 'ug', 'uz-AF'], AG: ['en-AG'], AI: ['en-AI'],
AO: ['pt-AO', 'kg', 'kj'], AR: ['es-AR', 'cy-AR', 'gn'], AW: ['nl-AW', 'en'], BD: ['bn-BD'],
BE: ['nl-BE', 'fr-BE', 'de-BE', 'en', 'wa', 'yi'], BH: ['ar-BH'], BI: ['fr-BI','rn'], BJ: ['fr-BJ'],
BN: ['ms-BN', 'en-BN'], BO: ['es-BO', 'ay'], BS: ['en-BS'], BT: ['dz', 'ne'], BW: ['tn-BW', 'sn', 'en-BW'],
BZ: ['en-BZ'], CA: ['fr-CA', 'en-CA', 'cr', 'iu', 'oj', 'yi' ], CC: ['ms-CC'], CF: ['fr-CF', 'sg'], CI: ['fr-CI', 'ak', 'bm'],
CM: ['fr-CM', 'en-CM'], CR: ['es-CR'], CU: ['es-CU'], CV: ['pt-CV', 'en'], DJ: ['fr-DJ', 'so-DJ', 'aa-DJ'],
DM: ['en-DM'], DZ: ['ar-DZ'], EC: ['es-EC', 'es'], EG: ['ar-EG'], ER: ['aa-ER', 'tig', 'ti-ER', 'byn', 'en-ER', 'gez-ER'],
ET: ['am', 'ti-ET', 'om-ET', 'so-ET', 'en-ET', 'gez-ET', 'sid', 'wal', 'aa-ET' ], FJ: ['fj', 'en-FJ'], GA: ['fr-GA'], GD: ['en-GD'],
GE: ['ka', 'ab', 'os'], GF: ['fr-GF'], GH: ['ak', 'ee', 'tw', 'en-GH'], GI: ['en-GI'], GL: ['kl', 'da-GL'], GM: ['wo', 'bm', 'en-GM'],
GN: ['fr-GN', 'fr', 'en'], GP: ['fr-GP', 'fr', 'en'], GQ: ['es-GQ', 'es', 'en'], GU: ['ch-GU', 'en-GU'], GW: ['pt-GW', 'en'],
GY: ['en-GY'], IE: ['en-IE', 'ga-IE', 'ga'], IQ: ['ar-IQ', 'ku'], JM: ['en-JM'], JO: ['ar-JO'], KG: ['ky', 'ug'], KM: ['fr-KM'],
KN: ['en-KN'], KP: ['ko-KP'], KW: ['ar-KW'], KZ: ['kk', 'av', 'os', 'ug'], KY: [ 'en-KY' ], LA: ['lo', 'en'], LB: ['ar-LB', 'fr-LB'],
LC: ['en-LC'], LI: ['de-LI'], LR: ['en-LR'], LS: ['st', 'xh', 'en-LS'], LU: ['lb', 'fr-LU', 'de-LU'], LY: ['ar-LY'], MA: ['ar-MA'],
MC: ['fr-MC'], MD: ['mo', 'ro', 'tr', 'uk', 'yi'], MG: ['mg', 'fr-MG'], MH: ['mh', 'en-MH'], MK: ['mk', 'cu'], ML: ['fr-ML', 'bm'],
MM: ['my'], MO: ['zh-MO', 'zh-Hant'], MP: ['ch-MP','en-MP' ], MQ: ['fr-MQ'], MR: ['ar-MR', 'wo'], MS: ['en-MS'], MT: ['mt', 'en-MT'],
MU: ['en-MU'], MV: ['dv'], MW: ['ny', 'en-MW'], MZ: ['pt-MZ', 'sn'], NA: ['hz', 'ng', 'en-NA'], NC: ['fr-NC'],
NE: ['fr-NE', 'ha', 'ff-NE', 'kr'], NG: ['en-NG', 'ha', 'yo', 'ig', 'ff-NG', 'kr'], NI: ['es-NI'], NP: ['ne'], NR: ['na', 'en-NR'],
NU: ['en-NU'], OM: ['ar-OM'], PA: ['es-PA'], PF: ['fr-PF', 'ty'], PM: ['fr-PM'], PR: ['es-PR', 'en-PR'], PS: ['ar-PS'], PW: ['en-PW'],
PY: ['es-PY', 'gn'], QA: ['ar-QA'], RE: ['fr-RE'], RW: ['rw', 'fr-RW', 'en-RW'], SA: ['ar-SA'], SD: ['ar-SD', 'din', 'ha'],
SI: ['hu-SI', 'it-SI'], SL: ['en-SL'], SM: ['it-SM'], SN: ['wo', 'ff-SN'], SO: ['so-SO', 'ar-SO', 'en-SO'], SR: ['nl-SR', 'jv'],
ST: ['pt-ST'], SV: ['es-SV'], SY: ['ar-SY', 'syr'], SZ: ['ss-SZ','en-SZ'], TC: ['en-TC'], TD: ['fr-TD', 'ar-TD'],
TG: ['fr-TG', 'ee', 'ha'], TJ: ['tg', 'os', 'ug'], TK: ['en-TK'], TL: ['pt-TL'], TM: ['tk', 'os'], TN: ['ar-TN'], TO: ['to', 'en-TO'],
TT: ['en-TT'], TV: ['tvl', 'gil'], TZ: ['sw-TZ', 'en'], UG: ['en-UG', 'lg'], UM: ['en-UM'], UY: ['es-UY'],
UZ: ['uz-UZ', 'uz-Cyrl', 'uz-Latn', 'os', 'ug'], VA: ['it', 'la', 'fr'], VC: ['en-VC'], VE: ['es-VE'], VI: ['en-VI'],
VU: ['bi', 'en-VU', 'fr-VU'], WF: ['fr-WF'], WS: ['sm','en-WS'], YE: ['ar-YE'], YT: ['fr-YT'], ZM: ['en-ZM'],
ZW: ['sn', 'en-ZW', 'nd', 've', 'zu']
};
const countryLocalesAdd = {
AU: ['yi'], CL: ['ay'], ES: ['ca', 'eu', 'an', 'gl'], FM: ['en-FM'],
FR: ['fr-FR', 'br', 'co', 'de-FR', 'oc'], GB: ['gd', 'fr-GB', 'ga-GB', 'gv', 'kw'],
ID: ['jv', 'su'], 'KI': ['en-KI'], AT: ['hu'], AZ: ['az-Arab', 'az-Cyrl', 'az-Latn', 'av', 'os'],
BA: ['bs', 'sr-BA'], BF: ['fr-BF', 'bm', 'ha'], BG: ['tr-BG', 'cu'], BQ: ['nl'],
BY: ['be', 'cu', 'yi'], CG: ['fr-CG', 'ln-CG', 'kg'], CH: ['rm'],
CN: ['zh-Hans','zh-guoyu','zh-yue','zh-yue','zh-wuu','zh-xiang','zh-gan','zh-hakka','i-hak','ii','za','bo'],
CY: ['el-CY', 'tr-CY'], DE: ['nds', 'da-DE', 'da-DE', 'dsb', 'fy-DE', 'hsb', 'lb', 'wen', 'yi'],
EE: ['yi'], FI: ['sv-FI', 'smn'], FO: ['da-FO'], HR: ['it-HR', 'it'], HU: ['de-HU', 'sk-HU', 'sr-HU'],
IL: ['he', 'ar-IL', 'en-IL', 'yi'],
IN: ['hi', 'gu', 'ks', 'ml', 'mr', 'or', 'te', 'kn', 'as', 'sa', 'bh', 'kok', 'ne', 'pa', 'pi'],
IT: ['de-IT', 'fr-IT', 'co', 'sc'], KE: ['en-KE', 'ki', 'om-KE', 'so-KE'], KH: ['km'], LK: ['si'],
LT: ['yi'], LV: ['yi'], PH: ['tl'], PL: ['de-PL', 'yi'], RO: ['hu', 'cu', 'yi'], SC: ['fr-SC'],
SE: ['sv-SE', 'sma', 'sme', 'fi-SE'], SK: ['hu'], TH: ['si'], TR: ['ku', 'ab', 'av', 'ug'],
UA: ['ru-UA', 'ro', 'pl', 'hu', 'ab', 'cu', 'os', 'yi']
}
const bcp = supportedLocales.reduce((r, s) => {
r.push(s); r.push(s.split('-')[0]); return r;
}, []);
const countryLocales = countryOlson.map(([iso]) => {
if (countryLocalesCustom[iso]) {
return [iso, countryLocalesCustom[iso]];
} else if (bcp.filter((k) => k === iso.toLowerCase()).length) {
return [iso, [iso.toLowerCase()]]
} else {
const locs = bcp.filter((k) => k.split('-')[1] === iso);
if (locs.length) { return [iso, [...locs, ...(countryLocalesAdd[iso] ?? [])]] }
return [iso, ['en', ...(countryLocalesAdd[iso] ?? [])]]
}
}).reduce((r, [iso, locales]) => { r[iso] = locales; return r; }, {});
const tzset = new Set(Intl.supportedValuesOf('timeZone'));
const a = Array.from(tzset);
const tzId = {};
const tzBase = a.reduce((r, s) => {
if (s.indexOf('/') < 0) { return r; }
const parts = s.split('/');
const locId = parts[parts.length-1];
const location = locId.replace(/[_-]/g, ' ');
const [k2,k3,k_] = [location.slice(0,2), location.slice(0,3), `${location.slice(0,4)}${s.slice(-1)}`];
let rK = null;
for (const k of [k2,k3]) {
if (a.filter((s2) => {
const parts2 = s2.split('/');
return parts2[parts2.length-1].startsWith(k);
}).length === 1) {
rK = k;
break;
}
}
tzId[rK ?? k_] = s;
tzId[location] = s;
return {...r, s, [locId]: s, [location]: s, ...(!alias[location] ? {} : alias[location].reduce((r2, s2) => ({...r2, [s2]: s}) ), {})};
}, {});
const nameMap_spokenLangFromIntl = (type, iso, locales = (IS_SERVER ? intlLocales : ['en', 'de', 'fr', 'pt', 'es'])) => {
const nameFormat = locales.map((l) => [[l, 'long'], [l, 'short'], [l, 'narrow']]).flat();
const nameMapBlank = [...locales].reduce((r,k) => ({...r, [k]: []}), {});
let nameMap = [...nameFormat].reduce((r, [locale, style]) => {
try {
const name = new Intl.DisplayNames([locale], { type, style });
if (!r[locale]) { r[locale] = []; }
r[locale].push(name.of(iso));
} catch(e) { }
return r;
}, nameMapBlank);
nameMap.und = [...nameMap.en];
const spokenLanguage = Object.hasOwnProperty.call(countryLocales, iso) ? countryLocales[iso] : [];
Array.from(new Set(spokenLanguage.map((l) => {
let locName = '';
try { locName = new Intl.DisplayNames([l], { type: 'region'}).of(iso); } catch(e) { }
return [l, locName];
}))).filter((s) => s).forEach(([l, locName]) => {
nameMap[l] = [locName];
nameMap.und.push(locName)
});
for (const k in nameMap) { nameMap[k] = Array.from(new Set(nameMap[k])); }
return {spokenLanguage, nameMap};
}
const dtu = {second:1, minute:1, hour:1, day:1, week:1, month:1, year:1};
const getStdNameIntl = (intlType, locales, _type = []) => {
const checkType = intlType === 'unit' ? 'dateTimeField' : intlType;
const [def, defName] = [{}, {}];
Intl.supportedValuesOf(intlType).forEach((id) => {
const { nameMap } = nameMap_spokenLangFromIntl(checkType, id, locales);
const contentMap = {};
if (intlType === 'unit') {
for (const l in nameMap) {
for (const unitDisplay of ['long','short','narrow']) {
const options = { style: 'unit', unit: id, unitDisplay };
const numberFormat = new Intl.NumberFormat(l, options);
const parts = [numberFormat.formatToParts(1), numberFormat.formatToParts(2)].flat().filter(({type}) => type==='unit').map(({value}) => value);
nameMap[l] = [...nameMap[l], ...(parts && parts.length ? parts : [])];
contentMap[l] = [...(contentMap[l] ?? []), (1).toLocaleString(l, options), (2).toLocaleString(l, options)];
}
nameMap[l] = Array.from(new Set(nameMap[l]));
contentMap[l] = Array.from(new Set(contentMap[l]));
}
} else if (intlType === 'timeZone') {
for (const l in nameMap) {
const d = new Date(Date.UTC(2020, 11, 20, 3, 0, 0, 0));
const tzNames = Array.from(new Set(['full', 'long'].map((timeStyle) =>
new Intl.DateTimeFormat(l, {timeStyle, timeZone: id}).formatToParts(d)
.filter(({type}) => type==='timeZoneName').map(({value}) => value)).flat()));
if (/^[A-Z][a-z]+[/][A-Z]/.test(id)) {
const parts = id.split('/');
const location = parts[parts.length-1];
nameMap[l].push(location);
nameMap[l].push(location.replace(/_/g, ' '));
}
nameMap[l] = Array.from(new Set([...nameMap[l], id, ...(tzNames.length ? tzNames : [])]));
}
}
const type = (dtu[id] ? [..._type, 'redaktor:DateTimeUnit'] : _type);
def[id] = { id, type, nameMap, ...(intlType === 'timeZone' ? {} : {contentMap}) };
defName[id] = id;
Array.from(new Set(Object.values(nameMap).flat())).forEach((k) => { defName[k] = id; });
});
return [def, defName];
}
// 001-096, // 100 (+4) [no 128] //142-145 //
const [weekday, monthname] = [
['sun', 'mon','tue','wed','thu','fri','sat'],
['jan','feb','mar','apr', 'may', 'jun','jul','aug','sep','oct','nov','dec']
];
const rtl = new Set(['Mero', 'Merc', 'Psin', 'Sarb', 'Narb', 'Chrs', 'Phnx', 'Lydi', 'Tfng',
'Samr', 'Armi', 'Hebr', 'Palm', 'Hatr', 'Elym', 'Prti', 'Phli', 'Phlp',
'Phlv', 'Avst', 'Syrc', 'Syrn', 'Syrj', 'Syre', 'Mani', 'Mand', 'Sogd',
'Sogo', 'Ougr', 'Mong', 'Nbat', 'Arab', 'Aran', 'Gara', 'Nkoo', 'Adlm',
'Rohg', 'Thaa', 'Orkh', 'Hung', 'Sidt', 'Yezi']);
export const intlToActivityPub = (_locales = (IS_SERVER ? intlLocales : ['en', 'de', 'fr', 'pt', 'es'])) => {
const locales = Array.from(new Set(['en', ..._locales]));
const [o, parser] = [{}, {}];
const localDateParser = { keys: ['localDate', 'monthDate', 'weekdayMonth', 'relative', 'casualTime', 'localTime'], r: {}, fn: {} }
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 locales) {
parser[l] = {};
o[l] = { day: [], dayShort: [], dayChar: [], month: [], monthShort: [] };
for (let i = 0; i <= 6; i++) { addDateConstant(i, l); }
for (let i = 1; i <= 12; i++) { addDateConstant(i, l, 'month'); }
}
const dayPeriod = {};
for (const [i, start, end, id] of [
[8,'07','11','morning'],[11,'11','15','noon'],[15,'15','18','afternoon'],
[18,'18','22','evening'],[2,'22','07','night']
]) {
const res = {}
const date = new Date(Date.UTC(0, 0, 0, i, 0, 0) );
dayPeriod[id] = {id, type: ['Event', 'redaktor:dayPeriod'], nameMap: {}, startTime: `${start}:00:00`, endTime: `${end}:00:00`};
for (const l of locales) {
const LOC_PERIOD1 = new Intl.DateTimeFormat(l, { dayPeriod: "short" });
const LOC_PERIOD2 = new Intl.DateTimeFormat(l, { dayPeriod: "long" });
dayPeriod[id].nameMap[l] = Array.from(new Set([
LOC_PERIOD1.formatToParts(date)[0]?.value,
LOC_PERIOD2.formatToParts(date)[0]?.value
].filter((s) => s)));
}
}
const [daysOfWeek, monthsOfYear] = [
weekday.map(((id) => ({id, type: ['redaktor:Weekday'], nameMap: {}}))),
monthname.map(((id) => ({id, type: ['redaktor:GregorianMonth'], nameMap: {}})))
];
const fallback = o['en-US'];
const toRK = (s) => (s.endsWith('.') || s.endsWith(','))
? s.slice(0, -1).replace(/(\W)/g, '[$1]')
: s.replace(/(\W)/g, '[$1]');
const toRegexString = (l, name, long, short?, char?, key = 'monthDigit') => {
const res = [];
long.forEach((r, i) => {
const rArr = !short || short[i] === r
? [toRK(r), `${toRK(name[i])}`]
: [toRK(r), `${toRK(short[i])}`, `${toRK(name[i])}`];
if (char) { rArr.push(`${toRK(char[i])}`); }
if (key && dateCasual?.r[l] && dateCasual.r[l][key]) {
for (const k in dateCasual.r[l][key]) { if (dateCasual.r[l][key][k] === i) { rArr.push(`${toRK(k)}`) } }
}
res.push(Array.from(new Set(rArr)));
});
return `(${res.map((a) => `(${a.join('|')})`).join('|')})`;
}
for (const l in o) {
const { day, dayShort, dayChar, month, monthShort } = o[l];
day.forEach((d, i) => {
if (!daysOfWeek[i].nameMap[l]) { daysOfWeek[i].nameMap[l] = []; }
daysOfWeek[i].nameMap[l].push(d);
});
dayShort.forEach((d, i) => { daysOfWeek[i].nameMap[l].push(d); });
dayChar.forEach((d, i) => {
daysOfWeek[i].nameMap[l].push(d);
daysOfWeek[i].nameMap[l].push(daysOfWeek[i].id);
});
month.forEach((m, i) => {
if (!monthsOfYear[i].nameMap[l]) { monthsOfYear[i].nameMap[l] = []; }
monthsOfYear[i].nameMap[l].push(m);
});
monthShort.forEach((m, i) => {
monthsOfYear[i].nameMap[l].push(m);
monthsOfYear[i].nameMap[l].push(monthsOfYear[i].id);
});
let r = toRegexString(l, weekday, day, dayShort, dayChar, 'dayDigit')
parser[l].dayOfWeek = { r, g: weekday, regex: new RegExp(`\\b(?:${r})\\b[.,]*`, 'i') };
if (l === 'fr') console.log(parser[l].dayOfWeek )
//[.]?
r = toRegexString(l, monthname, month, monthShort);
parser[l].monthOfYear = { r, g: monthname, regex: new RegExp(`\\b(?:${r})\\b[.,]*`, 'i') };
}
const dayOfWeek = daysOfWeek.reduce((r, o) => { r[o.id] = o; return r; }, {});
const monthOfYear = monthsOfYear.reduce((r, o) => { r[o.id] = o; return r; }, {});
const regionNameEn = new Intl.DisplayNames(['en'], { type: 'region', style: 'long' });
let type = ['redaktor:Country', 'redaktor:TimezoneLocation'];
const countryCache = {};
const [country, countryName] = [{}, {}];
countryOlson.forEach(([iso, ids, multiTz = false]) => {
countryCache[regionNameEn.of(iso)] = iso;
const timezone = ids.map((id) => Array.isArray(id) ? id : tzId[id]);
const {spokenLanguage, nameMap} = nameMap_spokenLangFromIntl('region', iso, locales);
const firstParts = timezone[0].split('/');
const firstLocation = firstParts[firstParts.length-1].replace(/[_]/g, ' ');
const capital = iso === 'AQ' ? '-' : (capitalAlias[iso] || firstLocation);
country[iso] = { type, id: iso, nameMap, capital, timezone, differentTimezones: !!multiTz, spokenLanguage };
countryName[iso] = iso;
if (capitalAlias[iso] && !tzBase[capitalAlias[iso]]) { tzBase[capitalAlias[iso]] = timezone[0]; }
Array.from(new Set(Object.values(nameMap).flat())).forEach((k) => { countryName[k] = iso; });
if (alias[iso]) { alias[iso].forEach((isoDup) => { if (!countryName[isoDup]) { countryName[isoDup] = iso; } }) }
});
const toParser = (asO, key) => {
const [g, c] = [Object.values(asO).map(({id}) => id), {}];
for (const l in o) {
try {
c[l] = Object.values(asO).map(({id, nameMap}) => {return [id, ...(nameMap[l]||nameMap[l.split('-')[0]||nameMap.en||{}])]});
} catch(e) { }
}
for (const l in c) {
const res = c[l].map((a) => {
const [iso, ..._names] = a;
const prefix = key === 'unit' ? '\\d' : '';
const isoR = key === 'timezone' ? `${prefix}(?:${toRK(iso)})` : `${prefix}(?:\\[?\\(?${iso}\\]?\\)?)`;
return `(${_names.map(toRK).map((k) => `${prefix}(?:${k})`).join('|')}|${isoR})`;
}).join('|')
parser[l][key] = {r: `(${res})`, g}
}
}
toParser(dayPeriod, 'dayPeriod');
toParser(country, 'country');
// console.log(country, countryName);
type = ['redaktor:Locale']; // TODO redaktor:Language (deu, eng) relations
const [language, languageName] = [{}, {}];
countryOlson.forEach(([iso]) => {
if (!country[iso]?.spokenLanguage) { return; }
for (const id of country[iso].spokenLanguage) {
try {
if (!language[id]) {
const locale = new Intl.Locale(id).maximize();
const summary = `Locale ${locale.toString()}`;
const { script } = locale;
const { nameMap } = nameMap_spokenLangFromIntl('language', id, locales);
language[id] = { id, type, nameMap, summary, country: [], script, direction: rtl.has(script) ? 'rtl' : 'ltr' }
}
language[id].country.push(iso);
} catch(e) {}
}
});
for (const id in language) {
languageName[id] = id;
Array.from(new Set(Object.values(language[id].nameMap).flat())).forEach((k) => { languageName[k] = id; });
}
toParser(language, 'language');
// console.log(language, languageName);
const [currency, currencyName] = getStdNameIntl('currency', locales, ['redaktor:Currency']);
const [unit, unitName] = getStdNameIntl('unit', locales, ['redaktor:Unit']);
const [timezone, timezoneName] = getStdNameIntl('timeZone', locales, ['redaktor:Timezone']);
// console.log(currency, currencyName);
// console.log(unit, unitName);
for (const id in timezone) {
timezone[id].country = Object.keys(country).filter((k) => {
const ctz = country[k]?.timezone ?? [];
if (ctz.indexOf(id) > -1) { return true; }
const [alias] = ctz.filter((a) => Array.isArray(a));
if (alias && alias.filter((cId) => (country[cId]?.timezone ?? []).indexOf(id) > -1)) { return true; }
return false;
});
}
for (const id in country) {
if (country[id]?.capital && country[id]?.timezone?.length) {
for (const l in timezone[country[id].timezone[0]].nameMap) {
timezone[country[id].timezone[0]].nameMap[l].push(country[id].capital);
}
}
}
// console.log(timezone, timezoneName);
toParser(currency, 'currency');
toParser(unit, 'unit');
toParser(timezone, 'timezone');
//console.log(unit);
// TODO ! deno is missing Intl UN M49 regions
// see ./m49.ts and https://github.com/denoland/deno/issues/13257
// TODO add from ./m49.ts if no '003' .name and deno
type = ['redaktor:Region'];
let [region, regionName] = [{}, {}]
const m49 = {};
for (let i = 1; i < 100; i++) {
const k = (i < 10 ? `00${i}` : `0${i}`);
const name = regionNameEn.of(k);
if (k !== name) { m49[k] = name; }
}
for (let i = 100; i < 900; i++) {
const name = regionNameEn.of(`${i}`);
if (`${i}` !== name) { m49[`${i}`] = name; }
}
for (const id in m49) {
const cK = countryCache[m49[id]];
if (cK) {
if (!country[cK].m49) { country[cK].m49 = []; }
country[cK].m49.push(id);
} else {
const { nameMap } = nameMap_spokenLangFromIntl('region', id, locales);
region[id] = { id, type, nameMap };
regionName[id] = id;
Array.from(new Set(Object.values(region[id].nameMap).flat())).forEach((k) => { regionName[k] = id; });
}
}
if (m49regionMissing) {
// currently deno-only (server), see https://github.com/denoland/deno/issues/13257 - TODO chromium?
region = {...region, ...m49regionMissing};
for (const id in region) {
regionName[id] = id;
Array.from(new Set(Object.values(region[id].nameMap).flat())).forEach((k) => { regionName[k] = id; });
}
for (const cK in country) {
if (!m49regionMap[cK]) { continue; }
if (!country[cK].m49) { country[cK].m49 = []; }
country[cK].m49.push(m49regionMap[cK]);
}
}
toParser(region, 'region');
/*
const testR = new RegExp(parser.de.monthOfYear.r, 'gi')
console.log(testR.exec('Am Samstag im Juli ist nachmittags ein 2 Minuten event bezahlt in USD aus Berlin'))
console.log('Am Samstag im Juli ist nachmittags ein 2 Minuten event bezahlt in USD aus Berlin'.split(testR))
*/
const dKeys = { year:1, month:1, day:1, weekday:1 };
const tKeys = { hour:1, minute:1, second:1, timeZoneName:1, dayPeriod:1 };
const dtMode = ['full', 'long', 'medium', 'short'];
const cardinalSuffix = '(?:\\s?(?:th|nd|rd|er?|ieme|te|-?й|ro|do|[.˚ ]))?';
const AM = '(?:a[.]?\\s?m[.]?)|(?:π[.]?\\s?μ[.]?)|PG|ಪೂರ್ವಾಹ್ನ|오전|[ص]';
const PM = '(?:p[.]?\\s?m[.]?)|(?:μ[.]?\\s?μ[.]?)|PTG|ಅಪರಾಹ್ನ|오후|[م]';
const AM_PM = `(?<am>${AM})|(?<pm>${PM})`;
const date = new Date(Date.UTC(1999, 7, 2, 15, 3, 4) );
const toLocalO = (r, f) => ({...r, [f[0].format(date)]: {parts: f[0].formatToParts(date), format: f[1]}});
const relative = {};
const unitRelative = ["second", "minute", "hour", "day", "week", "month", "quarter", "year"];
for (const l of locales) {
const [dLocal, tLocal] = [
dtMode.map((dateStyle) => [new Intl.DateTimeFormat(l, { dateStyle }), dateStyle]).reduce(toLocalO, {}),
dtMode.map((timeStyle) => [new Intl.DateTimeFormat(l, { timeStyle }), timeStyle]).reduce(toLocalO, {})
];
localDateParser.r[l] = {};
let minutes = [];
for (const unitDisplay of ['long','short','narrow']) {
const options = { style: 'unit', unit: 'minute', unitDisplay };
const numberFormat = new Intl.NumberFormat(l, options);
minutes = [...minutes, ...[numberFormat.formatToParts(2)].flat().filter(({type}) => type==='unit').map(({value}) =>
value.endsWith('.') ? value.slice(0, -1) : value)];
}
const minUnit = `(?:${Array.from(new Set(minutes)).join('|')})[.,]*`;
const hDigit = `(?:0?\\d)|(?:1\\d)|20|21|22|23|24`;
const adBc = `\\s?(?:(?<yBc>${(dateCasual?.r[l] ?? dateCasual.r.en).yearBC})|(?<yAd>${(dateCasual?.r[l] ?? dateCasual.r.en).yearAD}))?`;
const minSec = '(?:[0-5]\\d)|\\d';
const hasCasualParser = dateCasual?.r[l] ?? dateCasual?.r[l.split('-')[0]];
const W = (hasCasualParser ?? dateCasual.r.en);
const Cardinal = W.cardinal.map((c) => `${c}\\b`).join('|');
const {concatPrefix, concatTime = '[:]'} = W;
const [qPrefix, qSuffix] = [((hasCasualParser && W.timeQuarterPrefix) ?? ''), (hasCasualParser && W.timeQuarterSuffix) ?? ''];
localDateParser.r[l] = {
weekdayMonth: `\\b(?:${W.concatPrefix})?(?<past>${W.past})?(?<next>${W.next})?(?<each>${W.each})?\\s?(?<ordinal>${W.ordinal})?\\s?(?:(?<dayOfWeek>${
parser[l].dayOfWeek.r
})|(?<monthOfYear>${parser[l].monthOfYear.r}))(?<eachSuffix>${W.weekdayEachSuffix})?\\W?\\s?\\b`,
monthDate: `(?:(?:${W.concatPrefix})?\\s?(?:(?:(?<d>(?:[0-3]\\d)|[1-9]|(?<ordinal>${W.ordinal}))[., ]*(?<m>${
parser[l].monthOfYear.r
})\\s?)|(?:(?<m>${
parser[l].monthOfYear.r
})(?:${W.concatPrefix})?\\s?(?<d>(?:[0-3]\\d)|[1-9]|(?<ordinal>${
W.ordinal
})|(?<cardinal>${Cardinal}))[., ]*(?<y>\\d\\d?\\d?\\d?)?${adBc})))`
};
/*
if (l === 'es') {
console.log('ES ---- :');
console.log(new RegExp(localDateParser.r[l].monthDate, 'gi'), parser[l].dayOfWeek.r);
console.log('');
}
*/
localDateParser.r[l].localDate = `\\b(?:${W.concatPrefix})?${
Object.keys(dLocal).map((k) => `(?<${dLocal[k].format}>${dLocal[k].parts.map(({type, value}, i) => {
const isInt = !isNaN(parseInt(value, 10));
//
return type === 'year'
? (isInt ? `(?<y>\\d\\d?\\d?\\d?)?${adBc}` : `(?<y>${value})?${adBc}`)
: (type === 'month'
? `(?<m>${parser[l].monthOfYear.r}|10|11|12|(?:0?[1-9]))${cardinalSuffix}`
: (
type === 'day'
? `(?:${W.concatPrefix})?`+(isInt
? `(?<d>(?:[0-3]\\d)|[1-9])(?:\\w?\\w?\\w?\\s?)`
: `(?<d>.+\\b)`)+cardinalSuffix
: (type === 'literal' ? value.replace(/([\W])\s?/g, '(?:\\b|[$1])\\s?') : (type === 'weekday'
? parser[l].dayOfWeek.r+'?'
: (value ?? ''))
)
)
)
}).join('')})`
).join('|')}`;
localDateParser.r[l].localTime = `(?:${
Object.keys(tLocal).map((k) => `(?<${tLocal[k].format}>${tLocal[k].parts.map(({type, value}, i) => {
const isInt = !isNaN(parseInt(value, 10));
// if (l === 'de') console.log(W.cardinal)
return type === 'hour'
? `(?:(?<prefix>${concatPrefix})?\\s?${
qPrefix ? `(?<quarter>${qPrefix})?\\s?`: ''
}${isInt ? `(?:(?<h>(?:0?\\d)|(?:1\\d)|20|21|22|23|24)` : `(?<h>.*)`}${
hasCasualParser ? `|\\b(?<hCardinal>${W.cardinal.join('|')})\\b` : ''
}))\\b${
qSuffix ? `(?<quarter>${qSuffix})?`: ''
}`
: (type === 'minute'
? (isInt ? `(?<min>${minSec}\\b)?(?:${concatTime})?` : `(?<min>.*\\b)?(?:${concatTime})?`)
: (
type === 'second'
? (isInt ? `(?<sec>${minSec}\\b)?` : `(?<sec>.*\\b)?`)
: (type === 'literal'
? value.replace(/([\W])\s?/g, `(?:\\b|[$1]|${concatTime})\\s?`)
: (type === 'timeZoneName' ? '' : (type === 'dayPeriod' ? AM_PM : (value ?? ''))
)
)
)
)
}).join('')})`
).join('|')
})(?:${concatPrefix})?(?<timezone>\\s|${parser[l].timezone.r.slice(1)}`;
// TODO merge with above
localDateParser.r[l].casualTime = `\\b(?<prefix>${
W.concatPrefix
})?\\s?(?:(?:(?<minute_sub>${
minSec
}|(?:${Cardinal}))?(?: ${minUnit})* (?:${W.substract})\\s)|(?:(?<minute_add>${
minSec
}|(?:${Cardinal}))?(?: ${minUnit})* (?:${W.add})\\s))?\\s*(?<h>(?:(?:0?\\d)|(?:1\\d)|20|21|22|23|24|(?:${
Cardinal
}))(?:\\s|$))\\s*(?<suffix>${W.concatTime})?\\s*(?<_and>${W.and} )?(?<min>${
minSec
}|(?:${Cardinal}))?(?: ${minUnit})*\\b`;
if (l === 'de') console.log(localDateParser.r[l].casualTime);
// if (l === 'de') console.log(localDateParser.r[l].localTime)
relative[l] = [];
const names = new Intl.DisplayNames([l], { type: 'dateTimeField' });
const rtf1 = new Intl.RelativeTimeFormat(l, { style: "long" });
const rtf2 = new Intl.RelativeTimeFormat(l, { style: "short" });
const rtf3 = new Intl.RelativeTimeFormat(l, { style: "narrow" });
const mapRelative = (s) => `(?:${s.replace(/[.]/g, '[. ]').replace(/(\W)/g, '[$1]')
.replace(/\d/, `(?:(?<amount>\\d[\\d,.]*)|(?<few>${W.few})|(?<couple>${W.couple})|(?<several>${W.several
})|(?<cardinal>[a-z]+(?: [a-z]+)?))`)})\\b`;
for (const u of unitRelative) {
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)];
relative[l].push(`(?<${u}_sub>${
Array.from(new Set([prev, prev2, _prev, _prev2, __prev, __prev2])).map(mapRelative).join('|')
})`);
relative[l].push(`(?<${u}_add>${
Array.from(new Set([next, next2, _next, _next2, __next, __next2])).map(mapRelative).join('|')
})`);
/* + past/next like (next Monday) .map(pastNextMapper(...args)).flat().filter(ok)*/
}
localDateParser.r[l].relative = `(?:${relative[l].join('|')})`;
//if (l === 'de') {console.log(new RegExp('\\b'+localDateParser.r[l].relative+'\\b'))}
}
const RELATIVE = {
second: ((n)=>calcTime(n,'sec')), minute: ((n)=>calcTime(60,'sec')), hour: ((n)=>calcTime(60*n,'m')),
day: ((n)=>calcTime(24*n,'h')), week: ((n)=>calcTime(168*n,'h')), month: ((n)=>calcDate(30*n,'d')),
quarter: ((n)=>calcDate(90*n,'d')), year: ((n)=>calcDate(12*n,'m'))
};
const QUARTERS = [15, -15, 30, -30];
const R = {cardinal: {}, ordinal: {}};
const numericAmount = (s, l, type = 'cardinal') => {
if (!R[type][l]) {
const r = (dateCasual.r[l.split('-')[0]]||dateCasual.r.en)[type];
R[type][l] = new RegExp(`\\b(${type === 'ordinal' ? r : r.join('|')})\\b`, 'i');
}
const res = R[type][l].exec(s);
if (!res) return false;
//console.log(res)
const [m, _m, ...numbers] = res;
//console.log('--', l, s, s.match(R), m);
return (numbers.map((s, i) => !s ? s : i).filter(ok)[0]);
}
const getMonthFromResult = (res, m, l) => {
const match = parser[l].monthOfYear.regex.exec(m);
return !match ? NaN : match.slice(2).reduce((r, el, i) => ((r < 0 && el) ? i : r), -1);
}
const localDateFn = ({ type, start, end, result, source, groups, l }, baseDate) => {
// console.log('>', groups);
// console.log('>', localDateParser.r.de.localDate, new RegExp('\\b'+localDateParser.r.de.localDate).exec('19. August'));
if (typeof groups?.min === 'undefined' && typeof groups?.sec !== 'undefined') {
groups.min = groups.sec;
groups.sec = 0;
}
let {d, cardinal, ordinal, m, y, yBc, yAd, full, long, medium, quarter, hCardinal, h = 0, min = 0, sec = 0 } = groups;
if (m && cardinal) {
d = numericAmount(cardinal, l);
} else if (m && ordinal) {
d = numericAmount(ordinal, l, 'ordinal');
}
if (!d || !m) {
if (hCardinal || h) {
const { timezone = 'UTC' } = groups;
const tz = timezone === 'UTC' ? 'UTC' : timezoneName[timezone];
//console.log(source, l, !!hCardinal && !h, numericAmount(hCardinal, l))
if (!!hCardinal && !h) { h = numericAmount(hCardinal, l); }
if ((!isS(h) && !isNr(h)) || (isNr(h) && h > 24)) { return source; }
let [ hour, minute, second ] = [(h === '24' ? 0 : toTimeInt(h)), toTimeInt(min), toTimeInt(sec)];
if (quarter && !min) {
const { add15, sub15, sub30 } = groups;
if (sub15 || sub30) { hour = !hour ? 23 : (hour-1); }
minute = add15 ? 15 : (sub15 ? 45 : 30);
}
// TODO hCardinal
return {type: 'time', h: hour, min: minute, sec: second, tz, ...(hCardinal ? {hCardinal} : {}), start, end, source, l }
}
return source;
}
let [day, month, year] = [toTimeInt(d), toTimeInt(m)-1, baseDate.getFullYear()];
if (isS(y)) {
const Y = parseInt(y, 10);
if (!isNaN(Y)) { year = (yBc && Y > 0) ? 0-Y : Y; }
}
if (!isNr(month)) { month = getMonthFromResult(result, m, l); }
const mode = [(full ? 'localFull' : (long ? 'localLong' : (medium ? 'localMedium' : 'localShort')))];
const date = new Date(Date.UTC(year, month, day, h, min, sec));
if (isNaN(date)) { return source; }
return { type: 'date', mode, startTime: date.toISOString(), start, end, source, l };
}
// TODO dayPeriod instead time
/*
const tzStd = '((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)';
const TZ = {
EST: '-0500',
EDT: '-0400',
CST: '-0600',
CDT: '-0500',
MST: '-0700',
MDT: '-0600',
PST: '-0800',
PDT: '-0700'
};
*/
localDateParser.fn = {
localDate: localDateFn, localTime: localDateFn, monthDate: localDateFn,
casualTime: ({ type, start, end, result, source, groups, l }, d) => {
// TODO timezone
let { minute_sub, minute_add, prefix, suffix, _and, h, min = 0, sec = 0, timezone = 'UTC' } = groups;
if (!h || (!minute_sub && !minute_add && !prefix && !suffix && !_and)) { return source; }
const tz = timezone === 'UTC' ? 'UTC' : timezoneName[timezone];
let isWord = {h: false, min: false};
if (isNaN(parseInt(h, 10))) { isWord.h = h; h = numericAmount(h, l); }
if (isS(minute_sub) && isNaN(parseInt(minute_sub, 10))) { isWord.min = minute_sub; minute_sub = numericAmount(minute_sub, l); }
if (isS(minute_add) && isNaN(parseInt(minute_add, 10))) { isWord.min = minute_add; minute_add = numericAmount(minute_add, l); }
if (!isNr(h) || h > 24) { return source; }
if (minute_sub) {
h = !h ? 23 : (h-1);
min = 60-minute_sub;
} else if (minute_add) {
min = minute_add;
}
const cardinals = { ...(isWord.h ? {hCardinal: isWord.h} : {}), ...(isWord.min ? {minCardinal: isWord.min} : {}) };
return {type: 'time', h, min, sec, tz, ...cardinals, start, end, source, l }
},
weekdayMonth: ({ type, start, end, result, source, groups, l }, d) => {
// console.log('weekdayMonth',groups);
const baseD = new Date(d.getTime());
const { dayOfWeek, monthOfYear, eachSuffix, ordinal, each, past, next } = groups;
if (!isS(dayOfWeek) && !isS(monthOfYear)) { return source; }
let index;
let ordinalIndex = !isS(ordinal) ? false : result.slice(6).reduce((r, el, i) => ((r < 0 && el) ? i : r), -1);
if (dayOfWeek) {
// console.log(l, parser[l].dayOfWeek.regex, dayOfWeek);
const [dayIndex] = (parser[l].dayOfWeek.regex.exec(dayOfWeek)).slice(2).map((s, i) => !s ? s : i).filter(isNr);
if (!isNr(ordinalIndex)) {
d.setDate((d.getDate() + (dayIndex+(7-d.getDay())) % 7) - (past ? (d.getDay() === dayIndex ? 14 : 7) : 0));
//console.log('dayIndex', l, dayIndex, past, d.toISOString());
index = dayIndex;
} else if (!past && !next && ordinalIndex > 0 && ordinalIndex < 5) {
console.log('ordinalIndex',ordinalIndex);
d.setDate(1);
d.setDate((d.getDate() + (dayIndex+(7-d.getDay())) % 7));
if (ordinalIndex > 1) { d.setDate((d.getDate() + (ordinalIndex-1 * 7))) }
if (d.getTime() < baseD.getTime()) {
d.setDate(1);
d.setMonth(baseD.getMonth()+1);
d.setDate((d.getDate() + (dayIndex+(7-d.getDay())) % 7));
if (ordinalIndex > 1) { d.setDate((d.getDate() + (ordinalIndex-1 * 7))) }
}
index = dayIndex;
}
} else {
index = getMonthFromResult(result, monthOfYear, l);
console.log('!:', ordinalIndex, index);
if (!isNr(ordinalIndex)) {
console.log('ordinal a index', index);
if (past || next) {
d.setDate((d.getDate() + (index+(12-d.getMonth())) % 12) - (past ? (d.getMonth() === index ? 24 : 12) : 0));
} else {
d.setMonth(index); d.setDate(1);
}
} else if (ordinalIndex > 0 && ordinalIndex < 32) {
console.log('ordinalIndex',ordinalIndex);
d.setMonth(index);
d.setDate(ordinalIndex);
if (d.getTime() < baseD.getTime()) { d.setFullYear( baseD.getFullYear() + 1) }
}
}
if (!isNr(index) || isNaN(d)) { return source; }
//d.setHours(0); d.setMinutes(0); d.setSeconds(0);
const baseMode = `${dayOfWeek ? 'dayOfWeek' : 'monthOfYear'}${isNr(ordinalIndex) ? 'Ordinal' : ''}`;
const res = { type: 'date', value: dayOfWeek ?? monthOfYear, index, startTime: d.toISOString(), start, end, source, l };
if (isNr(ordinalIndex)) { res.ordinal = ordinalIndex; }
if ((eachSuffix || each)) {
return { ...res, mode: [baseMode, 'each'], cadence: (dayOfWeek ? {days: 7} : {months: 12}) };
}
return { ...res, mode: [baseMode, (past ? 'past' : (next ? 'next' : 'word'))] };
},
relative: ({ type, start, end, result, source, groups, l }) => {
//console.log(groups, result);
if (groups?.relative) {
let {amount, cardinal, few, couple, several} = groups;
if (cardinal) {
amount = numericAmount(cardinal, l);
} else if (few || couple || several) {
amount = few ? 3 : (couple ? 5 : 7);
} else {
amount = /\D/.test(amount) ? parseFloat(amount.replace(/\D/, '.')) : parseInt(amount, 10)
}
if (!isNr(amount)) { return source; }
let [[unit, type]] = ['second', 'minute', 'hour', 'day', 'week', 'month', 'quarter', 'year'].map((u) => {
if (!groups[`${u}_add`] && !groups[`${u}_sub`]) { return false }
return [u, (groups[`${u}_add`] ? 'add' : 'sub')];
}).filter((a) => Array.isArray(a));
if (isNr(amount) && unit && RELATIVE[unit] && type) {
if (type === 'sub') { amount = amount * -1; }
const startTime = RELATIVE[unit](amount);
return !isS(startTime) ? source : { type: 'date', mode: ['relative'], startTime, start, end, source, l };
}
}
return source;
}
};
return {
localDateParser,
parser,
as: {
dayOfWeek, weekday, monthOfYear, monthname, dayPeriod,country, countryName, language, languageName,
currency, currencyName, unit, unitName, timezone, timezoneName, region, regionName,
},
keys: ['dayOfWeek', 'monthOfYear', 'dayPeriod', 'unit', 'timezone', 'country', 'language', 'currency', 'region']
}
}
const { as } = intlToActivityPub(['en']);
export const asEN = as;
// languages which Intl does not support have an own module not covered here, set customExtended = {}:
import { customExtended } from './optionalExtended.ts';
const op = (w) => w.replace(/^[(]/, '(?:');
const rd = (fn) => (r, w, i, a) => (i > 19 ? [...r, w, ...a.slice(1, 10).map((w1) => fn(w, w1))] : [...r, w]);
const r = (a, mapFn) => new RegExp(`\\b(?:${(mapFn ? a.map(mapFn) : a).join('\\b|')})`, 'gi');
const esPt = [
'(nullo?|zero)', '(uno|uma?)', '(doi?s|duas)', '(tr[eê]s)', '([qc]uatro)', '(cinco)', '(seis)', '(si?ete)',
'(ocho|oito)', '(nueve|nove)', '(di?ez)', '(once)', '(doce)', '(tre[cz]e)', '(cator[cz]e)',
'(quin[cz]e)', '(dieciséis|dezesseis|dezasseis)', '(diecisiete|dezessete|dezassete)',
'(dieciocho|dezoito)', '(diecinueve|dezenove|dezanove)', '(ve?inte)', '(tre?inta)'
]
const esPtO = [
'(nullo?|zero)', '(primeir[oa])', '(second[oa]|segund[oa])', '(terz[oa]|terceir[oa])', '(quart[oa])', '(quint[oa])',
'(se[sx]t[oa])', '(s[eé]tt?im[oa])', '(o[it]tav[oa])', '(non[oa])', '(d[eé]cim[oa])',
'(und[eé]cim[oa]|décimo primeir[oa])', '(duo?d[eé]cim[oa]|décimo segund[oa])',
'(tred[eé]cim[oa]|décimo terceir[oa])', '(quattordicesim[oa]|décimo quart[oa])',
'(quindicesim[oa]|décimo quint[oa])', '(sedicesim[oa]|décimo sext[oa])',
'(diciassettesim[oa]|décimo sétim[oa])', '(diciottesim[oa]|décimo oitav[oa])',
'(diciannovesim[oa]|décimo non[oa])'
];
const cardinal = {
en: [
'(null|zero)', '(a|one)', '(two)', '(three)', '(four)', '(fi(?:ve|f))', '(six)', '(seven)',
'(eight)', '(nine?)', '(ten)', '(eleven)', '(twelve)', '(thirteen)', '(fourteen)', '(fifteen)',
'(sixteen)', '(seventeen)', '(eighteen)', '(nineteen)', '(twent(?:y|ie))', '(thirt(?:y|ie))'
],
de: [
'(null)', '((?:ais|ein)(?:em|en|er|s|e)?)', '(zwei|zwai|zwo)', '(drei|drüü|dry)', '(vier|viär)',
'(fünf|fuenf|foif)', '(sechs|sech)', '(sieben|sibe)', '(acht|achter)', '(neun|nüün)', '(zehn|zä)',
'(elf|elve?)', '(zwölf|zwoelf)', '(dreizehn|drüüzä)', '(vierzehn|viärzä)', '(fünfzehn|foifzä)',
'(sechs?zehn|sechszä)', '(siebzehn|sibezä)', '(achtzehn|achtzä)', '(neunzehn|nüünzä)',
'(zwanzigs?|zwänzgs?)', '(drei(?:ss|ß)igs?|dryssgs?)'
],
fr: [
'(null?|z[eéè]ro|zénith)', '(une?)', '(deux)', '(trois)', '(quatre)', '(cinq)', '(six)', '(sept)',
'(huit)', '(neuf)', '(dix)', '(onze)', '(douze)', '(treize)', '(quatorze)', '(quinze)',
'(seize)', '(dix-sept)', '(dix-huit)', '(dix-neuf)', '(vingt(?: |$))', '(trente(?: |$))'
],
it: [
'(null?|z[eéè]ro)', '(uno?)', '(due)', '(tre)', '(quattro)', '(cinque)', '(sei)', '(sette)',
'(otto)', '(nove)', '(dieci)', '(undici)', '(dodici)', '(tredici)', '(quattordici)', '(quindici)',
'(sedici)', '(diciassette)', '(diciotto)', '(diciannove)', '(venti)', '(trente)'
],
es: esPt,
pt: esPt,
ru: [
'(ноль|нуль|зеро)', '(один|o-din)', '(два|двое|dva)', '(три|tri)', '(четыре|che-TY-rye)',
"(пять|pyat')", "(шесть|shest')", "(семь|sem')", "(восемь|vo-sem')",
"(девять|dye-vyat')", "(десять|dye-syat')", "(одиннадцать|o-di-NA-tsy-t')",
"(двенадцать|dve-NA-tsy-t')", "(тринадцать|tri-NA-tsy-t')", "(четырнадцать|che-TYR-na-tsy-t')",
"(пятнадцать|pyat-NA-tsy-t')", "(шестнадцать|shes-NA-tsy-t')", "(семнадцать|sem-NA-tsy-t')",
"(восемнадцать|vo-sim-NA-tsy-t')", "(девятнадцать|dye-vyat-NA-tsy-t)", "(двадцать|dvád-tsat')",
"(тридцать|trí-d-tsat')"
],
ja: [
'(零|rei|zero)', '(一|ichi)', '(二|ni)', '(三|san)', '(四|yon|shi)', '(五|go)', '(六|roku)',
'(七|nana|shichi)', '(八|hachi)', '(九|kyuu|ku)', '(十|juu)', '(十一)', '(十二)', '(十三)', '(十四)',
'(十五)', '(十六)', '(十七)', '(十八)', '(十九)', '(二十)', '(二一)', '(二二)', '(二三)', '(二四)',
'(二五)', '(二六)', '(二七)', '(二八)', '(二九)', '(三十)', '(三一)'
],
ko: [
'(0)', '(하나|hana)', '(둘|dul)', '(셋|set)', '(넷|net)', '(다섯|daseot)', '(여섯|yeoseot)', '(일곱|ilgop)',
'(여덟|yeodeol)', '(아홉|ahop)', '(열|yeol)', '(열하나|yeol-hana)', '(열둘|yeol-dul)', '(열셋|yeol-set)',
'(열넷|yeol-net)', '(열다섯|yeol-daseot)', '(열여섯|yeol-yeoseot)', '(열일곱|yeol-ilgop)',
'(열여덟|yeol-yeodeol)', '(열아홉|yeol-ahop)', '(스물|seumul)', '(스물한)', '(스물두)', '(스물셋)',
'(스물넷)', '(스물다섯)', '(스물여섯)', '(스물일곱)', '(스물열넷)', '(스물다섯)', '(스물여섯)',
'(서른|seoreun)', '(서른하나|삼십일)'
],
zh: [
'(〇|零|líng)', '(一|壹)', '(二|貳)', '(三|叁)', '(四|肆)', '(五|伍)', '(六|陸)', '(七|柒)', '(八|捌)', '(九|玖)',
'(十|拾)', '(十一|拾壹)', '(十二|拾貳)', '(十三|拾叁)', '(十四|拾肆)', '(十五|拾伍)', '(十六|拾陸)',
'(十七|拾柒)', '(十八|拾捌)', '(十九|拾玖)', '(二十|貳拾|廿)', '(二十一|貳拾壹)', '(二十二|貳拾貳)',
'(二十三|貳拾叁)', '(二十四|貳拾肆)', '(二十五|貳拾伍)', '(二十六|貳拾陸)', '(二十七|貳拾柒)',
'(二十八|貳拾捌)', '(二十九|貳拾玖)', '(三十|叁拾|卅)', '(三十一|叁拾壹)'
]
};
const ordinal = {
it: [
'(null|zero)', '(primo)', '(secondo)', '(terzo)', '(quarto)', '(quinto)', '(sesto)', '(settimo)', '(ottavo)',
'(nono)', '(decimo)', '(undicesimo)', '(dodicesimo)', '(tredicesimo)', '(quattordicesimo)',
'(quindicesimo)', '(sedicesimo)', '(diciassettesimo)', '(diciottesimo)', '(diciannovesimo)',
'(ventesimo)', '(ventunesimo)', '(ventiduesimo)', '(ventitreesimo)', '(ventiquattresimo)',
'(venticinquesimo)', '(ventiseiesimo)', '(ventisettesimo)', '(ventottesimo)','(ventinovesimo)',
'(trentesimo)', '(trentunesimo)'
],
es: [...esPtO, ...[
'esimo', 'unesimo', 'iduesimo', 'itreesimo', 'iquattresimo',
'icinquesimo', 'iseiesimo', 'isettesimo', 'ottesimo', 'inovesimo'
].map((s) => `(vent${s})`), 'trentesimo', 'trentunesimo'
],
pt: [...esPtO, 'vigésimo',
...esPtO.slice(1,10).map((s) => `(vigésimo ${s.slice(1)}`), 'trigésimo', 'trigésimo primeiro'
],
ru: [
'(ноль|нуль|зеро)', '(пе́рвый|pervyj)', '(второ́й|vtoroj)', '(тре́тий|tretij)', '(четвёртый|četvërtyj)',
"(пя́тый|pjat'yj)", '(шесто́й|šestoj)', "(седьмо́й|sed'moj)", "(восьмо́й|vos'moj)",
'(девя́тый|devjatyj)', '(деся́тый|desjatyj)', '(оди́ннадцатый|odinnadcatyj)', '(двена́дцатый|dvenadcatyj)',
'(трина́дцатый|trinadcatyj)', '(четы́рнадцатый|četyrnadcatyj)', '(пятна́дцатый|pjatnadcatyj)',
'(шестна́дцатый|šestnadcatyj)', '(семна́дцатый|semnadcatyj)', '(восемна́дцатый|vosemnadcatyj)',
'(девятна́дцатый|devjatnadcatyj)', '(двадца́тый|dvadcatyj)', "(два́дцать пе́рвый|dvadcat' pervyj)",
"(два́дцать второ́й|dvadcat' vtoroj)", "(два́дцать тре́тий|dvadcat' tretij)",
"(два́дцать четвёртый|dvadcat' četvërtyj)", "(два́дцать пя́тый|dvadcat' pjatyj)",
"(два́дцать шесто́й|dvadcat' šestoj)", "(два́дцать седьмо́й|dvadcat' sed'moj)",
"(два́дцать восьмо́й|dvadcat' vos'emoj)", "(два́дцать девя́тый|dvadcat' devjatyj)", '(тридцатый|тридцатилетний)', '(тридцать первый)'
],
ja: [
'(ゼロ|第0)', '(一つ目|hitotsume|一番目|ichibanme|第1)', '(二つ目|futatsume|二番目|nibanme|第2)',
'(三番目|sanbanme|三つ目|mittsume|第3)', '(四番目|yonbanme|四つ目|yottsume|第4)',
'(五番目|gobanme|五つ目|itsutsume|第5)', '(六番目|rokubanme|六つ目|muttsume|第6)',
'(七番目|nanabanme|七つ目|nanatsume|第7)', '(八番目|hachibanme|八つ目|yattsume|第8)',
'(九番目|kyūbanme|九つ目|kokonotsume|第9)', '(十番目|jūbanme|第10)', '(十一番目|jū ichi banme|第11)',
'(十二番目|jū ni banme|第12)', '(十三番目|jū san banme|第13)', '(十四番目|jū yon banme|第14)',
'(十五番目|jū go banme|第15)', '(十六番目|jū roku banme|第16)', '(十七番目|jū nana banme|第17)',
'(十八番目|jū hachi banme|第18)', '(十九番目|jū kyū banme|第19)', '(二十番目|ni-jū banme|第20)',
'(二十一|二十一番目|21番目の?)', '(二十二番目の?|22番目)', '(二十三番目の?|23番目)',
'(二十四番目の?||24番目の?)', '(二十五番目の?|25番目の?)', '(二十六番目の?|26番目の?)',
'(二十七番目の?|27番目の?)', '(二十八番目の?|28番目の?)', '(二十九番目の?|29番目の?)', '(三十番目|san-jū banme|第30)',
'(三十一番目の?|31番目の?)'
],
zh: [
'(〇|零|líng)', '(第一)', '(第二)', '(第三)', '(第四)', '(第五)', '(第六)', '(第七)', '(第八)',
'(第九)', '(第十)', '(第十一)', '(第十二)', '(第十三)', '(第十四)', '(第十五)', '(第十六)', '(第十七)',
'(第十八)', '(第十九)', '(第二十)', '(第二十一)', '(第二十二)', '(第二十三)', '(第二十四)', '(第二十五)',
'(第二十六)', '(第二十七)', '(第二十八)', '(第二十九)', '(第三十)', '(第三十一)'
]
}
const wdEachStd = { weekdayEachSuffix: 's' }
const en = {
timeShift: '(recently|not long ago|(?:just )?(?:a )moments? ago)|(currently|n(?:ow)?)|'+
'(soon|right|in a second|in a minute|shortly)|(afterwards|later)',
dayShift: '((?:the day )?before yes[.]?(?:terday)?)|(yes[.]?terday)|(t[.]?od[.]?ay)|(tom[.]?orrow)|'+
'((?:the day )?after tom[.]?(?:orrow)?)',
// morning, forenoon, noon, afternoon, evening, night, midnight
// _8, _10, _12, _15, _19, _22, _0 */
dayPeriodCasual: '(?:(?:at\\b)|(?:in (?:the)?\\b))?\\s?(?:mornings?|early(?: day)?)|'+
'((?:(?:at\\b)|(?:in (?:the)?\\b))?\\s?late morning|mid-morning|forenoon)|'+
'((?:at )?noon|meridie|lunch(?: break)?|[sS]iesta)|((?:(?:at\\b)|(?:in (?:the)?\\b))?\\s?afternoons?)|'+
'((?:(?:at\\b)|(?:in (?:the)?\\b))?\\s?evenings?)|(nights?)|((?:at )?midnight)',
// daily, nightly, weekly, biweekly, monthly, bimonthly, yearly, annual, biannual
cadence: '(daily)|(nightly)|(weekly)|(biweekly)|(monthly)|(bimonthly)|(quarterly|by quarter)'+
'(yearly|annual)|(bi(?:yearly|annual))',
within: '\\bin|of\\b', each: '\\b(?:at )?(?:each|every|any)|(?:always(?: at)?)\\b', to: '\\bto|until|till?\\b',
or: '\\b(?:else|or|along with)( also)?\\b', and: '\\b(?:plus|in addition to|as well as|together with|and)( also)?\\b',
past: '\\b(?:[pl]ast|prev(?:ious)?)s?\\b', next: '\\bnext?s?\\b',
few: '\\ba few(?: of)?|few', couple: '\\ba couple(?: of)?|couple|some', several: '\\bseveral|more',
add: '\\b\\+|from|hence|aft(?:er)?\\b', substract: '\\b\\-|ago|bef(?:ore)?\\b',
half: '\\bhalf(?: to)?\\b', addHalf: '\\bhalf past|and(?: a)? half\\b',
yearBC: '\\s?(?:(?:b[.]?\\s?efore?(?:[cd]| [cd]))|bce|ce)[.]?',
yearAD: '\\s?(?:(?:a[.]?\\s?nno?(?:d| d))|anno(?: domini)?)[.]?',
timeMSubAdd: "(?:at\\s)?((?:\\d\\d?)|[a-züßö]{3,16})\\s?(?:mn[.]?|min(?:[.]?ute)?s?\\s)?(?:(to)|(past))\\s?"+
"((?:\\d\\d?)|[a-z]{3,6})(?: o['‘]clock| of the clock| oh| h[.]?)?",
timeQuarterPrefix: '(?<add15>(?:a\\s)?quarter past)|(?<sub15>(?:a\\s)?quarter to)|'+
'(?<add30>(?:a\\s)?half past)|(?<sub30>(?:a\\s)?half to)',
concatTime: "(?:\\s?(?:o['‘]clock|of the clock|oh|and|h[.]?))",
concatPrefix: '\\b\\W?\\s?(?:at|o[fhn]|the|in)\\s?\\b',
cardinal: cardinal.en.reduce(rd((w, w1) => `(${op(w)}[-]?${op(w1)})`), []), ...wdEachStd
};
const ptEs = {
timeShift: '(reci?entemente|(?:há|hace)? pou?co(?: tiempo)?|(?:há|hace)? un (?:rato|momento))|'+
'(a[gh]ora|(?:en este |no |neste )?(?:momento|instante)|ac?tualmente)|'+
'(enseguida|pronto|inmediatamente|em breve)|(después|depois|más tarde)',
dayShift: '(ante(?:ayer|ontem))|(ayer|ontem)|(ho[jy]e)|(mañana|amanhã)|((?:pasado|depois de) mañana|amanhã)',
dayPeriodCasual: '(mañanas|manhãs)|((?:(?:pela |de )manhã)|por la mañana)|(med?io[-]?(?:d[ií]as?))|'+
'((?:à |por la )?tardes?)|(por la tarde|ao fim da tarde|por la noche|à tardinha)|'+
'((?:[aà] )última hora|muy de noche|(?:[aà] )?noites?|noches?)|(med?ia[-]?(?:noite|noche))',
cadence: '(diari(?:o|amente))|(noc?turn(?:o|amente))|(semanal(?:o|amente))|(quin[cz]enal(?:o|amente))|'+
'(mensu?al(?:o|amente))|(bimensu?al(?:o|amente))|(trimestral(?:mente)?)|(anual(?:o|amente))|(bianual(?:o|amente)|semestralmente)',
within: '\\be[nm]\\b', each: '\\btodos l?[ao]s|cadas?|si?empre(?: l?[ao]s)?\\b', to: '\\baté|hasta(?: el)?\\b',
or: '\\bou?\\b', and: '\\bplus|[ye]\\b',
past: '\\bpasad[ao]|anterior|últim[ao]\\b', next: '\\bpróxim[ao]\\b',
few: '\\bpou?c[ao]s?|men[ao]s?', couple: '\\bun[ao]s|alg[uú][mn]a?s', several: '\\bv[aá]ri[ao]s|divers[ao]s?',
add: '\\b\\+|después|após|a partir\\b', substract: '\\b\\-|(?:desde )?hace|há|desde\\b',
half: '\\bmed?io\\b', addHalf: '\\b1[/]2|[ye] med?io\\b',
yearBC: '\\s?(?:antes de Cristo|a[.]C[.]|ante christum)',
yearAD: '\\s?(?:después de Cristo|d[.]C[.]|anno(?: domini)?)',
/*timeMSubAdd: "(?:at\\s)?((?:\\d\\d?)|[a-züßö]{3,16})\\s?(?:mn[.]?|min(?:[.]?ute)?s?\\s)?(?:(to)|(past))\\s?"+
"((?:\\d\\d?)|[a-z]{3,6})(?: o['‘]clock| of the clock| oh| h[.]?)?",
um 20 vor drei = a las tres menos veinte
The Spanish language also has an established convention for days of the week using one letter.
These are: L – lunes (Monday); M – martes (Tuesday); X – miércoles (Wednesday);
J – jueves (Thursday); V – viernes (Friday), S – sábado (Saturday); and D – domingo (Sunday).
*/
timeQuarterSuffix: '(?<add15>(?:[ey]) (?:um )?[cq]uarto)|(?<sub15>menos (?:um )?[cq]uarto)|'+
'(?<add30>[ey] med?ia)|(?<sub30>menos med?ia)',
concatTime: "(?:\\s?(?:[ey]|horas|(?:en )?punto|h[.]?))",
concatPrefix: '\\b\\W?\\s?(?:des?|e[lmn]|[aà]s?||l[ao]s?)\\s?\\b',
}
const CASUAL = {
keys: [
'timeShift','dayShift','dayPeriodCasual','cadence','within','to','or','and',
/*'each','few','couple','several','half','addHalf','yearBC','yearAD'*/
],
en: {
monthDigit: { sept: 8 },
regex: {}
},
de: {
monthDigit: { 'jänner': 0, janner: 0, feber: 1, maerz: 2, mrz: 2, sept: 8 },
unit: { y: 'J', d: 'T'},
regex: {}
},
r: {
// TODO not
// this diese?[mnrs] esta...
// cardinalSuffix = 'th|nd|rd|er?|te|-?й|ro|do|[.˚]';
en,
de: {
timeShift: '(kürzlich|neulich|unlängst|vorhin|(?:gerade |so)?eben)|(jetzt|momentan|i[nm] (?:diesem )?Moment)|'+
'(gleich|bald|sofort|in Kürze|umgehend)|(nachher|später)',
dayShift: '(vorgestern)|(ges[.]?tern)|(heute?|heuer|heit|hüt)|(morgen?|moin|moorn)|((?:ü|ue)ber(?:morgen?|moin|moorn))',
// morning, forenoon, noon, afternoon, evening, night, midnight
dayPeriodCasual: '((?:(?:am )?(Morgen|morgendlich))|(vormittags?)|'+
'(Mittag|mittäglich|meridie|(?:zur )?Mittagszeit|(?:in der )?Mittagspause|[sS]iesta)|([nN]achmittags?)|((?:am )?Abends?)|'+
'((?:in der )?Nachts?|nächtlich)|((?:um |zur? )?Mitternacht|Geisterstunde))',
// daily, nightly, weekly, biweekly, monthly, bimonthly, quaterly, yearly, annual, biannual
cadence: '(täglich)|(nächtlich)|(wöchentlich)|((?:(?:2|zwei)[-]?wöchentlich)|alle (?:2|zwei) Wochen)|'+
'(monatlich|jeden Monat)|((?:(?:2|zwei)[-]?monatlich)|alle (?:2|zwei) Monate)|(quartalsweise|jedes Quartal)|'+
'(jährlich|jedes Jahr)|((?:(?:2|zwei)[-]?jährlich)|alle (?:2|zwei) Jahre)',
within: '\\bi[nm]\\b', each: '\\b(?:an )?(?:jede[mnrs]?)|(?:immer(?: a[mn])?)\\b', to: '\\bbis( zum?)?\\b',
or: '\\boder\\b', and: '\\b(?:plus|und|als auch)\\b',
past: '\\b(?:letzte[mnrs]?)|(?:vorige[mnrs]?)\\b', next: '\\bnächste[mnrs]?\\b',
few: '\\bwenig(?:e(?:[mnrs])?)', couple: '\\beinig(?:e(?:[mnrs])?)', several: '\\b(?:ein paar)|mehrer(?:e(?:[mnrs])?)',
add: '\\b\\+|nach|in|ab jetzt\\b', substract: '\\b\\-|(?:(zu)?vor)|seit\\b',
half: '\\b(?:hoib|halb)(?:e(?:[mnrs])?)\\b', addHalf: '\\b1[/]2|einhalb|(?:th?alb)\\b',
yearBC: '\\s?(?:(?:vor|(?:v|a)[.]?)\\s(?:Ch?r?[.]?(?:istus)?)|ante christum)',
yearAD: '\\s?(?:(?:nach|n[.]?)\\s(?:Ch?r?[.]?(?:istus)?)|anno(?: domini)?)',
timeMSubAdd: '(?:um\\s)?((?:,\\d,\\d?)|[a-züßö]{4,16}),\\s?(?:mn[.]?|(?:min[.]?(?:ute)?n?),\\s)?'+
'(?:(vor)|(nach))\\s?((?:,\\d,\\d?)|[a-zöü]{4,6})(?: ?uhr)?',
timeQuarterPrefix: '(?<add15>viertel(?: nach))|(?<sub15>viertel vor|dreiviertel)|'+
'(?<add30>(?:hoib|halb) nach)|(?<sub30>hoib|halb)',
concatTime: '(\\s?(?:uhr|h[.]?)?(?: und)?\\s?)',
concatPrefix: '\\b\\W?\\s?(?:am|an|ab|um|den|der|des|i[nm])\\s?\\b',
cardinal: cardinal.de.reduce(rd((w, w1) => `(${op(w1)}und${op(w)})`), []), ...wdEachStd
},
fr: { // TODO past next can be suffix
timeShift: '(récemment|il y a peu|tout à l\'heure)|(maintenant|en (?:ce )?moment|cette moment|actuellement|immédiament)|'+
'(tout à l\'heure|bientôt|tout (?:de )?suite)|(après|plus tard)',
dayShift: '(avant-hier)|(hier)|(aujourd\'hui)|(demain)|(après-demain)',
dayPeriodCasual: '(matins)|(matinées)|(midis)|(après-midis)|(soirs)|(nuits)|(minuit)',
cadence: '(quotidien)|(nocturne)|(hebdomadaire)|(bihebdomadaire)|(mensuel)|(bimensuel)|(trimestriel)|(annuel)|(biannuel)',
within: '\\bà|en\\b', each: '\\bchaque|toujours(?: l[ae])?\\b', to: '\\bjusqu\'à|jusqu\'en|jusque\\b',
or: '\\bou\\b', and: '\\bet\\b',
past: '\\bdernier|dernière\\b', next: '\\bprochaine?|précédente?\\b',
few: '\\bpeu|quelques?|moins?(?: de)?', couple: '\\bun couple', several: '\\bplusieurs?|plus de',
add: '\\b\\+|après|à partir de maintenant\\b', substract: '\\b\\-|il y a|depuis\\b',
half: '\\bdemi[-]\\b', addHalf: '\\b1[/]2|(?:et )?demie\\b',
yearBC: '\\s?(?:avant Jésus[- ]?(?:Christ)?|ante christum)',
yearAD: '\\s?(?:après Jésus[- ]?(?:Christ)?|anno(?: domini)?)',
/* timeMSubAdd: "", um 20 vor drei = à trois heures moins vingt */
timeQuarterSuffix: '(?<add15>et quart)|(?<sub15>moins (?:le )?quart)|(?<add30>et demie)|(?<sub30>moins (?:le )?demie)',
concatTime: "(?:\\s?(?:heures?|et|h[.]?))",
concatPrefix: '\\b\\W?\\s?(?:dans|des|l[ae]s?|[ei][nm])\\s?\\b',
//(?: |$)
cardinal: cardinal.fr.reduce(rd((w, w1) => { return `(${op(w.replace('(?: |$)', ''))}[- ](?:et[- ])?${op(w1)})`}), []),
...wdEachStd
},
es: {
...ptEs, cardinal: cardinal.es.reduce(rd((w, w1) => `(${op(w1)}(?: [ye] )?\\s?${op(w)})`), []), ...wdEachStd
},
pt: {
...ptEs, cardinal: cardinal.pt.reduce(rd((w, w1) => `(${op(w1)}(?: [ye] )?\\s?${op(w)})`), []), ...wdEachStd
},
it: {
// (?:un )?quarto)|? (?:un )?quarto)|?<sub15>
timeShift: '(recentemente|di recente|poco(?: tempo)? fa|proprio ora)|(ora|al momento|in questo momento)|'+
'(tra poco|presto|subito)|(dopo|più tardi)',
dayShift: '((?:l\')?altro ieri)|(ieri)|(oggi)|(domani|dmn[.]?)|(dopodomani)',
dayPeriodCasual: '(mattine)|(mattinate)|(mezzogiorni)|(pomeriggi)|(sere)|(notti)|(mezzanotte)',
cadence: '(giornaliero)|(notturno)|(settimanale)|(bisettimanale)|(mensile)|(bimestrale)|(trimestrale)|'+
'(annuale)|(biennale|semestrale)',
within: '\\ba|in\\b', each: '\\bogni|sempre(?: la)\\b', to: '\\bfino(?: a)?\\b',
or: '\\bo\\b', and: '\\bplus|e\\b',
past: '\\bscors[ao]|(?:l\')?ultimo|precedente\\b', next: '\\bprossim[ao]\\b',
few: '\\bpoch[ei]|poc[ao]', couple: '\\bqualche|alcune', several: '\\bdivers[eio]|alcuni',
add: '\\b\\+|dopo|da adesso\\b', substract: '\\b\\-|[fd]a\\b',
half: '\\bmezzo\\b', addHalf: '\\b1[/]2|e mezzo\\b',
yearBC: '\\s?(?:prima(?: di) Cristo|a[.]C[.]|ante christum)',
yearAD: '\\s?(?:dopo(?: di) Cristo|d[.]C[.]|anno(?: domini)?)',
/* timeMSubAdd: "", um 20 vor drei = alle due meno venti */
timeQuarterSuffix: '(?<add15>(?:e )?(?:un )?quarto)|(?<sub15>meno (?:un )?quarto)|'+
'(?<add30>(?:e )? mezza)|(?<sub30>meno mezza)',
concatTime: "(?:\\s?(?:in punto|e|h[.]?))",
concatPrefix: '\\b\\W?\\s?(?:e[lmn]|tra||d[ao]s?|al?)\\s?\\b',
cardinal: cardinal.it.reduce(rd((w, w1) => `(${op(w1)}\\s?${op(w)})`), []), ...wdEachStd
},
ru: {
timeShift: '(недавно|только что)|(сейчас|(?:в данный |в этот )?момент)|(сразу|скоро|немедленно)|(после|позже)',
dayShift: '(позавчера)|(вчера)|(сегодня)|(завтра)|(послезавтра)',
dayPeriodCasual: '(утром)|(до полудня)|(в полдень)|(днем)|(вечером)|(ночью)|(полночь)',
cadence: '(ежедневно)|(ежедневно)|(еженедельно)|(два раза в неделю)|'+
'(ежемесячно)|(два раза в месяц)|(ежеквартально)|(ежегодно)|(два раза в год)',
within: '\\bв\\b', each: '\\bкаждый|каждое|каждую|всегда по\\b', to: '\\bдо\\b',
or: '\\bили\\b', and: '\\bplus|и\\b',
past: '\\bпрошлую|прошлый|последнее\\b', next: '\\bследующий|следующую\\b',
few: '\\bнесколько|меньшее|немного', couple: '\\bнекоторое', several: '\\bнескольких',
add: '\\b\\+|через|с этого момента\\b', substract: '\\b\\-|назад\\b',
half: '\\bполовине|полдня|полгода\\b', addHalf: '\\b1[/]2|с половиной\\b',
/* timeMSubAdd: "", um 20 vor drei = за 20 минут до трех */
timeQuarterPrefix: '(?<add15>четверть)|(?<sub15>четверть до)|(?<add30>до пол)|(?<sub30>пол)',
concatTime: "(?:\\s?(?:в|за|до|часа|h[.]?))",
concatPrefix: '\\b\\W?\\s?(?:в|i[nm])\\s?\\b',
cardinal: cardinal.ru.reduce(rd((w, w1) => `(${op(w)}[-]?${op(w1)})`), []), ...wdEachStd
},
// TODO :
ja: {
...en,
timeShift: '(最近|つい先日|ついさっき|さっき|ちょうど今)|(今|現在|この瞬間)|(すぐに|まもなく|すぐに)|(後で|後ほど)',
dayShift: '(一昨日|一昨日の前日)|(昨日|きのう)|(本日|今日|ほんじつ|きょう)|(明日|あした)|(あさって|明後日)',
dayPeriodCasual: '(朝)|(午前中)|(昼)|(午後)|(夕方)|(夜)|(真夜中)',
cadence: '(毎日)|(毎晩)|(毎週)|(隔週)|(毎月)|(隔月)|(四半期ごと)|(毎年)|(半年ごと)',
cardinal: cardinal.ja, ...wdEachStd
},
ko: {
...en,
timeShift: '(최근에|얼마 전에|얼마 전|방금 전에|방금)|(지금|현재|이 순간)|(곧|곧바로|즉시)|(나중에|나중)',
dayShift: '(그저께|엊그제)|(어제)|(오늘)|(내일)|(모레)',
dayPeriodCasual: '(아침)|(오전)|(점심)|(오후)|(저녁)|(밤)|(한밤중)',
cadence: '(매일)|(매일 밤)|(매주)|(격주)|(매월)|(2개월마다)|(분기별)|(매년)|(반년마다)',
cardinal: cardinal.ko, ...wdEachStd
},
zh: {
...en,
cardinal: cardinal.zh, ...wdEachStd
}
},
fn: {
}
};
/*
ja
こんや|今夜 Heute Abend
けさ|今朝 Heute Morgen
zh
if (text == "今夜" || text == "今夕" || text == "今晩") {
components.imply("hour", 22);
components.assign("meridiem", Meridiem.PM);
} else if (text.match("今朝")) {
components.imply("hour", 6);
components.assign("meridiem", Meridiem.AM);
}
const PATTERN =
/(?:(?:([同今本])|((昭和|平成|令和)?([0-90-9]{1,4}|元)))年\s*)?([0-90-9]{1,2})月\s*([0-90-9]{1,2})日/i;
const SPECIAL_YEAR_GROUP = 1;
const TYPICAL_YEAR_GROUP = 2;
const ERA_GROUP = 3;
const YEAR_NUMBER_GROUP = 4;
const MONTH_GROUP = 5;
const DAY_GROUP = 6;
*/
// TODO ordinal
const [enO, deS, frO, koO] = [
{1: '(first|1st)', 2: '(second|2nd)', 3: '(third|3rd)'},
`(?:[mnrs])?)`,
{1: '(premier|première?)', 2: '(deuxième|seconde)', 4: '(quartième)' },
{1: '(첫 번째|cheot beonjjae)', 2: '(두 번째|du beonjjae)', 3: '(세 번째|se beonjjae)'}
];
/** */
const baseKeys = ['monthDigit', 'unit', 'regex'];
for (const l in CASUAL.r) {
if (!CASUAL[l]) { CASUAL[l] = {regex: {}}; }
//CASUAL[l].regex.cardinal = r(CASUAL.r[l].cardinal);
}
/*
for (const l in cardinal) {
if (!CASUAL[l]) { CASUAL[l] = {regex: {}}; }
baseKeys.forEach((k) => { if (!CASUAL[l][k]) { CASUAL[l][k] = {}; } });
CASUAL[l].regex.cardinal = r(cardinal[l]);
}
*/
// ordinals …
CASUAL.en.regex.ordinal = r(cardinal.en, (s,i) => (enO[i] || `(${(i>20&&i<24)
? `${op(cardinal.en[20])}[-]?${op(enO[i-20])}`
: (i>30&&i<34 ? `${op(cardinal.en[30])}[-]?${op(enO[i-30])}` : op(s+'th'))}|${i}th)`));
CASUAL.de.regex.ordinal = r(cardinal.de, (s,i) => (i===1
? `(erste${deS}`
: `((?:${(i>9 ? s : s.replace('drei|','drit|').replace('sieben|','siebe?n?|')).slice(1)}te${deS}`));
CASUAL.fr.regex.ordinal = r(cardinal.fr, (s,i) => (frO[i] ||
`${s.replace(/f$/, 'v').replace(/e$/, '').replace(/^vingt/, 'ving?t')+'ième'}`));
CASUAL.ko.regex.ordinal = r(cardinal.ko, (s,i) =>
(koO[i] || `(${s.replace(/[()]/g, '').split('|').map((s2) => `${s2}번째`).join('|')})`));
/* Sino-Korean :
Alternatively, for more formal or written contexts, the prefix 제 (je) is used with Sino-Korean numbers (e.g., 제 일 장 for "Chapter 1").
*/
CASUAL.it.regex.ordinal = r(ordinal.it);
CASUAL.pt.regex.ordinal = r(ordinal.pt);
CASUAL.es.regex.ordinal = r(ordinal.es);
CASUAL.ru.regex.ordinal = r(ordinal.ru);
CASUAL.ja.regex.ordinal = r(ordinal.ja);
CASUAL.zh.regex.ordinal = r(ordinal.zh);
for (const l in CASUAL) {
if (CASUAL[l]?.regex?.ordinal) { CASUAL.r[l].ordinal = CASUAL[l].regex.ordinal.source; }
}
export const dateCasual = CASUAL;
//console.log(CASUAL.r.fr.cardinal, CASUAL.r.fr.ordinal);
/*console.log(CASUAL.de.regex.ordinal);*/
/*
export const extend = {
en: {
regexPattern: {
time: /((?:(?:at|from)\s)?(2[0-3]|[01][0-9]|[0-9]\b)(?:\s?(?:o['‘]clock|of the clock|oh|h[.]?))(?: and)?\s?([0-5][0-9]|[0-9]\b)(?: (?:mn[.]?|min(?:[.]?ute)?s?)(?: and)?\s?)?([0-5][0-9]|[0-9]\b)?(?:\s(?:sec[.]?(?:ond)?s?))?\s?(?:o['‘]clock|of the clock|oh|h[.]?)?|(?:(?:(?:at|from)\s)(2[0-3]|[01][0-9]|[0-9]\b)[.,:]?\s?([0-5][0-9]|[0-9]\b)?(?:\s?(?:o['‘]clock|of the clock|oh|h[.]?)?)))/i,
timeMSubAdd: /(?:at\s)?((?:\d\d?)|[a-züßö]{3,16})\s?(?:mn[.]?|min(?:[.]?ute)?s?\s)?(?:(to)|(past))\s?((?:\d\d?)|[a-z]{3,6})(?: o['‘]clock| of the clock| oh| h[.]?)?/i,
timeHalfSubAdd: /(?:at\s)?half(?:( to )|( past ))((?:\d\d?)|[a-z]{3,6})(?: o['‘]clock| of the clock| oh| h[.]?)?/i,
timeQSub: /(?:at\s)?(a\s)?quarter to ((?:\d\d?)|[a-z]{3,6})/i, timeQAdd: /(?:at\s)?(a\s)?quarter past ((?:\d\d?)|[a-z]{3,6})/i,
// _8, _10, _12, _15, _19, _22, _0
dayPeriodExt: /\b((?:(?:at\b)|(?:in (?:the)?\b))?\s?(mornings?|early day)|(late morning|forenoon)|(noon|meridie|lunch(?: break)?|[sS]iesta)|(afternoons?)|(evenings?)|(night|nightly)|(midnight))/
// cadence the daily / nightly / weekly / biweekly / monthly / bimonthly / yearly / annual / biannual... event / festival / fair / party...
// alternate
// The abbreviation QOD or QAD (from Latin mean Quaque Alternis Die") means 'every other day' or 'every two days'.
}
},
//
de: { // Todo: vor 2 Wochen = 2 Wochen vorher/zuvor
regexPattern: {
time: /((?:(?:um|ab)\s)?(2[0-3]|[01][0-9]|[0-9]\b)(?:\s?(?:uhr|h[.]?)(?: und)?\s?)?([0-5][0-9]|[0-9]\b)(?: (?:mn[.]?|(?:min[.]?(?:ute)?n?))?)?(?: und )?([0-5][0-9]|[0-9]\b)?(?:\s(?:sek[.]?(?:unde)?n?))?|(?:(?:(?:um|ab)\s)(2[0-3]|[01][0-9]|[0-9]\b)[.,:]?\s?([0-5][0-9]|[0-9]\b)?(?:\s?(?:uhr|h[.]?)?)))/i,
timeMSubAdd: /(?:um\s)?((?:\d\d?)|[a-züßö]{4,16})\s?(?:mn[.]?|(?:min[.]?(?:ute)?n?)\s)?(?:(vor)|(nach))\s?((?:\d\d?)|[a-zöü]{4,6})(?: ?uhr)?/i,
timeHalfSubAdd: /(?:um\s)?(?:hoib|halb)(?:( )|( nach ))((?:\d\d?)|[a-zöü]{4,6})/i,
timeQSub: /(?:um\s)?viertel vor ((?:\d\d?)|[a-zöü]{4,6})/i,
timeQAdd: /(?:um\s)?(?:(?:viertel nach )|(?:drei)?viertel )((?:\d\d?)|[a-zöü]{4,6})/i,
dayPeriodExt: /((?:am )?(Morgen|morgendlich)|(Vormittag|vormittags)|(Mittag|mittäglich|meridie|(?:zur )?Mittagszeit|(?:in der )?Mittagspause|[sS]iesta)|([nN]achmittags?)|(Abend)|((?:in der )?Nacht|nächtlich)|((?:um |zur? )?Mitternacht|Geisterstunde))/
}
}
}
*/
// TODO optional:
//for (const k in customExtended) { customExtended[k] = {...base[k], ...customExtended[k]}; }
export const custom = { ...customExtended };
// <-- optional
import { dateCasual } from './local.ts';
import { asEN } from './intlToActivityPub.ts';
export 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'
];
export const MONTH_DIGIT_EN = {Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11};
export const ranges = { d: 'day', m: 'month', y: 'year' };
export const isS = (s): s is string => (typeof s === 'string' && s);
export const isNr = (n): n is number => (typeof n === 'number' && !isNaN(n));
export const ok = (s) => s;
export const byLength = (a, b) => b.length-a.length;
export const rIndex = (r, w, i) => (!w && r < 0 ? -1 : (r < 0 ? i : r));
export const validH = (n) => isNr(n) && Number.isInteger(n) && n > -1 && n < 25;
export const splitMapper = (regex, calcFnOrO) => (s) => {
if (!isS(s) || !regex.test(s)) { return s; }
const parts = s.split(regex);
const result = typeof calcFn === 'function' ? calcFnOrO() : calcFnOrO;
return parts.length === 1 ? s : parts.filter(ok).map((input) => !regex.test(input) ? input : { input, ...result });
}
/*
const cardinalYear = (locale) => {
const { yearADsuffix, yearBDsuffix } = extend[locale].regexPattern;
const { yearADsuffixEN, yearBDsuffixEN } = extend['en-US'].regexPattern;
const ignoreSet = new Set();
return (o, i, a) => {
if (isS(o) || ignoreSet.has(i) || !a || !isS(a[i+1])) { return o }
const next = a[i+1];
const [isAD] = next.match(yearADsuffix) ?? next.match(yearADsuffixEN);
const [isBC] = next.match(yearBDsuffix) ?? next.match(yearBDsuffixEN);
if (isAD || isBC) {
const m = (isAD || isBC);
o.type = 'y';
o.input = `${o.input}${m}`;
if (isBC && o.amount > 0) { o.amount = (0-o.amount); }
a[i+1] = next.replace(m, '');
ignoreSet.add(i+1);
}
return o;
}
}
*/
export const co = (locale, type:string = 'C', o={next:0}) => {
const ext = Object.hasOwnProperty.call(dateCasual, locale) ? dateCasual[locale] : dateCasual.en;
const R = ext.regex[type === 'C' ? 'cardinal' : 'ordinal'];
return (s, i, a) => {
if (!isS(s)) { return s; }
const parts = s.split(R).filter((s) => s === void 0 || s);
if (parts.length < 2) { return s; }
let [ input, amount ] = parts.map((p, n) => p && R.test(p) ? [p, n] : false).filter(ok)[0];
if (input && isNr(amount)) {
return splitMapper(new RegExp(`(${input.replace(REGEX.NONW, '[$1]')})`), {input, amount, type})(s).flat().filter(ok)
//.map(cardinalYear(locale));
}
return s;
}
};
export const toTimeInt = (s, locale = 'en') => {
if (isS(s) && s === '0') { return 0; }
if (isS(s) && !REGEX.DIGIT_ALL.test(s)) {
const nrO = co(locale)(s);
if (nrO !== s && isNr(nrO?.amount)) { return nrO.amount; }
}
return !isS(s) ? (isNr(s) ? s : 0) : parseInt(s.trim().replace(/^[0,.]/,''), 10);
};
export const intS = (i: number) => `${i >= 0 && i < 10 ? '0' : ''}${i}`;
export const calcTime = (amount = 1, mode: 'sec'|'min'|'h' = 'h', jsdate = new Date() ) => {
if (!(jsdate instanceof Date)) { return false; }
let startTime = new Date(jsdate.valueOf());
const ms = Math.round((mode === 'sec' ? 1000 : (mode === 'min' ? (60*1000) : (60*60*1000))) * amount);
startTime.setTime(startTime.getTime() + ms);
return isNaN(startTime) ? false : startTime.toISOString();
}
export const calcDate = (amount = 1, mode: 'd'|'m'|'y' = 'd', jsdate = new Date() ) => {
if (!(jsdate instanceof Date)) { return false; }
let startTime = new Date(jsdate.valueOf());
const attr = mode === 'y' ? 'FullYear' : (mode === 'm' ? 'Month' : 'Date');
if (amount) { startTime[`set${attr}`](startTime[`get${attr}`]() + Math.round(amount)); }
const valid = !isNaN(startTime);
return isNaN(startTime) ? false : startTime.toISOString();
}
export const coDigit = (locale) => (s, i, a) => {
if (!isS(s)) { return s; }
const parts = s.split(REGEX.CO_DIGIT);
if (parts.length < 2) { return s; }
let [ input, amount ] = parts.map((p) => p && REGEX.CO_DIGIT.test(p) ? [p, toTimeInt(p.replace(/[.]$/, ''), 10)] : false).filter(ok)[0];
if (input && isNr(amount)) {
const type = REGEX.DIGIT_ALL.test(input) ? 'C' : 'O';
return splitMapper(new RegExp(`(${input.replace(REGEX.NONW, '[$1]')})`), {input, amount, type})(s).flat().filter(ok).map(cardinalYear(locale));
}
return s;
}
const [N, D] = ['numeric', '2-digit'];
const flatParts = (r, o) => { r[o.type] = o.value; return r; }
export const datestringToTimezone = (s, timeZone) => {
if (!isS(s)) { return s;}
const [_d, __t] = s.split('T');
const _t = __t.split('.')[0];
const [[y, m, d], [h, min, sec]] = [_d.split('-'), _t.split(':')];
const utcDateString = new Date(`${y}-${m}-${d}T${h}:${min}:${sec}Z`).toISOString();
const utcDate = new Date(utcDateString);
const diff = new Intl.DateTimeFormat('de', {timeZone, year:N, month:D, day:D, hour:D, minute:D, second:D});
const { year, month, day, hour, minute, second } = diff.formatToParts(utcDate).reduce(flatParts, {});
const diffDateString = new Date (`${year}-${month}-${day}T${hour}:${minute}:${second}Z`).toISOString();
const diffDate = new Date(diffDateString);
let diffN = diffDate.valueOf() - utcDate.valueOf();
if (!diffN) { return utcDateString; }
let resDate = new Date(utcDate.valueOf()-diffN);
if (diffDate.valueOf() < utcDate.valueOf()) {
diffN = utcDate.valueOf() - diffDate.valueOf();
resDate = new Date(utcDate.valueOf()+diffN);
}
const { day: d2, hour: h2, minute: min2, second: sec2 } = diff.formatToParts(resDate).reduce(flatParts, {});
const result = new Date(utcDate.valueOf()-diffN).toISOString();
if (d2 === d && h2 === h && min2 === min && sec2 === sec) {
// console.log(new Date(utcDate.valueOf()-diffN).toLocaleString(), diff.format(new Date(utcDate.valueOf()-diffN)), h);
return result;
}
// TODO - a time shift occured within the calculated timezone difference ...
}
// datestringToTimezone('2025-09-03T15:00:00', 'Australia/Sydney');
const toDateS = (dO, tO) => `${dO.startTime.split('T')[0]}T${intS(tO.h)}:${intS(tO.min)}:${intS(tO.sec)}`;
export const toDateTimeLocal = (a, l, d) => {
const [res, ignoreSet] = [[], new Set([])];
const isWD = (o) => Object.hasOwnProperty.call(o, 'type') && o.type === 'dayOfWeek';
const isD = (o) => Object.hasOwnProperty.call(o, 'type') && o.type === 'date';
const isT = (o) => Object.hasOwnProperty.call(o, 'type') && o.type === 'time';
// console.log('a',a);
const addToNext = (el, next, i, mergeMode = false) => {
ignoreSet.add(i+1);
if (isS(next)) { return `${el.source}${next}`; }
next.source = `${el.source}${next.source}`;
next.start = el.start;
if (mergeMode) { next.mode = [...(el?.mode ?? []), ...(next?.mode ?? [])]; }
return next
}
a.forEach((el, i) => {
if (ignoreSet.has(i)) { return; }
if (isD(el) && el?.startTime) {
let mode = [];
if (el.startTime.startsWith('-')) {
mode = ['BC', 'prehistoric'];
} else if (new Date(el.startTime) < -12212553600000) {
mode = ['prehistoric'];
}
if (mode.length) {
if (!el?.mode) { el.mode = []; }
el.mode = [...el.mode, ...mode];
}
}
const [next, next2 = ''] = [a[i+1], a[i+2]];
// consider these signals to be to weak for date/time
if ((isD(el) || isT(el)) && isS(next) && (el.value ? (el.value.length < 4 || el.source.length < 4) : (el.source.length < 4))) {
res.push(addToNext(el, next, i, true));
return;
}
if (isD(el) && i < a.length-1) {
//console.log('isDate', el)
if (el.mode.indexOf('dayOfWeek') > -1 && isD(next)) {
res.push(addToNext(el, next, i, true));
return;
}
if (isS(next) && next.startsWith('[')) {
const tz = asEN.timezoneName[next.slice(1).split(/ ?\]/)[0]];
if (isS(tz)) {
ignoreSet.add(i+1);
const [tzSource, suffix] = [next.slice(0, next.indexOf(']')+1), next.slice(next.indexOf(']')+1)];
el.mode = ['rfc9557'];
el.source = `${el.source}${tzSource}`;
el.startTime = datestringToTimezone(el.startTime, tz);
el.end = (el.start+el.source.length);
res.push(el);
res.push(suffix);
return;
}
// console.log('!!', next.slice(1).split(']')[0], !!asEN.timezoneName[next.slice(1).split(']')[0]]);
}
const prefixRegex = (dateCasual?.r[l] && dateCasual.r[l]?.concatPrefix) ? new RegExp(dateCasual.r[l].concatPrefix, 'i') : /\b[a-z].?\b/;
if (prefixRegex.test(next) && isT(next2)) {
const source = `${el.source}${next}${next2.source}`;
const startTime = datestringToTimezone(toDateS(el, next2), (next2?.tz ?? 'UTC'));
const [{ start }, { end }] = [el, next2];
if (isS(startTime)) {
ignoreSet.add(i+1); ignoreSet.add(i+2);
res.push({ type: 'date', mode: [...(el?.mode ?? []), ...(next?.mode ?? [])], startTime, start, end, source });
return;
}
} else if (isT(next)) {
const startTime = datestringToTimezone(toDateS(el, next), (next?.tz ?? 'UTC'));
if (isS(startTime)) {
res.push({ ...addToNext(el, next, i, true), type: 'date', startTime });
return;
}
} else if (isD(next)) {
if ((el?.mode||[]).indexOf('timeShift') > -1 && (next?.mode||[]).indexOf('timeShift') > -1) {
res.push(addToNext(el, next, i, true));
return;
}
}
}
res.push(el);
});
return res;
}
export const parse = (input, o, l = 'en', outFn = (s) => s, baseDate = (new Date())) => {
const regexParts = o.keys.map((k) => {
const W = o.r[l] ? o.r[l] : (o.r[l.split('-')[0]] ?? o.r.en);
return `(?<${k}>${W[k]})\\W*`
});
const regex = new RegExp(`\\b${regexParts.join('|')}\\b`, 'gi');
//console.log(input,regex.exec(input))
let [position, res, array] = [-1, []];
while ((array = regex.exec(input)) !== null) {
const [source, ..._result] = array;
let r = {start: array.index, end: regex.lastIndex, source, groups: {...array.groups}, l};
// console.log(array.groups)
for (const k in array.groups) {
if (array.groups[k]) {
if (o.keys.indexOf(k) > -1) { r.type = k; }
r[k] = array.groups[k];
}
}
let had = false;
r.result = _result.filter((s) => { if (!had && s) { had = true; } return had; });
// console.log('!!', r.type, source, o.fn[r.type](r))
if (o.fn[r.type]) { r = o.fn[r.type](r, baseDate); }
if (!res.length) {
res = array.index > 0 ? [input.slice(0, array.index), r] : [r];
} else {
res = position < (array.index-1) ? [...res, input.slice(position, array.index), r] : [...res, r];
}
position = r?.end||regex.lastIndex;
// console.log(`Found ${array[0]} ${array.index}, next starts at ${r.lastIndex}.`, array, array.groups);
}
// if (position === -1) { return input }
return outFn((position < 0 ? [input] : (position === input.length ? res : [...res, input.slice(position)]).filter(ok)), l, baseDate);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment