Merge pull request #2401 from aaronmjones/THRIFT-3840

THRIFT-3840: C++ TJSONProtocol still using locale dependent formatting
diff --git a/build/docker/ubuntu-bionic/Dockerfile b/build/docker/ubuntu-bionic/Dockerfile
index 291aa5d..a11e9ba 100644
--- a/build/docker/ubuntu-bionic/Dockerfile
+++ b/build/docker/ubuntu-bionic/Dockerfile
@@ -253,6 +253,14 @@
     rm swift-5.1.4-RELEASE-ubuntu18.04.tar.gz && \
     swift --version
 
+# Locale(s) for cpp unit tests
+RUN apt-get install -y --no-install-recommends \
+`# Locale dependencies` \
+      locales && \
+    locale-gen en_US.UTF-8 && \
+    locale-gen de_DE.UTF-8 && \
+    update-locale
+
 # cppcheck-1.82 has a nasty cpp parser bug, so we're using something newer
 RUN apt-get install -y --no-install-recommends \
 `# Static Code Analysis dependencies` \
diff --git a/build/docker/ubuntu-disco/Dockerfile b/build/docker/ubuntu-disco/Dockerfile
index 9775fbb..de99574 100644
--- a/build/docker/ubuntu-disco/Dockerfile
+++ b/build/docker/ubuntu-disco/Dockerfile
@@ -255,6 +255,14 @@
 #     rm swift-4.2.1-RELEASE-ubuntu18.04.tar.gz && \
 #     swift --version
 
+# Locale(s) for cpp unit tests
+RUN apt-get install -y --no-install-recommends \
+`# Locale dependencies` \
+      locales && \
+    locale-gen en_US.UTF-8 && \
+    locale-gen de_DE.UTF-8 && \
+    update-locale
+
 # cppcheck-1.82 has a nasty cpp parser bug, so we're using something newer
 # don't need this on disco, nobody uses it
 # RUN apt-get install -y --no-install-recommends \
diff --git a/build/docker/ubuntu-xenial/Dockerfile b/build/docker/ubuntu-xenial/Dockerfile
index 62da2c4..441b692 100644
--- a/build/docker/ubuntu-xenial/Dockerfile
+++ b/build/docker/ubuntu-xenial/Dockerfile
@@ -235,6 +235,14 @@
 # Rust dependencies
 RUN curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain 1.40.0 -y
 
+# Locale(s) for cpp unit tests
+RUN apt-get install -y --no-install-recommends \
+`# Locale dependencies` \
+      locales && \
+    locale-gen en_US.UTF-8 && \
+    locale-gen de_DE.UTF-8 && \
+    update-locale
+
 # Clean up
 RUN rm -rf /var/cache/apt/* && \
     rm -rf /var/lib/apt/lists/* && \
diff --git a/lib/cpp/src/thrift/TToString.h b/lib/cpp/src/thrift/TToString.h
index 25780f9..79743fd 100644
--- a/lib/cpp/src/thrift/TToString.h
+++ b/lib/cpp/src/thrift/TToString.h
@@ -22,6 +22,7 @@
 
 #include <cmath>
 #include <limits>
+#include <locale>
 #include <map>
 #include <set>
 #include <sstream>
@@ -34,6 +35,7 @@
 template <typename T>
 std::string to_string(const T& t) {
   std::ostringstream o;
+  o.imbue(std::locale("C"));
   o << t;
   return o.str();
 }
@@ -42,6 +44,7 @@
 // is enabled.
 inline std::string to_string(const float& t) {
   std::ostringstream o;
+  o.imbue(std::locale("C"));
   o.precision(static_cast<std::streamsize>(std::ceil(static_cast<double>(std::numeric_limits<float>::digits * std::log10(2.0f) + 1))));
   o << t;
   return o.str();
@@ -49,6 +52,7 @@
 
 inline std::string to_string(const double& t) {
   std::ostringstream o;
+  o.imbue(std::locale("C"));
   o.precision(static_cast<std::streamsize>(std::ceil(static_cast<double>(std::numeric_limits<double>::digits * std::log10(2.0f) + 1))));
   o << t;
   return o.str();
@@ -56,6 +60,7 @@
 
 inline std::string to_string(const long double& t) {
   std::ostringstream o;
+  o.imbue(std::locale("C"));
   o.precision(static_cast<std::streamsize>(std::ceil(static_cast<double>(std::numeric_limits<long double>::digits * std::log10(2.0f) + 1))));
   o << t;
   return o.str();
diff --git a/lib/cpp/test/ToStringTest.cpp b/lib/cpp/test/ToStringTest.cpp
index 5a05ed7..722e590 100644
--- a/lib/cpp/test/ToStringTest.cpp
+++ b/lib/cpp/test/ToStringTest.cpp
@@ -19,6 +19,7 @@
 
 #include <vector>
 #include <map>
+#include <locale>
 
 #include <boost/test/unit_test.hpp>
 
@@ -40,6 +41,26 @@
   BOOST_CHECK_EQUAL(to_string("abc"), "abc");
 }
 
+BOOST_AUTO_TEST_CASE(locale_en_US_int_to_string) {
+#if _WIN32
+  std::locale::global(std::locale("en-US.UTF-8"));
+#else
+  std::locale::global(std::locale("en_US.UTF-8"));
+#endif
+  BOOST_CHECK_EQUAL(to_string(1000000), "1000000");
+}
+
+BOOST_AUTO_TEST_CASE(locale_de_DE_floating_point_to_string) {
+#if _WIN32
+  std::locale::global(std::locale("de-DE.UTF-8"));
+#else
+  std::locale::global(std::locale("de_DE.UTF-8"));
+#endif
+  BOOST_CHECK_EQUAL(to_string(1.5), "1.5");
+  BOOST_CHECK_EQUAL(to_string(1.5f), "1.5");
+  BOOST_CHECK_EQUAL(to_string(1.5L), "1.5");
+}
+
 BOOST_AUTO_TEST_CASE(empty_vector_to_string) {
   std::vector<int> l;
   BOOST_CHECK_EQUAL(to_string(l), "[]");