mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
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.
This commit is contained in:
parent
67dbfca0e7
commit
7d7da8e2c4
@ -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 <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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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; }
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user