1'use strict'; 2const common = require('../common'); 3const assert = require('assert'); 4const { execFileSync } = require('child_process'); 5 6// system-icu should not be tested 7const hasBuiltinICU = process.config.variables.icu_gyp_path === 'tools/icu/icu-generic.gyp'; 8if (!hasBuiltinICU) 9 common.skip('system ICU'); 10 11// small-icu doesn't support non-English locales 12const hasFullICU = (() => { 13 try { 14 const january = new Date(9e8); 15 const spanish = new Intl.DateTimeFormat('es', { month: 'long' }); 16 return spanish.format(january) === 'enero'; 17 } catch { 18 return false; 19 } 20})(); 21if (!hasFullICU) 22 common.skip('small ICU'); 23 24const icuVersionMajor = Number(process.config.variables.icu_ver_major ?? 0); 25if (icuVersionMajor < 71) 26 common.skip('ICU too old'); 27 28 29function runEnvOutside(addEnv, code, ...args) { 30 return execFileSync( 31 process.execPath, 32 ['-e', `process.stdout.write(String(${code}));`], 33 { env: { ...process.env, ...addEnv }, encoding: 'utf8' } 34 ); 35} 36 37function runEnvInside(addEnv, func, ...args) { 38 Object.assign(process.env, addEnv); // side effects! 39 return func(...args); 40} 41 42function isPack(array) { 43 const firstItem = array[0]; 44 return array.every((item) => item === firstItem); 45} 46 47function isSet(array) { 48 const deduped = new Set(array); 49 return array.length === deduped.size; 50} 51 52 53const localesISO639 = [ 54 'eng', 'cmn', 'hin', 'spa', 55 'fra', 'arb', 'ben', 'rus', 56 'por', 'urd', 'ind', 'deu', 57 'jpn', 'pcm', 'mar', 'tel', 58]; 59 60const locales = [ 61 'en', 'zh', 'hi', 'es', 62 'fr', 'ar', 'bn', 'ru', 63 'pt', 'ur', 'id', 'de', 64 'ja', 'pcm', 'mr', 'te', 65]; 66 67// These must not overlap 68const zones = [ 69 'America/New_York', 70 'UTC', 71 'Asia/Irkutsk', 72 'Australia/North', 73 'Antarctica/South_Pole', 74]; 75 76 77assert.deepStrictEqual(Intl.getCanonicalLocales(localesISO639), locales); 78 79 80// On some platforms these keep original locale (for example, 'January') 81const enero = runEnvOutside( 82 { LANG: 'es' }, 83 'new Intl.DateTimeFormat(undefined, { month: "long" } ).format(new Date(9e8))' 84); 85const janvier = runEnvOutside( 86 { LANG: 'fr' }, 87 'new Intl.DateTimeFormat(undefined, { month: "long" } ).format(new Date(9e8))' 88); 89const isMockable = enero !== janvier; 90 91// Tests with mocked env 92if (isMockable) { 93 assert.strictEqual( 94 isSet(zones.map((TZ) => runEnvOutside({ TZ }, 'new Date(333333333333).toString()'))), 95 true 96 ); 97 assert.strictEqual( 98 isSet(zones.map((TZ) => runEnvOutside({ TZ }, 'new Date(333333333333).toLocaleString()'))), 99 true 100 ); 101 assert.deepStrictEqual( 102 locales.map((LANG) => runEnvOutside({ LANG, TZ: 'Europe/Zurich' }, 'new Date(333333333333).toString()')), 103 [ 104 'Fri Jul 25 1980 01:35:33 GMT+0100 (Central European Standard Time)', 105 'Fri Jul 25 1980 01:35:33 GMT+0100 (中欧标准时间)', 106 'Fri Jul 25 1980 01:35:33 GMT+0100 (मध्य यूरोपीय मानक समय)', 107 'Fri Jul 25 1980 01:35:33 GMT+0100 (hora estándar de Europa central)', 108 'Fri Jul 25 1980 01:35:33 GMT+0100 (heure normale d’Europe centrale)', 109 'Fri Jul 25 1980 01:35:33 GMT+0100 (توقيت وسط أوروبا الرسمي)', 110 'Fri Jul 25 1980 01:35:33 GMT+0100 (মধ্য ইউরোপীয় মানক সময়)', 111 'Fri Jul 25 1980 01:35:33 GMT+0100 (Центральная Европа, стандартное время)', 112 'Fri Jul 25 1980 01:35:33 GMT+0100 (Horário Padrão da Europa Central)', 113 'Fri Jul 25 1980 01:35:33 GMT+0100 (وسطی یورپ کا معیاری وقت)', 114 'Fri Jul 25 1980 01:35:33 GMT+0100 (Waktu Standar Eropa Tengah)', 115 'Fri Jul 25 1980 01:35:33 GMT+0100 (Mitteleuropäische Normalzeit)', 116 'Fri Jul 25 1980 01:35:33 GMT+0100 (中央ヨーロッパ標準時)', 117 'Fri Jul 25 1980 01:35:33 GMT+0100 (Mídúl Yúrop Fíksd Taim)', 118 'Fri Jul 25 1980 01:35:33 GMT+0100 (मध्य युरोपियन प्रमाण वेळ)', 119 'Fri Jul 25 1980 01:35:33 GMT+0100 (సెంట్రల్ యూరోపియన్ ప్రామాణిక సమయం)', 120 ] 121 ); 122 assert.deepStrictEqual( 123 locales.map((LANG) => runEnvOutside({ LANG, TZ: 'Europe/Zurich' }, 'new Date(333333333333).toLocaleString()')), 124 [ 125 '7/25/1980, 1:35:33 AM', 126 '1980/7/25 01:35:33', 127 '25/7/1980, 1:35:33 am', 128 '25/7/1980, 1:35:33', 129 '25/07/1980 01:35:33', 130 '٢٥/٧/١٩٨٠، ١:٣٥:٣٣ ص', 131 '২৫/৭/১৯৮০, ১:৩৫:৩৩ AM', 132 '25.07.1980, 01:35:33', 133 '25/07/1980, 01:35:33', 134 '25/7/1980، 1:35:33 AM', 135 '25/7/1980, 01.35.33', 136 '25.7.1980, 01:35:33', 137 '1980/7/25 1:35:33', 138 '25/7/1980 01:35:33', 139 '२५/७/१९८०, १:३५:३३ AM', 140 '25/7/1980 1:35:33 AM', 141 ] 142 ); 143 assert.strictEqual( 144 runEnvOutside({ LANG: 'en' }, '["z", "ä"].sort(new Intl.Collator().compare)'), 145 'ä,z' 146 ); 147 assert.strictEqual( 148 runEnvOutside({ LANG: 'sv' }, '["z", "ä"].sort(new Intl.Collator().compare)'), 149 'z,ä' 150 ); 151 assert.deepStrictEqual( 152 locales.map( 153 (LANG) => runEnvOutside({ LANG, TZ: 'Europe/Zurich' }, 'new Intl.DateTimeFormat().format(333333333333)') 154 ), 155 [ 156 '7/25/1980', '1980/7/25', 157 '25/7/1980', '25/7/1980', 158 '25/07/1980', '٢٥/٧/١٩٨٠', 159 '২৫/৭/১৯৮০', '25.07.1980', 160 '25/07/1980', '25/7/1980', 161 '25/7/1980', '25.7.1980', 162 '1980/7/25', '25/7/1980', 163 '२५/७/१९८०', '25/7/1980', 164 ] 165 ); 166 assert.deepStrictEqual( 167 locales.map((LANG) => runEnvOutside({ LANG }, 'new Intl.DisplayNames(undefined, { type: "region" }).of("CH")')), 168 [ 169 'Switzerland', '瑞士', 170 'स्विट्ज़रलैंड', 'Suiza', 171 'Suisse', 'سويسرا', 172 'সুইজারল্যান্ড', 'Швейцария', 173 'Suíça', 'سوئٹزر لینڈ', 174 'Swiss', 'Schweiz', 175 'スイス', 'Swítsaland', 176 'स्वित्झर्लंड', 'స్విట్జర్లాండ్', 177 ] 178 ); 179 assert.deepStrictEqual( 180 locales.map((LANG) => runEnvOutside({ LANG }, 'new Intl.NumberFormat().format(275760.913)')), 181 [ 182 '275,760.913', '275,760.913', 183 '2,75,760.913', '275.760,913', 184 '275 760,913', '٢٧٥٬٧٦٠٫٩١٣', 185 '২,৭৫,৭৬০.৯১৩', '275 760,913', 186 '275.760,913', '275,760.913', 187 '275.760,913', '275.760,913', 188 '275,760.913', '275,760.913', 189 '२,७५,७६०.९१३', '2,75,760.913', 190 ] 191 ); 192 assert.deepStrictEqual( 193 locales.map((LANG) => runEnvOutside({ LANG }, 'new Intl.PluralRules().select(0)')), 194 [ 195 'other', 'other', 'one', 'other', 196 'one', 'zero', 'one', 'many', 197 'one', 'other', 'other', 'other', 198 'other', 'one', 'other', 'other', 199 ] 200 ); 201 assert.deepStrictEqual( 202 locales.map((LANG) => runEnvOutside({ LANG }, 'new Intl.RelativeTimeFormat().format(-586920.617, "hour")')), 203 [ 204 '586,920.617 hours ago', 205 '586,920.617小时前', 206 '5,86,920.617 घंटे पहले', 207 'hace 586.920,617 horas', 208 'il y a 586 920,617 heures', 209 'قبل ٥٨٦٬٩٢٠٫٦١٧ ساعة', 210 '৫,৮৬,৯২০.৬১৭ ঘন্টা আগে', 211 '586 920,617 часа назад', 212 'há 586.920,617 horas', 213 '586,920.617 گھنٹے پہلے', 214 '586.920,617 jam yang lalu', 215 'vor 586.920,617 Stunden', 216 '586,920.617 時間前', 217 '586,920.617 áwa wé dọ́n pas', 218 '५,८६,९२०.६१७ तासांपूर्वी', 219 '5,86,920.617 గంటల క్రితం', 220 ] 221 ); 222} 223 224 225// Tests with process.env mutated inside 226{ 227 // process.env.TZ is not intercepted in Workers 228 if (common.isMainThread) { 229 assert.strictEqual( 230 isSet(zones.map((TZ) => runEnvInside({ TZ }, () => new Date(333333333333).toString()))), 231 true 232 ); 233 assert.strictEqual( 234 isSet(zones.map((TZ) => runEnvInside({ TZ }, () => new Date(333333333333).toLocaleString()))), 235 true 236 ); 237 } else { 238 assert.strictEqual( 239 isPack(zones.map((TZ) => runEnvInside({ TZ }, () => new Date(333333333333).toString()))), 240 true 241 ); 242 assert.strictEqual( 243 isPack(zones.map((TZ) => runEnvInside({ TZ }, () => new Date(333333333333).toLocaleString()))), 244 true 245 ); 246 } 247 248 assert.strictEqual( 249 isPack(locales.map((LANG) => runEnvInside({ LANG, TZ: 'Europe/Zurich' }, () => new Date(333333333333).toString()))), 250 true 251 ); 252 assert.strictEqual( 253 isPack(locales.map( 254 (LANG) => runEnvInside({ LANG, TZ: 'Europe/Zurich' }, () => new Date(333333333333).toLocaleString()) 255 )), 256 true 257 ); 258 assert.deepStrictEqual( 259 runEnvInside({ LANG: 'en' }, () => ['z', 'ä'].sort(new Intl.Collator().compare)), 260 runEnvInside({ LANG: 'sv' }, () => ['z', 'ä'].sort(new Intl.Collator().compare)) 261 ); 262 assert.strictEqual( 263 isPack(locales.map( 264 (LANG) => runEnvInside({ LANG, TZ: 'Europe/Zurich' }, () => new Intl.DateTimeFormat().format(333333333333)) 265 )), 266 true 267 ); 268 assert.strictEqual( 269 isPack(locales.map( 270 (LANG) => runEnvInside({ LANG }, () => new Intl.DisplayNames(undefined, { type: 'region' }).of('CH')) 271 )), 272 true 273 ); 274 assert.strictEqual( 275 isPack(locales.map((LANG) => runEnvInside({ LANG }, () => new Intl.NumberFormat().format(275760.913)))), 276 true 277 ); 278 assert.strictEqual( 279 isPack(locales.map((LANG) => runEnvInside({ LANG }, () => new Intl.PluralRules().select(0)))), 280 true 281 ); 282 assert.strictEqual( 283 isPack(locales.map( 284 (LANG) => runEnvInside({ LANG }, () => new Intl.RelativeTimeFormat().format(-586920.617, 'hour')) 285 )), 286 true 287 ); 288} 289