From 7c77c0c68c2a4def4e60ca07fc713d4110538e15 Mon Sep 17 00:00:00 2001 From: Sotaro Hikita Date: Sun, 20 Sep 2026 01:33:41 +0900 Subject: [PATCH] GH-1300: Use Locale.ROOT when building C Data Interface format strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Format.asString built the FixedSizeList, FixedSizeBinary and Decimal format strings with String.format and no explicit Locale, so JVMs whose default locale uses digits outside ASCII exported strings such as "+w:٨" that other Arrow implementations cannot parse. Closes #1300. --- c/src/main/java/org/apache/arrow/c/Format.java | 8 ++++---- .../java/org/apache/arrow/c/FormatTest.java | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/c/src/main/java/org/apache/arrow/c/Format.java b/c/src/main/java/org/apache/arrow/c/Format.java index 7ce99614d2..7f546fca2c 100644 --- a/c/src/main/java/org/apache/arrow/c/Format.java +++ b/c/src/main/java/org/apache/arrow/c/Format.java @@ -63,10 +63,10 @@ static String asString(ArrowType arrowType) { { ArrowType.Decimal type = (ArrowType.Decimal) arrowType; if (type.getBitWidth() == 128) { - return String.format("d:%d,%d", type.getPrecision(), type.getScale()); + return String.format(Locale.ROOT, "d:%d,%d", type.getPrecision(), type.getScale()); } return String.format( - "d:%d,%d,%d", type.getPrecision(), type.getScale(), type.getBitWidth()); + Locale.ROOT, "d:%d,%d,%d", type.getPrecision(), type.getScale(), type.getBitWidth()); } case Duration: { @@ -88,12 +88,12 @@ static String asString(ArrowType arrowType) { case FixedSizeBinary: { ArrowType.FixedSizeBinary type = (ArrowType.FixedSizeBinary) arrowType; - return String.format("w:%d", type.getByteWidth()); + return String.format(Locale.ROOT, "w:%d", type.getByteWidth()); } case FixedSizeList: { ArrowType.FixedSizeList type = (ArrowType.FixedSizeList) arrowType; - return String.format("+w:%d", type.getListSize()); + return String.format(Locale.ROOT, "+w:%d", type.getListSize()); } case FloatingPoint: { diff --git a/c/src/test/java/org/apache/arrow/c/FormatTest.java b/c/src/test/java/org/apache/arrow/c/FormatTest.java index c773324330..3691ca095d 100644 --- a/c/src/test/java/org/apache/arrow/c/FormatTest.java +++ b/c/src/test/java/org/apache/arrow/c/FormatTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Locale; import org.apache.arrow.vector.types.DateUnit; import org.apache.arrow.vector.types.FloatingPointPrecision; import org.apache.arrow.vector.types.IntervalUnit; @@ -156,4 +157,20 @@ public void testAsType() assertThrows(UnsupportedOperationException.class, () -> Format.asType(":", 0L)); assertThrows(NumberFormatException.class, () -> Format.asType("w:1,2,3", 0L)); } + + @Test + public void testAsStringIgnoresDefaultLocale() { + // Locales that use digits other than 0-9 (Arabic-Indic, Bengali, Devanagari, ...) + // must not leak into the format string, which other implementations parse as ASCII. + Locale saved = Locale.getDefault(); + try { + Locale.setDefault(Locale.forLanguageTag("ar-EG")); + assertEquals("d:10,2", Format.asString(new ArrowType.Decimal(10, 2, 128))); + assertEquals("d:10,2,256", Format.asString(new ArrowType.Decimal(10, 2, 256))); + assertEquals("w:16", Format.asString(new ArrowType.FixedSizeBinary(16))); + assertEquals("+w:8", Format.asString(new ArrowType.FixedSizeList(8))); + } finally { + Locale.setDefault(saved); + } + } }