]> git.ardis.lu - csvio/commitdiff
Refactor `DECIMAL_SEPARATOR` to a function
authorArdis Lu <ardisglu@gmail.com>
Tue, 27 May 2025 05:10:07 +0000 (22:10 -0700)
committerArdis Lu <ardisglu@gmail.com>
Tue, 27 May 2025 05:10:07 +0000 (22:10 -0700)
This change enables easier testing and also allows future improvements for dynamically changing the locale during runtime.

src/normalization.js
test/normalization.test.js

index f1428437cdc8a4bb8d91da2c6ade8582cfbde178..04e24af4a88df17ca47ea57197a94de33820b4e0 100644 (file)
@@ -1,7 +1,14 @@
 import { TransformStream } from 'node:stream/web';
 
-// User locale's decimal separator
-const DECIMAL_SEPARATOR = Intl.NumberFormat().formatToParts(.1).find(p => p.type === 'decimal').value;
+/**
+ * Get the decimal separator character to parse numbers formatted in different locales.
+ * @param {string|Intl.Locale} [locale] A `string` Unicode locale identifier, or an `Intl.Locale` object specifying
+ * the locale that is applicable. If no value is provided, the current execution environment's locale is used.
+ * @returns {string} The locale's decimal separator as a `string` (e.g., "." for the "en-US" locale).
+ */
+export function getDecimalSeparator(locale) {
+  return Intl.NumberFormat(locale).formatToParts(.1).find(p => p.type === 'decimal').value;
+}
 
 /**
  * Convert a string to camelCase.
@@ -40,7 +47,7 @@ export function expandScientificNotation(str, truncate = false) {
   }
   const e = Number(exponent);
   let expanded = null;
-  if (mantissa.indexOf(DECIMAL_SEPARATOR) === -1) {
+  if (mantissa.indexOf(getDecimalSeparator()) === -1) {
     expanded = mantissa + '0'.repeat(e);
   }
   else {
@@ -201,7 +208,7 @@ export class CSVNormalizer extends TransformStream {
 
     // Excel added extra characters from setting the format to "Accounting" or other custom formats
     // Delete everything that's not a number or the decimal separator
-    const replaced = original.replaceAll(new RegExp(`[^0-9${DECIMAL_SEPARATOR}]`, 'g'), '');
+    const replaced = original.replaceAll(new RegExp(`[^0-9${getDecimalSeparator()}]`, 'g'), '');
     const num2 = Number(replaced);
     if (!Number.isNaN(num2) && replaced !== '') {
       return num2;
@@ -240,9 +247,10 @@ export class CSVNormalizer extends TransformStream {
     // Excel added extra characters from setting the format to "Accounting" or other custom formats
     // Delete everything that's not a number or the decimal separator
     try {
-      const str = original.replaceAll(new RegExp(`[^0-9${DECIMAL_SEPARATOR}]`, 'g'), '');
+      const decimalSeparator = getDecimalSeparator();
+      const str = original.replaceAll(new RegExp(`[^0-9${decimalSeparator}]`, 'g'), '');
       if (str === '') { throw new Error(); }
-      return BigInt(str.split(DECIMAL_SEPARATOR)[0]).toString();
+      return BigInt(str.split(decimalSeparator)[0]).toString();
     } catch { }
 
     // Otherwise, pass through as string
index 4ca8e494757a07113df8e700b6f49eb90dd9d4a7..e41a8762123ac0ef28f8365e56a49c82b94ba5da 100644 (file)
@@ -1,9 +1,33 @@
 import { suite, test } from 'node:test';
-import { deepStrictEqual, throws } from 'node:assert/strict';
+import { ok, deepStrictEqual, throws } from 'node:assert/strict';
 
 import { csvStreamEqualWritable, createCSVMockStream, assertConsole } from './utils.js';
 import { CSVReader } from '../src/core.js';
-import { toCamelCase, expandScientificNotation, CSVNormalizer, CSVDenormalizer } from '../src/normalization.js';
+import { getDecimalSeparator, toCamelCase, expandScientificNotation, CSVNormalizer, CSVDenormalizer } from '../src/normalization.js';
+
+suite('getDecimalSeparator', { concurrency: true }, () => {
+  test('returns a value if no locale is specified', { concurrency: true }, () => {
+    const s = getDecimalSeparator();
+    ok(typeof s === 'string');
+    ok(s.length === 1);
+  });
+  test('returns "." for locale identifier "en-US"', { concurrency: true }, () => {
+    const s = getDecimalSeparator('en-US');
+    deepStrictEqual(s, '.');
+  });
+  test('returns "," for locale identifier "de-DE"', { concurrency: true }, () => {
+    const s = getDecimalSeparator('de-DE');
+    deepStrictEqual(s, ',');
+  });
+  test('returns "." for `Intl.Locale` object set to "en-US"', { concurrency: true }, () => {
+    const s = getDecimalSeparator(new Intl.Locale('en-US'));
+    deepStrictEqual(s, '.');
+  });
+  test('returns "," for `Intl.Locale` object set to "de-DE"', { concurrency: true }, () => {
+    const s = getDecimalSeparator(new Intl.Locale('de-DE'));
+    deepStrictEqual(s, ',');
+  });
+});
 
 suite('toCamelCase', { concurrency: true }, () => {
   const vectors = [
This page took 0.042707 seconds and 4 git commands to generate.