diff --git a/libgnucash/core-utils/gnc-locale-utils.cpp b/libgnucash/core-utils/gnc-locale-utils.cpp index e3de961903..6707b39f4c 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@gnu.org * \********************************************************************/ - +extern "C" +{ +#include +} #include #include #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 e1946ec9bd..2192ffd3fe 100644 --- a/libgnucash/engine/gnc-datetime.cpp +++ b/libgnucash/engine/gnc-datetime.cpp @@ -40,6 +40,9 @@ extern "C" #include #include #include +#ifdef __MINGW32__ +#include +#endif #include #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, 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(*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(*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(*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(*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 970ad0d46b..debcd00f93 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"); } }