gnucash maint: Bug 797067 - Date displayed incorrectly in register take two.

John Ralls jralls at code.gnucash.org
Sat Feb 9 16:06:24 EST 2019


Updated	 via  https://github.com/Gnucash/gnucash/commit/7d7da8e2 (commit)
	from  https://github.com/Gnucash/gnucash/commit/67dbfca0 (commit)



commit 7d7da8e2c44d117e3124a671e02b9d7f248d4d92
Author: John Ralls <jralls at ceridwen.us>
Date:   Fri Feb 8 11:40:21 2019 -0800

    Bug 797067 - Date displayed incorrectly in register take two.
    
    Revert using boost::locale to generate std::locales as boost::locale-
    generated locales don't implement std::locale::facet and there was
    a bug in the boost::locale ICU wrapper code that caused the wrong year
    to be output for the last 3 days of December.
    
    GCC's libstdc++ supports only the "C" locale on Windows and throws if
    one attempts to create any other kind. For dates we work around this
    by using wstrftime() to format according to locale and then convert
    the UTF16 string to UTF8. wstrftime() interprets the time zone flags
    %z, %Z, and %ZP differently so we process those first before calling
    strftime. This will have the unfortunate effect of not localizing
    timezone names but it's as close as we can get.

diff --git a/libgnucash/core-utils/gnc-locale-utils.cpp b/libgnucash/core-utils/gnc-locale-utils.cpp
index e3de96190..6707b39f4 100644
--- a/libgnucash/core-utils/gnc-locale-utils.cpp
+++ b/libgnucash/core-utils/gnc-locale-utils.cpp
@@ -19,7 +19,10 @@
  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
  * Boston, MA  02110-1301,  USA       gnu at gnu.org                   *
 \********************************************************************/
-
+extern "C"
+{
+#include <glib.h>
+}
 #include <clocale>
 #include <boost/locale.hpp>
 #include "gnc-locale-utils.hpp"
@@ -50,29 +53,22 @@ gnc_get_locale()
     tried_already = true;
       try
       {
-	  cached = gen("");
+	cached = std::locale("");
       }
       catch (const std::runtime_error& err)
       {
-	  std::string c_locale(setlocale(LC_ALL, nullptr));
+#ifdef __MINGW32__
+	  char* locale = g_win32_getlocale();
+#else
+	  char* locale = g_strdup(setlocale(LC_ALL, ""));
+#endif
+	  std::string c_locale(locale);
 	  std::cerr << "[gnc_get_locale] Failed to create app-default locale from " << c_locale << " because " << err.what() << "\n";
-	  auto dot = c_locale.find(".");
-	  if (dot != std::string::npos)
-	  {
-	      try
-	      {
-		  cached = gen(c_locale.substr(0, dot));
-	      }
-	      catch (std::runtime_error& err2)
-	      {
-		std::cerr << "[gnc_get_locale] Failed to create app-default locale from " << c_locale << " because " << err.what() << " so using the 'C' locale for C++.\n";
-	      }
-	  }
-	  else
-	  {
 	      std::cerr << "[gnc_get_locale] Using the 'C' locale for C++\n";
-	  }
+	  g_free(locale);
       }
   }
   return cached;
 }
+
+
diff --git a/libgnucash/engine/gnc-datetime.cpp b/libgnucash/engine/gnc-datetime.cpp
index e1946ec9b..2192ffd3f 100644
--- a/libgnucash/engine/gnc-datetime.cpp
+++ b/libgnucash/engine/gnc-datetime.cpp
@@ -40,6 +40,9 @@ extern "C"
 #include <sstream>
 #include <string>
 #include <vector>
+#ifdef __MINGW32__
+#include <codecvt>
+#endif
 #include <gnc-locale-utils.hpp>
 #include "gnc-timezone.hpp"
 #include "gnc-datetime.hpp"
@@ -263,7 +266,9 @@ public:
     void today() { m_greg = boost::gregorian::day_clock::local_day(); }
     ymd year_month_day() const;
     std::string format(const char* format) const;
-    std::string format_zulu(const char* format) const;
+    std::string format_zulu(const char* format) const {
+	return this->format(format);
+    }
 private:
     Date m_greg;
 
@@ -428,27 +433,98 @@ normalize_format (const std::string& format)
         });
     return normalized;
 }
+#ifdef __MINGW32__
+constexpr size_t DATEBUFLEN = 100;
+static std::string
+win_date_format(std::string format, struct tm tm)
+{
+    wchar_t buf[DATEBUFLEN];
+    memset(buf, 0, DATEBUFLEN);
+    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conv;
+    auto numchars = wcsftime(buf, DATEBUFLEN - 1, conv.from_bytes(format).c_str(), &tm);
+    return conv.to_bytes(buf);
+}
+
+/* Microsoft's strftime uses the time zone flags differently from
+ * boost::date_time so we need to handle any before passing the
+ * format string to strftime.
+ */
+inline std::string
+win_format_tz_abbrev (std::string format, TZ_Ptr tz, bool is_dst)
+{
+    size_t pos = format.find("%z");
+    if (pos != std::string::npos)
+    {
+	auto tzabbr = tz->has_dst() && is_dst ? tz->dst_zone_abbrev() :
+	    tz->std_zone_abbrev();
+	format.replace(pos, 2, tzabbr);
+    }
+    return format;
+}
+
+inline std::string
+win_format_tz_name (std::string format, TZ_Ptr tz, bool is_dst)
+{
+    size_t pos = format.find("%Z");
+    if (pos != std::string::npos)
+    {
+	auto tzname =  tz->has_dst() && is_dst ? tz->dst_zone_name() :
+	    tz->std_zone_name();
+	format.replace(pos, 2, tzname);
+    }
+    return format;
+}
 
+inline std::string
+win_format_tz_posix (std::string format, TZ_Ptr tz)
+{
+    size_t pos = format.find("%ZP");
+    if (pos != std::string::npos)
+	format.replace(pos, 2, tz->to_posix_string());
+    return format;
+}
+
+#endif
 std::string
 GncDateTimeImpl::format(const char* format) const
 {
-    namespace as = boost::locale::as;
+#ifdef __MINGW32__
+    auto tz = m_time.zone();
+    auto tm =  static_cast<struct tm>(*this);
+    auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
+    sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
+    sformat = win_format_tz_posix(sformat, tz);
+    return win_date_format(sformat, tm);
+#else
+    using Facet = boost::local_time::local_time_facet;
+    auto output_facet(new Facet(normalize_format(format).c_str()));
     std::stringstream ss;
-    ss.imbue(gnc_get_locale());
-    ss << as::ftime(format)
-       << as::time_zone(m_time.zone()->std_zone_name())
-       << static_cast<time64>(*this);
+    ss.imbue(std::locale(gnc_get_locale(), output_facet));
+    ss << m_time;
     return ss.str();
+#endif
 }
 
 std::string
 GncDateTimeImpl::format_zulu(const char* format) const
 {
-    namespace as = boost::locale::as;
+#ifdef __MINGW32__
+    auto tz = m_time.zone();
+    auto tm =  static_cast<struct tm>(*this);
+    auto sformat = win_format_tz_abbrev(format, tz, tm.tm_isdst);
+    sformat = win_format_tz_name(sformat, tz, tm.tm_isdst);
+    sformat = win_format_tz_posix(sformat, tz);
+    return win_date_format(sformat, utc_tm());
+#else
+    using Facet = boost::local_time::local_time_facet;
+    auto offset = m_time.local_time() - m_time.utc_time();
+    auto zulu_time = m_time - offset;
+    auto output_facet(new Facet(normalize_format(format).c_str()));
     std::stringstream ss;
-    ss.imbue(gnc_get_locale());
-    ss << as::ftime(format) << as::gmt << static_cast<time64>(*this);
+    ss.imbue(std::locale(gnc_get_locale(), output_facet));
+    ss << zulu_time;
     return ss.str();
+#endif
 }
 
 std::string
@@ -517,14 +593,17 @@ GncDateImpl::year_month_day() const
 std::string
 GncDateImpl::format(const char* format) const
 {
+#ifdef __MINGW32__
+    return win_date_format(format, to_tm(m_greg));
+#else
     using Facet = boost::gregorian::date_facet;
     std::stringstream ss;
     //The stream destructor frees the facet, so it must be heap-allocated.
     auto output_facet(new Facet(normalize_format(format).c_str()));
-    // FIXME Rather than imbueing a locale below we probably should set std::locale::global appropriately somewhere.
     ss.imbue(std::locale(gnc_get_locale(), output_facet));
     ss << m_greg;
     return ss.str();
+#endif
 }
 
 bool operator<(const GncDateImpl& a, const GncDateImpl& b) { return a.m_greg < b.m_greg; }
diff --git a/libgnucash/engine/test/gtest-gnc-datetime.cpp b/libgnucash/engine/test/gtest-gnc-datetime.cpp
index 970ad0d46..debcd00f9 100644
--- a/libgnucash/engine/test/gtest-gnc-datetime.cpp
+++ b/libgnucash/engine/test/gtest-gnc-datetime.cpp
@@ -360,7 +360,11 @@ TEST(gnc_datetime_constructors, test_gncdate_BST_transition)
 {
     const ymd begins = {2018, 03, 25};
     const ymd ends = {2018, 10, 28};
+#ifdef __MINGW32__
+    TimeZoneProvider tzp{"GMT Standard Time"};
+#else
     TimeZoneProvider tzp("Europe/London");
+#endif
     _set_tzp(tzp);
     GncDateTime btime(GncDate(begins.year, begins.month, begins.day), DayPart::start);
     GncDateTime etime(GncDate(ends.year, ends.month, ends.day), DayPart::start);
@@ -392,7 +396,7 @@ TEST(gnc_datetime_constructors, test_gncdate_neutral_constructor)
     if (gncdt.offset() >= max_western_offset &&
         gncdt.offset() <= max_eastern_offset)
     {
-        EXPECT_EQ(atime.format("%d-%m-%Y %H:%M:%S %Z"), "20-04-2017 10:59:00 GMT");
+        EXPECT_EQ(atime.format("%d-%m-%Y %H:%M:%S %Z"), "20-04-2017 10:59:00 UTC");
     }
 }
 



Summary of changes:
 libgnucash/core-utils/gnc-locale-utils.cpp    | 32 ++++-----
 libgnucash/engine/gnc-datetime.cpp            | 99 ++++++++++++++++++++++++---
 libgnucash/engine/test/gtest-gnc-datetime.cpp |  6 +-
 3 files changed, 108 insertions(+), 29 deletions(-)



More information about the gnucash-changes mailing list