Add GncDateTime constructor taking a GncDate as parameter

As a GncDate doesn't have any time information, this has to be made up.
GnuCash uses 3 times-of-day quite a lot:
- start-of-day (00:00 local time)
- end-of-day (23:59 local time)
- neutral time (10:59 utc, chosen to minimize
  day offsetting when converting to/from localtime)

A second parameter to the new constructor will tell it to use one of these presets.
This commit is contained in:
Geert Janssens 2017-04-20 22:58:35 +02:00
parent 724f8aa784
commit d1ff16138e
3 changed files with 92 additions and 0 deletions

View File

@ -100,6 +100,7 @@ public:
GncDateTimeImpl() : m_time(boost::local_time::local_sec_clock::local_time(tzp.get(boost::gregorian::day_clock::local_day().year()))) {}
GncDateTimeImpl(const time64 time) : m_time(LDT_from_unix_local(time)) {}
GncDateTimeImpl(const struct tm tm) : m_time(LDT_from_struct_tm(tm)) {}
GncDateTimeImpl(const GncDateImpl& date, DayPart part = DayPart::neutral);
GncDateTimeImpl(const std::string str);
GncDateTimeImpl(PTime&& pt) : m_time(pt, tzp.get(pt.date().year())) {}
GncDateTimeImpl(LDT&& ldt) : m_time(ldt) {}
@ -132,10 +133,46 @@ public:
std::string format_zulu(const char* format) const;
private:
Date m_greg;
friend GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl&, DayPart);
};
/* Member function definitions for GncDateTimeImpl.
*/
GncDateTimeImpl::GncDateTimeImpl(const GncDateImpl& date, DayPart part) :
m_time(unix_epoch, utc_zone)
{
using TD = boost::posix_time::time_duration;
static const TD start(0, 0, 0);
static const TD neutral(10, 59, 0);
static const TD end(23,59, 0);
TD time_of_day;
switch (part)
{
case DayPart::start:
time_of_day = start;
break;
case DayPart::neutral:
time_of_day = neutral;
break;
case DayPart::end:
time_of_day = end;
break;
}
try
{
auto tz = utc_zone;
if (part != DayPart::neutral)
tz = tzp.get(date.m_greg.year());
m_time = LDT(date.m_greg, time_of_day, tz, LDT::EXCEPTION_ON_ERROR);
}
catch(boost::gregorian::bad_year)
{
throw(std::invalid_argument("Time value is outside the supported year range."));
}
}
GncDateTimeImpl::GncDateTimeImpl(const std::string str) :
m_time(unix_epoch, utc_zone)
{
@ -286,6 +323,9 @@ GncDateTime::GncDateTime(const std::string str) :
m_impl(new GncDateTimeImpl(str)) {}
GncDateTime::~GncDateTime() = default;
GncDateTime::GncDateTime(const GncDate& date, DayPart part) :
m_impl(new GncDateTimeImpl(*(date.m_impl), part)) {}
void
GncDateTime::now()
{

View File

@ -36,6 +36,12 @@ typedef struct
int day; //1-31
} ymd;
enum class DayPart {
start, // 00:00
neutral, // 10:59
end, // 23:59
};
class GncDateTimeImpl;
class GncDateImpl;
class GncDate;
@ -77,6 +83,21 @@ public:
* @exception std::invalid_argument if the year is outside the constraints.
*/
GncDateTime(const struct tm tm);
/** Construct a GncDateTime from a GncDate. As a GncDate doesn't contain time
* information, the time will be set depending on the second parameter
* to start of day, neutral or end of day.
* @param date: A GncDate representing a date.
* @param part: An optinoal DayPart indicating which time to use in the conversion.
* This can be "DayPart::start" for start of day (00:00 local time),
* "DayPart::neutral" for a neutral time (10:59 UTC, chosen to have the
* least chance of date changes when crossing timezone borders),
* "DayPart::end" for end of day (23:59 local time).
* If omitted part defaults to DayPart::neutral.
* Note the different timezone used for DayPart::neutral compared to the other
* two options!
* @exception std::invalid_argument if the year is outside the constraints.
*/
GncDateTime(const GncDate& date, DayPart part = DayPart::neutral);
/** Construct a GncDateTime
* @param str: A string representing the date and time in some
* recognizable format. Note that if a timezone is not specified the
@ -174,6 +195,8 @@ class GncDate
private:
std::unique_ptr<GncDateImpl> m_impl;
friend GncDateTime::GncDateTime(const GncDate&, DayPart);
};
#endif // __GNC_DATETIME_HPP__

View File

@ -71,6 +71,35 @@ TEST(gnc_datetime_constructors, test_struct_tm_constructor)
EXPECT_EQ(tm1.tm_min, tm.tm_min);
}
/* Note: the following tests for the constructor taking a GncDate as input parameter
* use GncDateTime's format() member function to simplify the result checking.
* If there's a bug in this member function, these tests may fail in addition
* to the format test later in the test suite. Be sure to check that later
* test as well in case any of the below constructor tests fails. */
TEST(gnc_datetime_constructors, test_gncdate_start_constructor)
{
const ymd aymd = { 2017, 04, 20 };
GncDateTime atime(GncDate(aymd.year, aymd.month, aymd.day), DayPart::start);
//Skipping timezone information as this can't be controlled.
EXPECT_EQ(atime.format("%d-%m-%Y %H:%M:%S"), "20-04-2017 00:00:00");
}
TEST(gnc_datetime_constructors, test_gncdate_end_constructor)
{
const ymd aymd = { 2046, 11, 06 };
GncDateTime atime(GncDate(aymd.year, aymd.month, aymd.day), DayPart::end);
//Skipping timezone information as this can't be controlled.
EXPECT_EQ(atime.format("%d-%m-%Y %H:%M:%S"), "06-11-2046 23:59:00");
}
TEST(gnc_datetime_constructors, test_gncdate_neutral_constructor)
{
const ymd aymd = { 2017, 04, 20 };
GncDateTime atime(GncDate(aymd.year, aymd.month, aymd.day), DayPart::neutral);
EXPECT_EQ(atime.format("%d-%m-%Y %H:%M:%S %z"), "20-04-2017 10:59:00 UTC");
}
TEST(gnc_datetime_functions, test_format)
{
GncDateTime atime(2394187200); //2045-11-13 12:00:00 Z