Hi,

While running the TAP tests on Windows with the system locale set to
Russian, I found that frontend programs don't honor LC_NUMERIC from the
environment.

PostgreSQL::Test::Utils explicitly sets:

    $ENV{LC_NUMERIC} = 'C';

but pg_test_timing still produces output such as:

    Average loop time including overhead: 21,30 ns
    Observed timing durations up to 99,9900%:

instead of using a dot as the decimal separator.

This is not specific to pg_test_timing.  For example, psql also fails to
parse decimal arguments such as:

    \watch 0.01

when the Windows user locale uses a comma as the decimal separator.

I reduced this to the behavior of the Windows CRT.  With the environment:

    LANG=C
    LC_ALL=C
    LC_NUMERIC=C

a small MSVC test program shows that:

    setlocale(LC_ALL, "");

selects the Windows user locale (Russian_Russia.1251), with "," as the
decimal separator.  Calling:

    setlocale(LC_NUMERIC, "C");

explicitly afterwards restores the expected "." separator.

For comparison, on Linux I checked the locale selection behavior:

    LANG=C                                      -> "."
    LANG=C, LC_NUMERIC=ru_RU.UTF-8             -> ","
    LC_ALL=C, LC_NUMERIC=ru_RU.UTF-8           -> "."
    LANG=C, LC_NUMERIC=""                       -> "."

The attached patch keeps the existing Windows behavior when LC_NUMERIC
is unset or empty, but explicitly applies a non-empty LC_NUMERIC
environment setting after setlocale(LC_ALL, "").

With the patch, pg_test_timing uses "." when LC_NUMERIC=C, and psql
correctly accepts decimal \watch intervals under a Russian Windows
system locale.

Tested on PostgreSQL master (20devel), Windows 11, MSVC 19.44.

Regards,
Andrew
From 74592b796d87e369e8f35d06e880e271a2ea4648 Mon Sep 17 00:00:00 2001
From: Andrew Bille <[email protected]>
Date: Mon, 21 Sep 2026 09:36:06 +0300
Subject: [PATCH] Honor LC_NUMERIC environment variable on Windows

The Windows CRT does not honor the LC_NUMERIC environment variable when
setlocale(LC_ALL, "") is called.  Instead, LC_NUMERIC is initialized
from the Windows user locale.

This differs from POSIX behavior and can cause frontend programs to use
an unexpected decimal separator even when LC_NUMERIC has been explicitly
set.  In particular, PostgreSQL TAP tests set LC_NUMERIC=C, but Windows
frontend programs can still format and parse numbers using the system
locale.

After initializing the locale normally, explicitly apply a non-empty
LC_NUMERIC environment setting on Windows.
---
 src/common/exec.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/src/common/exec.c b/src/common/exec.c
index 2881aa92ca6..45957b7a721 100644
--- a/src/common/exec.c
+++ b/src/common/exec.c
@@ -437,6 +437,21 @@ set_pglocale_pgservice(const char *argv0, const char *app)
 	{
 		setlocale(LC_ALL, "");
 
+#ifdef WIN32
+		/*
+		 * Unlike POSIX implementations, the Windows CRT does not honor
+		 * LC_NUMERIC from the environment when setlocale() is called with
+		 * an empty locale name.  Apply an explicitly specified LC_NUMERIC
+		 * setting separately.
+		 */
+		{
+			const char *lc_numeric = getenv("LC_NUMERIC");
+
+			if (lc_numeric != NULL && lc_numeric[0] != '\0')
+				setlocale(LC_NUMERIC, lc_numeric);
+		}
+#endif
+
 		/*
 		 * One could make a case for reproducing here PostmasterMain()'s test
 		 * for whether the process is multithreaded.  Unlike the postmaster,
-- 
2.55.0.windows.3

Reply via email to