Replace GDateTime dependency with boost::date_time.

This commit is contained in:
John Ralls 2015-02-07 11:29:48 -08:00
parent 6673128b9f
commit 6c6153b5b6
3 changed files with 149 additions and 317 deletions

View File

@ -23,10 +23,15 @@
#ifndef __GNC_DATE_P_H__
#define __GNC_DATE_P_H__
#ifdef __cplusplus
extern "C"
{
#endif
#include <stdint.h>
#include "gnc-date.h"
#define NANOS_PER_SECOND 1000000000
#define NANOS_PER_SECOND INT32_C(1000000000)
/** Convert a given date/time format from UTF-8 to an encoding suitable for the
* strftime system call.
@ -74,4 +79,7 @@ typedef struct
Testfuncs *gnc_date_load_funcs (void);
#ifdef __cplusplus
}
#endif
#endif /* __GNC_DATE_P_H__ */

View File

@ -25,46 +25,36 @@
\********************************************************************/
#define __EXTENSIONS__
#ifdef __cplusplus
extern "C"
{
#endif
#include "config.h"
#include <glib.h>
#include <glib/gprintf.h>
/* to be renamed qofdate.c */
#include <ctype.h>
#ifdef HAVE_LANGINFO_D_FMT
# include <langinfo.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <glib.h>
#include <glib/gi18n.h>
#include "gnc-date-p.h"
#include <libintl.h>
#include "platform.h"
#include "qof.h"
#ifndef HAVE_STRPTIME
#include "strptime.h"
#ifdef HAVE_LANGINFO_D_FMT
# include <langinfo.h>
#endif
#ifndef HAVE_LOCALTIME_R
#include "localtime_r.h"
#endif
#include "platform.h"
#ifdef G_OS_WIN32
# include <windows.h>
#endif
#ifdef __cplusplus
}
#endif
#include "gnc-date.h"
#include "gnc-date-p.h"
#include "gnc-timezone.hpp"
#define N_(string) string //So that xgettext will find it
using Date = boost::gregorian::date;
using Month = boost::gregorian::greg_month;
using PTime = boost::posix_time::ptime;
using LDT = boost::local_time::local_date_time;
using Duration = boost::posix_time::time_duration;
using LDTBase = boost::local_time::local_date_time_base<PTime, boost::date_time::time_zone_base<PTime, char>>;
#ifdef HAVE_LANGINFO_D_FMT
# define GNC_D_FMT (nl_langinfo (D_FMT))
@ -101,151 +91,55 @@ static int dateCompletionBackMonths = 6;
/* This static indicates the debugging module that this .o belongs to. */
static QofLogModule log_module = QOF_MOD_ENGINE;
/* Getting a timezone is expensive, and we do it a lot. Cache the value. */
static GTimeZone*
gnc_g_time_zone_new_local (void)
{
static GTimeZone* tz = NULL;
if (tz)
return tz;
tz = g_time_zone_new_local();
return tz;
}
static GDateTime*
gnc_g_date_time_new_local (gint year, gint month, gint day, gint hour, gint minute, gdouble seconds)
{
GTimeZone *tz = gnc_g_time_zone_new_local();
GDateTime *gdt = g_date_time_new (tz, year, month, day,
hour, minute, seconds);
if (!gdt)
return gdt;
g_date_time_unref (gdt);
/* g_date_time_new truncates nanoseconds to microseconds. Sometimes in
* converting (particularly when parsing from a string) the
* nanoseconds will have lost 1/2 a femtosecond or so. Adding 1/2 a
* nano second ensures that the truncation doesn't lose a micorsecond
* in translation.
*/
seconds += 5e-10;
gdt = g_date_time_new (tz, year, month, day, hour, minute, seconds);
return gdt;
}
static GDateTime*
gnc_g_date_time_adjust_for_dst (GDateTime *gdt, GTimeZone *tz)
{
GDateTime *ngdt;
g_return_val_if_fail (gdt != NULL, NULL);
ngdt = g_date_time_to_timezone (gdt, tz);
g_date_time_unref (gdt);
gdt = g_date_time_to_timezone (ngdt, tz);
g_date_time_unref (ngdt);
return gdt;
}
GDateTime*
gnc_g_date_time_new_from_unix_local (time64 time)
{
GTimeZone *tz = gnc_g_time_zone_new_local ();
GDateTime *gdt = g_date_time_new_from_unix_utc (time);
if (gdt)
gdt = gnc_g_date_time_adjust_for_dst (gdt, tz);
return gdt;
}
static GDateTime*
gnc_g_date_time_new_from_timeval_local (const GTimeVal* tv)
{
GTimeZone *tz = gnc_g_time_zone_new_local ();
GDateTime *gdt = g_date_time_new_from_timeval_utc (tv);
if (gdt)
gdt = gnc_g_date_time_adjust_for_dst (gdt, tz);
return gdt;
}
static GDateTime*
gnc_g_date_time_new_now_local (void)
{
GTimeZone *tz = gnc_g_time_zone_new_local ();
GDateTime *gdt = g_date_time_new_now_utc ();
if (gdt)
gdt = gnc_g_date_time_adjust_for_dst (gdt, tz);
return gdt;
}
static GDateTime*
gnc_g_date_time_to_local (GDateTime* gdt)
{
GTimeZone *tz = NULL;
if (gdt)
{
tz = gnc_g_time_zone_new_local ();
gdt = gnc_g_date_time_adjust_for_dst (g_date_time_to_utc (gdt), tz);
}
return gdt;
}
typedef struct
{
GDateTime *(*new_local)(gint, gint, gint, gint, gint, gdouble);
GDateTime *(*adjust_for_dst)(GDateTime *, GTimeZone *);
GDateTime *(*new_from_unix_local)(time64);
GDateTime *(*new_from_timeval_local)(const GTimeVal *);
GDateTime *(*new_now_local)(void);
GDateTime *(*to_local)(GDateTime *);
} _GncDateTime;
#ifdef __cplusplus
extern "C"
{
// Default constructor Initializes to the current locale.
static const TimeZoneProvider tzp;
// For converting to/from POSIX time.
static const PTime unix_epoch (Date(1970, boost::gregorian::Jan, 1),
boost::posix_time::seconds(0));
/* To ensure things aren't overly screwed up by setting the nanosecond clock for boost::date_time. Don't do it, though, it doesn't get us anything and slows down the date/time library. */
#ifndef BOOST_DATE_TIME_HAS_NANOSECONDS
static constexpr uint64_t ticks_per_second = UINT64_C(1000000);
#else
static constexpr uint64_t ticks_per_second = UINT64_C(1000000000);
#endif
void _gnc_date_time_init(_GncDateTime*);
void
_gnc_date_time_init (_GncDateTime *gncdt)
static LDT
gnc_get_LDT(int year, int month, int day, int hour, int minute, int seconds)
{
gncdt->new_local = gnc_g_date_time_new_local;
gncdt->adjust_for_dst = gnc_g_date_time_adjust_for_dst;
gncdt->new_from_unix_local = gnc_g_date_time_new_from_unix_local;
gncdt->new_from_timeval_local = gnc_g_date_time_new_from_timeval_local;
gncdt->new_now_local = gnc_g_date_time_new_now_local;
gncdt->to_local = gnc_g_date_time_to_local;
Date date(year, static_cast<Month>(month), day);
Duration time(hour, minute, seconds);
auto tz = tzp.get(year);
return LDT(date, time, tz, LDTBase::NOT_DATE_TIME_ON_ERROR);
}
#ifdef __cplusplus
static LDT
LDT_from_unix_local(const time64 time)
{
PTime temp(unix_epoch.date(),
boost::posix_time::hours(time / 3600) +
boost::posix_time::seconds(time % 3600));
auto tz = tzp.get(temp.date().year());
return LDT(temp, tz);
}
template<typename T>
static time64
time64_from_date_time(T time)
{
auto duration = time - unix_epoch;
return duration.ticks() / ticks_per_second;
}
#endif
/****************** Posix Replacement Functions ***************************/
void
gnc_tm_free (struct tm* time)
{
g_slice_free1 (sizeof (struct tm), time);
}
#define MAX_TZ_SIZE
static void
gnc_g_date_time_fill_struct_tm (GDateTime *gdt, struct tm* time)
{
memset (time, 0, sizeof (struct tm));
g_date_time_get_ymd (gdt, &(time->tm_year), &(time->tm_mon), &(time->tm_mday));
time->tm_sec = g_date_time_get_second (gdt);
time->tm_min = g_date_time_get_minute (gdt);
time->tm_hour = g_date_time_get_hour (gdt);
// Watch out: struct tm has wday=0..6 with Sunday=0, but GDateTime has wday=1..7 with Sunday=7.
time->tm_wday = g_date_time_get_day_of_week (gdt) % 7;
time->tm_yday = g_date_time_get_day_of_year (gdt);
time->tm_isdst = g_date_time_is_daylight_savings (gdt);
time->tm_year -= 1900;
--time->tm_mon;
free(time);
}
struct tm*
gnc_localtime (const time64 *secs)
{
struct tm *time = static_cast<struct tm*>(g_slice_alloc0 (sizeof (struct tm)));
auto time = static_cast<struct tm*>(calloc(1, sizeof(struct tm)));
if (gnc_localtime_r (secs, time) == NULL)
{
gnc_tm_free (time);
@ -254,39 +148,41 @@ gnc_localtime (const time64 *secs)
return time;
}
/* Linux, Darwin, and MSWindows implementations of this function set the
* globals timezone and daylight; BSD doesn't have those globals, and
* Gnucash never uses them, so they're omitted from this
* implementation. Bug 704185.
*/
struct tm*
gnc_localtime_r (const time64 *secs, struct tm* time)
{
GDateTime *gdt = gnc_g_date_time_new_from_unix_local (*secs);
g_return_val_if_fail (gdt != NULL, NULL);
gnc_g_date_time_fill_struct_tm (gdt, time);
if (g_date_time_is_daylight_savings (gdt))
time->tm_isdst = 1;
try
{
auto ldt = LDT_from_unix_local(*secs);
*time = boost::local_time::to_tm(ldt);
#ifdef HAVE_STRUCT_TM_GMTOFF
time->tm_gmtoff = g_date_time_get_utc_offset (gdt) / G_TIME_SPAN_SECOND;
auto offset = ldt.zone()->base_utc_offset();
if (ldt.is_dst())
offset += ldt.zone()->dst_offset();
time->tm_gmtoff = offset.total_seconds();
#endif
g_date_time_unref (gdt);
return time;
}
catch(boost::gregorian::bad_year)
{
return NULL; //Yeah, it should be nullptr, but this is a C-linkage func.
}
return time;
}
struct tm*
gnc_gmtime (const time64 *secs)
{
struct tm *time;
GDateTime *gdt = g_date_time_new_from_unix_utc (*secs);
g_return_val_if_fail (gdt != NULL, NULL);
time = static_cast<struct tm*>(g_slice_alloc0 (sizeof (struct tm)));
gnc_g_date_time_fill_struct_tm (gdt, time);
g_date_time_unref (gdt);
return time;
auto time = static_cast<struct tm*>(calloc(1, sizeof (struct tm)));
try {
PTime pdt(unix_epoch.date(), boost::posix_time::hours(*secs / 3600) +
boost::posix_time::seconds(*secs % 3600));
*time = boost::posix_time::to_tm(pdt);
}
catch(boost::gregorian::bad_year)
{
return NULL; //Yeah, it should be nullptr, but this is a C-linkage func.
}
return time;
}
static void
@ -318,10 +214,10 @@ normalize_struct_tm (struct tm* time)
gint last_day;
++time->tm_mon;
/* GDateTime doesn't protect itself against out-of range years,
* so clamp year into GDateTime's range.
/* Gregorian_date throws if it gets an out-of-range year
* so clamp year into gregorian_date's range.
*/
if (year < 0) year = -year;
if (year < 1400) year += 1400;
if (year > 9999) year %= 10000;
normalize_time_component (&(time->tm_sec), &(time->tm_min), 60, 0);
@ -348,68 +244,30 @@ normalize_struct_tm (struct tm* time)
time64
gnc_mktime (struct tm* time)
{
GDateTime *gdt;
time64 secs;
normalize_struct_tm (time);
gdt = gnc_g_date_time_new_local (time->tm_year + 1900, time->tm_mon,
time->tm_mday, time->tm_hour,
time->tm_min, (gdouble)(time->tm_sec));
if (gdt == NULL)
{
g_warning("Invalid time passed to gnc_mktime");
return -1;
}
time->tm_mon = time->tm_mon > 0 ? time->tm_mon - 1 : 11;
// Watch out: struct tm has wday=0..6 with Sunday=0, but GDateTime has wday=1..7 with Sunday=7.
time->tm_wday = g_date_time_get_day_of_week (gdt) % 7;
time->tm_yday = g_date_time_get_day_of_year (gdt);
time->tm_isdst = g_date_time_is_daylight_savings (gdt);
#ifdef HAVE_STRUCT_TM_GMTOFF
time->tm_gmtoff = g_date_time_get_utc_offset (gdt) / G_TIME_SPAN_SECOND;
#endif
secs = g_date_time_to_unix (gdt);
g_date_time_unref (gdt);
return secs;
normalize_struct_tm (time);
return time64_from_date_time(boost::posix_time::ptime_from_tm(*time));
}
time64
gnc_timegm (struct tm* time)
{
GDateTime *gdt;
time64 secs;
normalize_struct_tm (time);
gdt = g_date_time_new_utc (time->tm_year + 1900, time->tm_mon,
time->tm_mday, time->tm_hour, time->tm_min,
(gdouble)(time->tm_sec));
time->tm_mon = time->tm_mon > 0 ? time->tm_mon - 1 : 11;
// Watch out: struct tm has wday=0..6 with Sunday=0, but GDateTime has wday=1..7 with Sunday=7.
time->tm_wday = g_date_time_get_day_of_week (gdt) % 7;
time->tm_yday = g_date_time_get_day_of_year (gdt);
time->tm_isdst = g_date_time_is_daylight_savings (gdt);
secs = g_date_time_to_unix (gdt);
g_date_time_unref (gdt);
return secs;
auto newtime = *time;
newtime.tm_gmtoff = 0;
return gnc_mktime(&newtime);
}
gchar*
char*
gnc_ctime (const time64 *secs)
{
GDateTime *gdt = gnc_g_date_time_new_from_unix_local (*secs);
gchar *string = g_date_time_format (gdt, "%a %b %e %H:%M:%S %Y");
g_date_time_unref (gdt);
return string;
return gnc_print_time64(*secs, "%a %b %e %H:%M:%S %Y");
}
time64
gnc_time (time64 *tbuf)
{
GDateTime *gdt = gnc_g_date_time_new_now_local ();
time64 secs = g_date_time_to_unix (gdt);
g_date_time_unref (gdt);
if (tbuf != NULL)
auto pdt = boost::posix_time::second_clock::local_time();
auto secs = time64_from_date_time(pdt);
if (tbuf != nullptr)
*tbuf = secs;
return secs;
}
@ -417,9 +275,8 @@ gnc_time (time64 *tbuf)
time64
gnc_time_utc (time64 *tbuf)
{
GDateTime *gdt = g_date_time_new_now_utc ();
time64 secs = g_date_time_to_unix (gdt);
g_date_time_unref (gdt);
auto pdt = boost::posix_time::second_clock::universal_time();
auto secs = time64_from_date_time(pdt);
if (tbuf != NULL)
*tbuf = secs;
return secs;
@ -434,17 +291,6 @@ gnc_difftime (const time64 secs1, const time64 secs2)
/****************************************************************************/
GDateTime*
gnc_g_date_time_new_from_timespec_local (Timespec ts)
{
GDateTime *gdt1 = gnc_g_date_time_new_from_unix_local (ts.tv_sec);
double nsecs = ((double)ts.tv_nsec + 0.5)/ 1000000000.0L;
GDateTime *gdt2 = g_date_time_add_seconds (gdt1, nsecs);
g_date_time_unref (gdt1);
g_assert (g_date_time_to_unix (gdt2) == ts.tv_sec + (nsecs >= 1.0 ? (gint64)nsecs : 0));
return gdt2;
}
const char*
gnc_date_dateformat_to_string(QofDateFormat format)
{
@ -530,6 +376,24 @@ gnc_date_string_to_monthformat(const char *fmt_str, GNCDateMonthFormat *format)
return FALSE;
}
char*
gnc_print_time64(time64 time, const char* format)
{
using Facet = boost::local_time::local_time_facet;
auto date_time = LDT_from_unix_local(time);
std::stringstream ss;
//The stream destructor frees the facet, so it must be heap-allocated.
auto output_facet(new Facet(format));
ss.imbue(std::locale(std::locale(), output_facet));
ss << date_time;
auto sstr = ss.str();
//ugly C allocation so that the ptr can be freed at the other end
char* cstr = static_cast<char*>(malloc(sstr.length() + 1));
memset(cstr, 0, sstr.length() + 1);
strncpy(cstr, sstr.c_str(), sstr.length());
return cstr;
}
/********************************************************************\
\********************************************************************/
@ -1412,10 +1276,7 @@ qof_strftime(gchar *buf, gsize max, const gchar *format, const struct tm *tm)
gchar *
gnc_date_timestamp (void)
{
GDateTime *gdt = gnc_g_date_time_new_now_local ();
gchar *timestr = g_date_time_format (gdt, "%Y%m%d%H%M%S");
g_date_time_unref (gdt);
return timestr;
return gnc_print_time64(gnc_time(nullptr), "%Y-%M-%d %H:%M%S");
}
/********************************************************************\
@ -1429,38 +1290,9 @@ gnc_date_timestamp (void)
Timespec
gnc_iso8601_to_timespec_gmt(const char *str)
{
Timespec time = { 0L, 0L };
GDateTime *gdt;
gint hour = 0, minute = 0, day = 0, month = 0, year = 0;
gchar zone[12];
gdouble second = 0.0;
gint fields;
memset (zone, 0, sizeof (zone));
if (!str)
return time;
fields = sscanf (str, ISO_DATE_FORMAT, &year, &month,
&day, &hour, &minute, &second, zone);
if (fields < 1)
return time;
else if (fields > 6 && strlen (zone) > 0) /* Date string included a timezone */
{
GTimeZone *tz = g_time_zone_new (zone);
second += 5e-10;
gdt = g_date_time_new (tz, year, month, day, hour, minute, second);
}
else /* No zone info, assume UTC */
{
second += 5e-10;
gdt = g_date_time_new_utc (year, month, day, hour, minute, second);
}
time.tv_sec = g_date_time_to_unix (gdt);
time.tv_nsec = g_date_time_get_microsecond (gdt) * 1000;
g_date_time_unref (gdt);
return time;
auto pdt = boost::posix_time::time_from_string(str);
auto time = time64_from_date_time(pdt);
return {time, 0};
}
/********************************************************************\
@ -1469,28 +1301,20 @@ gnc_iso8601_to_timespec_gmt(const char *str)
char *
gnc_timespec_to_iso8601_buff (Timespec ts, char * buff)
{
const gchar *fmt1 = "%Y-%m-%d %H:%M", *fmt2 = "%s:%02d.%06d %s";
GDateTime *gdt;
gchar *time_base, *tz;
constexpr size_t max_iso_date_length = 32;
std::string fmt1 = "%Y-%m-%d %H:%M";
g_return_val_if_fail (buff != NULL, NULL);
gdt = gnc_g_date_time_new_from_timespec_local (ts);
g_return_val_if_fail (gdt != NULL, NULL);
time_base = g_date_time_format (gdt, fmt1);
#ifdef G_OS_WIN32
tz = g_date_time_format (gdt, "%Z");
fmt1 += "%Z";
#else
tz = g_date_time_format (gdt, "%z");
fmt1 += "%z";
#endif
snprintf (buff, MAX_DATE_LENGTH, fmt2, time_base,
g_date_time_get_second (gdt), g_date_time_get_microsecond (gdt),
tz);
g_free (time_base);
g_free (tz);
g_date_time_unref (gdt);
char* str = gnc_print_time64(ts.tv_sec, fmt1.c_str());
strncpy (buff, str, max_iso_date_length);
free(str);
return buff + strlen (buff);
}
void
@ -1614,16 +1438,13 @@ GDate timespec_to_gdate (Timespec ts)
GDate* gnc_g_date_new_today ()
{
GDateTime *gdt = gnc_g_date_time_new_now_local ();
gint day, month, year;
GDate *result;
g_date_time_get_ymd (gdt, &year, &month, &day);
result = g_date_new_dmy (day, static_cast<GDateMonth>(month), year);
g_date_time_unref (gdt);
g_assert(g_date_valid (result));
return result;
auto pdt = boost::posix_time::second_clock::local_time();
auto ymd = pdt.date().year_month_day();
auto month = static_cast<GDateMonth>(ymd.month.as_number());
auto result = g_date_new_dmy (ymd.day, month, ymd.year);
g_assert(g_date_valid (result));
return result;
}
Timespec gdate_to_timespec (GDate d)

View File

@ -238,12 +238,6 @@ time64 gnc_time_utc (time64 *tbuf);
*/
gdouble gnc_difftime (const time64 secs1, const time64 secs2);
/** Wrapper for g_date_time_new_from_unix_local() that takes special care on
* windows to take the local timezone into account. On unix, it just calles the
* g_date function. */
GDateTime*
gnc_g_date_time_new_from_unix_local (time64 time);
/** \brief free a struct tm* created with gnc_localtime() or gnc_gmtime()
* \param time: The struct tm* to be freed.
*/
@ -280,6 +274,19 @@ Note the reversed return values!
*/
gboolean gnc_date_string_to_monthformat(const gchar *format_string,
GNCDateMonthFormat *format);
/** \brief print a time64 as a date string per format
* \param time
* \param format A date format conforming to the strftime format rules.
* \return a raw heap-allocated char* which must be freed.
*/
char* gnc_print_time64(time64 time, const char* format);
/** Returns a newly allocated date of the current clock time, taken from
* time(2). The caller must g_date_free() the object afterwards. */
GDate* gnc_g_date_new_today (void);
// @}
/* Datatypes *******************************************************/
@ -338,10 +345,6 @@ void timespecFromTime64 (Timespec *ts, time64 t );
/** Turns a Timespec into a time64 */
time64 timespecToTime64 (Timespec ts);
/** Returns a newly allocated date of the current clock time, taken from
* time(2). The caller must g_date_free() the object afterwards. */
GDate* gnc_g_date_new_today (void);
/** Turns a Timespec into a GDate */
GDate timespec_to_gdate (Timespec ts);