2015-03-26 19:59:06 -05:00
/********************************************************************\
* gnc - datetime . cpp - - Date and Time classes for GnuCash *
* *
* Copyright 2015 John Ralls < jralls @ ceridwen . us > *
* *
* This program is free software ; you can redistribute it and / or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation ; either version 2 of *
* the License , or ( at your option ) any later version . *
* *
* This program is distributed in the hope that it will be useful , *
* but WITHOUT ANY WARRANTY ; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the *
* GNU General Public License for more details . *
* *
* You should have received a copy of the GNU General Public License *
* along with this program ; if not , contact : *
* *
* Free Software Foundation Voice : + 1 - 617 - 542 - 5942 *
* 51 Franklin Street , Fifth Floor Fax : + 1 - 617 - 542 - 2652 *
* Boston , MA 02110 - 1301 , USA gnu @ gnu . org *
* *
\ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
extern " C "
{
2017-10-26 04:14:21 -05:00
# include <config.h>
2015-03-26 19:59:06 -05:00
# include "platform.h"
}
# include <boost/date_time/gregorian/gregorian.hpp>
2015-12-15 15:38:50 -06:00
# include <boost/date_time/posix_time/posix_time.hpp>
2017-12-12 10:42:32 -06:00
# include <boost/date_time/local_time/local_time.hpp>
2019-01-19 15:41:17 -06:00
# include <boost/locale.hpp>
2017-04-21 09:58:20 -05:00
# include <boost/regex.hpp>
# include <libintl.h>
2019-01-06 11:52:43 -06:00
# include <locale.h>
2017-04-21 09:58:20 -05:00
# include <map>
2015-03-26 19:59:06 -05:00
# include <memory>
2015-12-15 15:38:50 -06:00
# include <iostream>
# include <sstream>
2016-06-30 16:09:25 -05:00
# include <string>
2017-04-21 09:58:20 -05:00
# include <vector>
2019-02-08 13:40:21 -06:00
# ifdef __MINGW32__
# include <codecvt>
# endif
2019-01-06 11:52:43 -06:00
# include <gnc-locale-utils.hpp>
2015-03-26 19:59:06 -05:00
# include "gnc-timezone.hpp"
# include "gnc-datetime.hpp"
2019-01-06 11:52:43 -06:00
# include "qoflog.h"
static const char * log_module = " gnc.engine " ;
2015-03-26 19:59:06 -05:00
2017-04-21 09:58:20 -05:00
# define N_(string) string //So that xgettext will find it
2021-03-20 17:49:54 -05:00
using PTZ = boost : : local_time : : posix_time_zone ;
2015-03-26 19:59:06 -05:00
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 > > ;
2015-12-26 14:18:27 -06:00
using boost : : date_time : : not_a_date_time ;
2015-03-26 19:59:06 -05:00
using time64 = int64_t ;
2018-11-02 12:29:52 -05:00
static const TimeZoneProvider ltzp ;
static const TimeZoneProvider * tzp = & ltzp ;
2015-03-26 19:59:06 -05:00
// For converting to/from POSIX time.
static const PTime unix_epoch ( Date ( 1970 , boost : : gregorian : : Jan , 1 ) ,
2015-04-28 19:53:35 -05:00
boost : : posix_time : : seconds ( 0 ) ) ;
2015-04-28 12:06:18 -05:00
static const TZ_Ptr utc_zone ( new boost : : local_time : : posix_time_zone ( " UTC-0 " ) ) ;
2015-03-26 19:59:06 -05:00
2018-11-02 12:29:52 -05:00
/* Backdoor to enable unittests to temporarily override the timezone: */
void _set_tzp ( TimeZoneProvider & tz ) ;
void _reset_tzp ( ) ;
2015-04-07 17:36:40 -05:00
/* 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 auto ticks_per_second = INT64_C ( 1000000 ) ;
# else
static constexpr auto ticks_per_second = INT64_C ( 1000000000 ) ;
# endif
2015-03-26 19:59:06 -05:00
2017-04-21 09:58:20 -05:00
/* Vector of date formats understood by gnucash and corresponding regex
* to parse each from an external source
* Note : while the format names are using a " - " as separator , the
* regexes will accept any of " -/.' " and will also work for dates
* without separators .
*/
const std : : vector < GncDateFormat > GncDate : : c_formats ( {
GncDateFormat {
N_ ( " y-m-d " ) ,
" (?: " // either y-m-d
" (?<YEAR>[0-9]+)[-/.' ]+ "
" (?<MONTH>[0-9]+)[-/.' ]+ "
" (?<DAY>[0-9]+) "
" | " // or CCYYMMDD
" (?<YEAR>[0-9]{4}) "
" (?<MONTH>[0-9]{2}) "
" (?<DAY>[0-9]{2}) "
" ) "
} ,
GncDateFormat {
N_ ( " d-m-y " ) ,
" (?: " // either d-m-y
" (?<DAY>[0-9]+)[-/.' ]+ "
" (?<MONTH>[0-9]+)[-/.' ]+ "
" (?<YEAR>[0-9]+) "
" | " // or DDMMCCYY
" (?<DAY>[0-9]{2}) "
" (?<MONTH>[0-9]{2}) "
" (?<YEAR>[0-9]{4}) "
" ) "
} ,
GncDateFormat {
N_ ( " m-d-y " ) ,
" (?: " // either m-d-y
" (?<MONTH>[0-9]+)[-/.' ]+ "
" (?<DAY>[0-9]+)[-/.' ]+ "
" (?<YEAR>[0-9]+) "
" | " // or MMDDCCYY
" (?<MONTH>[0-9]{2}) "
" (?<DAY>[0-9]{2}) "
" (?<YEAR>[0-9]{4}) "
" ) "
} ,
// Note year is still checked for in the regexes below
// This is to be able to raise an error if one is found for a yearless date format
GncDateFormat {
( N_ ( " d-m " ) ) ,
" (?: " // either d-m(-y)
" (?<DAY>[0-9]+)[-/.' ]+ "
" (?<MONTH>[0-9]+)(?:[-/.' ]+ "
" (?<YEAR>[0-9]+))? "
" | " // or DDMM(CCYY)
" (?<DAY>[0-9]{2}) "
" (?<MONTH>[0-9]{2}) "
" (?<YEAR>[0-9]+)? "
" ) "
} ,
GncDateFormat {
( N_ ( " m-d " ) ) ,
" (?: " // either m-d(-y)
" (?<MONTH>[0-9]+)[-/.' ]+ "
" (?<DAY>[0-9]+)(?:[-/.' ]+ "
" (?<YEAR>[0-9]+))? "
" | " // or MMDD(CCYY)
" (?<MONTH>[0-9]{2}) "
" (?<DAY>[0-9]{2}) "
" (?<YEAR>[0-9]+)? "
" ) "
}
} ) ;
2015-03-26 19:59:06 -05:00
/** Private implementation of GncDateTime. See the documentation for that class.
*/
static LDT
LDT_from_unix_local ( const time64 time )
{
2015-04-28 17:23:26 -05:00
try
{
2015-04-28 19:53:35 -05:00
PTime temp ( unix_epoch . date ( ) ,
boost : : posix_time : : hours ( time / 3600 ) +
boost : : posix_time : : seconds ( time % 3600 ) ) ;
2018-11-02 12:29:52 -05:00
auto tz = tzp - > get ( temp . date ( ) . year ( ) ) ;
2015-04-28 19:53:35 -05:00
return LDT ( temp , tz ) ;
2015-04-28 17:23:26 -05:00
}
2018-04-03 21:02:44 -05:00
catch ( boost : : gregorian : : bad_year & )
2015-04-28 17:23:26 -05:00
{
2015-04-28 19:53:35 -05:00
throw ( std : : invalid_argument ( " Time value is outside the supported year range. " ) ) ;
2015-04-28 17:23:26 -05:00
}
2015-03-26 19:59:06 -05:00
}
2021-03-20 17:49:54 -05:00
/* If a date-time falls in a DST transition the LDT constructor will
* fail because either the date - time doesn ' t exist ( when starting DST
* because the transition skips an hour ) or is ambiguous ( when ending
* because the transition hour is repeated ) . We try again an hour
* later to be outside the DST transition . When starting DST that ' s
* now the correct time but at the end of DST we need to set the
* returned time back an hour .
*/
static LDT
LDT_with_pushup ( const Date & tdate , const Duration & tdur , const TZ_Ptr tz ,
bool putback )
{
static const boost : : posix_time : : hours pushup { 1 } ;
LDT ldt { tdate , tdur + pushup , tz , LDTBase : : NOT_DATE_TIME_ON_ERROR } ;
if ( ldt . is_special ( ) )
{
std : : string error { " Couldn't create a valid datetime at " } ;
error + = to_simple_string ( tdate ) + " " ;
error + = to_simple_string ( tdur ) + " TZ " ;
error + = tz - > std_zone_abbrev ( ) ;
throw ( std : : invalid_argument { error } ) ;
}
if ( putback )
ldt - = pushup ;
return ldt ;
}
2015-03-26 19:59:06 -05:00
2015-04-26 18:44:39 -05:00
static LDT
2021-03-20 17:49:54 -05:00
LDT_from_date_time ( const Date & tdate , const Duration & tdur , const TZ_Ptr tz )
2015-04-26 18:44:39 -05:00
{
2018-05-25 11:18:06 -05:00
2015-04-28 17:23:26 -05:00
try
{
2017-12-12 10:42:32 -06:00
LDT ldt ( tdate , tdur , tz , LDTBase : : EXCEPTION_ON_ERROR ) ;
return ldt ;
2015-04-28 17:23:26 -05:00
}
2021-03-20 17:49:54 -05:00
catch ( const boost : : local_time : : time_label_invalid & err )
2015-04-28 17:23:26 -05:00
{
2021-03-20 17:49:54 -05:00
return LDT_with_pushup ( tdate , tdur , tz , false ) ;
2015-04-28 17:23:26 -05:00
}
2021-03-20 17:49:54 -05:00
catch ( const boost : : local_time : : ambiguous_result & err )
2017-12-12 10:42:32 -06:00
{
2021-03-20 17:49:54 -05:00
return LDT_with_pushup ( tdate , tdur , tz , true ) ;
2017-12-12 10:42:32 -06:00
}
2021-03-20 17:49:54 -05:00
catch ( boost : : gregorian : : bad_year & )
2017-12-12 10:42:32 -06:00
{
2021-03-20 17:49:54 -05:00
throw ( std : : invalid_argument ( " Time value is outside the supported year range. " ) ) ;
2017-12-12 10:42:32 -06:00
}
2021-03-20 17:49:54 -05:00
2015-04-26 18:44:39 -05:00
}
2021-03-20 17:49:54 -05:00
static LDT
LDT_from_date_daypart ( const Date & date , DayPart part , const TZ_Ptr tz )
{
using hours = boost : : posix_time : : hours ;
static const Duration day_begin { 0 , 0 , 0 } ;
static const Duration day_neutral { 10 , 59 , 0 } ;
static const Duration day_end { 23 , 59 , 59 } ;
switch ( part )
{
case DayPart : : start :
return LDT_from_date_time ( date , day_begin , tz ) ;
case DayPart : : end :
return LDT_from_date_time ( date , day_end , tz ) ;
default : // To stop gcc from emitting a control reaches end of non-void function.
case DayPart : : neutral :
PTime pt { date , day_neutral } ;
LDT lt { pt , tz } ;
auto offset = lt . local_time ( ) - lt . utc_time ( ) ;
if ( offset < hours ( - 10 ) )
lt - = hours ( offset . hours ( ) + 10 ) ;
if ( offset > hours ( 13 ) )
lt + = hours ( 13 - offset . hours ( ) ) ;
return lt ;
}
}
static LDT
LDT_from_struct_tm ( const struct tm tm )
{
2021-06-08 15:14:30 -05:00
try
{
Date tdate { boost : : gregorian : : date_from_tm ( tm ) } ;
Duration tdur { boost : : posix_time : : time_duration ( tm . tm_hour , tm . tm_min ,
tm . tm_sec , 0 ) } ;
TZ_Ptr tz { tzp - > get ( tdate . year ( ) ) } ;
return LDT_from_date_time ( tdate , tdur , tz ) ;
}
catch ( const boost : : gregorian : : bad_year & )
{
throw ( std : : invalid_argument { " Time value is outside the supported year range. " } ) ;
}
2021-03-20 17:49:54 -05:00
}
2017-12-12 23:01:58 -06:00
2018-11-02 12:29:52 -05:00
void
_set_tzp ( TimeZoneProvider & new_tzp )
{
tzp = & new_tzp ;
}
void
_reset_tzp ( )
{
tzp = & ltzp ;
}
2015-04-07 17:36:40 -05:00
class GncDateTimeImpl
{
public :
2018-11-02 12:29:52 -05:00
GncDateTimeImpl ( ) : m_time ( boost : : local_time : : local_sec_clock : : local_time ( tzp - > get ( boost : : gregorian : : day_clock : : local_day ( ) . year ( ) ) ) ) { }
2015-04-07 17:36:40 -05:00
GncDateTimeImpl ( const time64 time ) : m_time ( LDT_from_unix_local ( time ) ) { }
2015-04-26 18:44:39 -05:00
GncDateTimeImpl ( const struct tm tm ) : m_time ( LDT_from_struct_tm ( tm ) ) { }
2017-04-20 15:58:35 -05:00
GncDateTimeImpl ( const GncDateImpl & date , DayPart part = DayPart : : neutral ) ;
2018-02-24 12:55:03 -06:00
GncDateTimeImpl ( std : : string str ) ;
2018-11-02 12:29:52 -05:00
GncDateTimeImpl ( PTime & & pt ) : m_time ( pt , tzp - > get ( pt . date ( ) . year ( ) ) ) { }
2015-04-07 17:36:40 -05:00
GncDateTimeImpl ( LDT & & ldt ) : m_time ( ldt ) { }
2015-04-10 11:33:51 -05:00
operator time64 ( ) const ;
2015-04-26 18:44:39 -05:00
operator struct tm ( ) const ;
2018-11-02 12:29:52 -05:00
void now ( ) { m_time = boost : : local_time : : local_sec_clock : : local_time ( tzp - > get ( boost : : gregorian : : day_clock : : local_day ( ) . year ( ) ) ) ; }
2015-04-26 18:44:39 -05:00
long offset ( ) const ;
2015-04-28 17:23:26 -05:00
struct tm utc_tm ( ) const { return to_tm ( m_time . utc_time ( ) ) ; }
2015-04-28 12:06:18 -05:00
std : : unique_ptr < GncDateImpl > date ( ) const ;
2015-04-26 20:01:23 -05:00
std : : string format ( const char * format ) const ;
2016-11-06 11:39:21 -06:00
std : : string format_zulu ( const char * format ) const ;
2018-12-28 18:39:02 -06:00
std : : string format_iso8601 ( ) const ;
2019-01-05 16:53:25 -06:00
static std : : string timestamp ( ) ;
2015-04-07 17:36:40 -05:00
private :
LDT m_time ;
} ;
2015-04-10 11:33:51 -05:00
2017-04-20 14:42:24 -05:00
/** Private implementation of GncDate. See the documentation for that class.
*/
class GncDateImpl
{
public :
GncDateImpl ( ) : m_greg ( boost : : gregorian : : day_clock : : local_day ( ) ) { }
GncDateImpl ( const int year , const int month , const int day ) :
m_greg ( year , static_cast < Month > ( month ) , day ) { }
GncDateImpl ( Date d ) : m_greg ( d ) { }
2017-04-21 09:58:20 -05:00
GncDateImpl ( const std : : string str , const std : : string fmt ) ;
2017-04-20 14:42:24 -05:00
void today ( ) { m_greg = boost : : gregorian : : day_clock : : local_day ( ) ; }
ymd year_month_day ( ) const ;
std : : string format ( const char * format ) const ;
2019-02-08 13:40:21 -06:00
std : : string format_zulu ( const char * format ) const {
return this - > format ( format ) ;
}
2017-04-20 14:42:24 -05:00
private :
Date m_greg ;
2017-04-20 15:58:35 -05:00
friend GncDateTimeImpl : : GncDateTimeImpl ( const GncDateImpl & , DayPart ) ;
2017-04-29 11:01:12 -05:00
friend bool operator < ( const GncDateImpl & , const GncDateImpl & ) ;
friend bool operator > ( const GncDateImpl & , const GncDateImpl & ) ;
friend bool operator = = ( const GncDateImpl & , const GncDateImpl & ) ;
friend bool operator < = ( const GncDateImpl & , const GncDateImpl & ) ;
friend bool operator > = ( const GncDateImpl & , const GncDateImpl & ) ;
friend bool operator ! = ( const GncDateImpl & , const GncDateImpl & ) ;
2017-04-20 14:42:24 -05:00
} ;
2021-03-20 17:49:54 -05:00
/* Needs to be separately defined so that the friend decl can grant
* access to date . m_greg .
2017-04-20 14:42:24 -05:00
*/
2017-04-20 15:58:35 -05:00
GncDateTimeImpl : : GncDateTimeImpl ( const GncDateImpl & date , DayPart part ) :
2021-03-20 17:49:54 -05:00
m_time { LDT_from_date_daypart ( date . m_greg , part ,
tzp - > get ( date . m_greg . year ( ) ) ) } { }
2017-04-20 15:58:35 -05:00
2021-03-20 17:49:54 -05:00
/* Member function definitions for GncDateTimeImpl.
*/
2018-01-09 15:58:25 -06:00
static TZ_Ptr
tz_from_string ( std : : string str )
{
if ( str . empty ( ) ) return utc_zone ;
std : : string tzstr = " XXX " + str ;
if ( tzstr . length ( ) > 6 & & tzstr [ 6 ] ! = ' : ' ) //6 for XXXsHH, s is + or -
tzstr . insert ( 6 , " : " ) ;
if ( tzstr . length ( ) > 9 & & tzstr [ 9 ] ! = ' : ' ) //9 for XXXsHH:MM
{
tzstr . insert ( 9 , " : " ) ;
}
return TZ_Ptr ( new PTZ ( tzstr ) ) ;
}
2018-02-24 12:55:03 -06:00
GncDateTimeImpl : : GncDateTimeImpl ( std : : string str ) :
2015-04-28 12:06:18 -05:00
m_time ( unix_epoch , utc_zone )
{
if ( str . empty ( ) ) return ;
2018-04-08 19:37:39 -05:00
TZ_Ptr tzptr ;
2015-04-28 19:53:35 -05:00
try
{
2018-04-08 19:37:39 -05:00
static const boost : : regex delim_iso ( " ^( \\ d{4}- \\ d{2}- \\ d{2} \\ d{2}: \\ d{2}: \\ d{2}(?: \\ . \\ d{0,9})?) \\ s*([+-] \\ d{2}(?::? \\ d{2})?)?$ " ) ;
static const boost : : regex non_delim ( " ^( \\ d{14}(?: \\ . \\ d{0,9})?) \\ s*([+-] \\ d{2} \\ s*(:? \\ d{2})?)?$ " ) ;
PTime pdt ;
boost : : smatch sm ;
if ( regex_match ( str , sm , non_delim ) )
{
std : : string time_str ( sm [ 1 ] ) ;
time_str . insert ( 8 , " T " ) ;
pdt = boost : : posix_time : : from_iso_string ( time_str ) ;
}
else if ( regex_match ( str , sm , delim_iso ) )
{
pdt = boost : : posix_time : : time_from_string ( sm [ 1 ] ) ;
}
else
{
throw ( std : : invalid_argument ( " The date string was not formatted in a way that GncDateTime(std::string) knows how to parse. " ) ) ;
}
std : : string tzstr ( " " ) ;
if ( sm [ 2 ] . matched )
tzstr + = sm [ 2 ] ;
tzptr = tz_from_string ( tzstr ) ;
2021-03-20 17:49:54 -05:00
m_time = LDT_from_date_time ( pdt . date ( ) , pdt . time_of_day ( ) , tzptr ) ;
2015-04-28 19:53:35 -05:00
}
2018-04-03 21:02:44 -05:00
catch ( boost : : gregorian : : bad_year & )
2015-04-28 19:53:35 -05:00
{
throw ( std : : invalid_argument ( " The date string was outside of the supported year range. " ) ) ;
2015-04-28 12:06:18 -05:00
}
2018-01-09 15:58:25 -06:00
/* Bug 767824: A GLib bug in parsing the UTC timezone on Windows may have
* created a bogus timezone of a random number of minutes . Since there are
* no fractional - hour timezones around the prime meridian we can safely
* check for this in files by resetting to UTC if there ' s a
* less - than - an - hour offset .
*/
auto offset = tzptr - > base_utc_offset ( ) . seconds ( ) ;
if ( offset ! = 0 & & std : : abs ( offset ) < 3600 )
m_time = m_time . local_time_in ( utc_zone ) ;
2015-04-28 12:06:18 -05:00
}
2015-04-10 11:33:51 -05:00
GncDateTimeImpl : : operator time64 ( ) const
{
auto duration = m_time . utc_time ( ) - unix_epoch ;
auto secs = duration . ticks ( ) ;
secs / = ticks_per_second ;
return secs ;
2015-04-07 17:36:40 -05:00
}
2015-04-26 18:44:39 -05:00
GncDateTimeImpl : : operator struct tm ( ) const
{
2015-04-28 17:23:26 -05:00
struct tm time = to_tm ( m_time ) ;
# if HAVE_STRUCT_TM_GMTOFF
time . tm_gmtoff = offset ( ) ;
# endif
return time ;
2015-04-26 18:44:39 -05:00
}
long
GncDateTimeImpl : : offset ( ) const
{
auto offset = m_time . local_time ( ) - m_time . utc_time ( ) ;
return offset . total_seconds ( ) ;
}
2015-04-28 12:06:18 -05:00
std : : unique_ptr < GncDateImpl >
GncDateTimeImpl : : date ( ) const
{
return std : : unique_ptr < GncDateImpl > ( new GncDateImpl ( m_time . local_time ( ) . date ( ) ) ) ;
}
2018-05-28 10:56:01 -05:00
/* The 'O', 'E', and '-' format modifiers are not supported by
* boost ' s output facets . Remove them .
*/
static inline std : : string
normalize_format ( const std : : string & format )
{
bool is_pct = false ;
std : : string normalized ;
std : : remove_copy_if (
format . begin ( ) , format . end ( ) , back_inserter ( normalized ) ,
[ & is_pct ] ( char e ) {
bool r = ( is_pct & & ( e = = ' E ' | | e = = ' O ' | | e = = ' - ' ) ) ;
is_pct = e = = ' % ' ;
return r ;
} ) ;
return normalized ;
}
2019-02-08 13:40:21 -06:00
# 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 ;
}
2018-05-28 10:56:01 -05:00
2019-02-08 13:40:21 -06:00
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
2015-04-26 20:01:23 -05:00
std : : string
GncDateTimeImpl : : format ( const char * format ) const
{
2019-02-08 13:40:21 -06:00
# 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 ( ) ) ) ;
2015-04-26 20:01:23 -05:00
std : : stringstream ss ;
2019-02-08 13:40:21 -06:00
ss . imbue ( std : : locale ( gnc_get_locale ( ) , output_facet ) ) ;
ss < < m_time ;
2015-04-26 20:01:23 -05:00
return ss . str ( ) ;
2019-02-08 13:40:21 -06:00
# endif
2015-04-26 20:01:23 -05:00
}
2016-11-06 11:39:21 -06:00
std : : string
GncDateTimeImpl : : format_zulu ( const char * format ) const
{
2019-02-08 13:40:21 -06:00
# 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 ;
2021-03-21 15:53:48 -05:00
auto zulu_time = LDT { m_time . utc_time ( ) , utc_zone } ;
2019-02-08 13:40:21 -06:00
auto output_facet ( new Facet ( normalize_format ( format ) . c_str ( ) ) ) ;
2016-11-06 11:39:21 -06:00
std : : stringstream ss ;
2019-02-08 13:40:21 -06:00
ss . imbue ( std : : locale ( gnc_get_locale ( ) , output_facet ) ) ;
ss < < zulu_time ;
2016-11-06 11:39:21 -06:00
return ss . str ( ) ;
2019-02-08 13:40:21 -06:00
# endif
2016-11-06 11:39:21 -06:00
}
2018-12-28 18:39:02 -06:00
std : : string
GncDateTimeImpl : : format_iso8601 ( ) const
{
auto str = boost : : posix_time : : to_iso_extended_string ( m_time . utc_time ( ) ) ;
str [ 10 ] = ' ' ;
return str . substr ( 0 , 19 ) ;
}
2019-01-05 16:53:25 -06:00
std : : string
GncDateTimeImpl : : timestamp ( )
{
GncDateTimeImpl gdt ;
auto str = boost : : posix_time : : to_iso_string ( gdt . m_time . local_time ( ) ) ;
return str . substr ( 0 , 8 ) + str . substr ( 9 , 15 ) ;
}
2017-04-21 09:58:20 -05:00
/* Member function definitions for GncDateImpl.
2017-04-20 14:42:24 -05:00
*/
2017-04-21 09:58:20 -05:00
GncDateImpl : : GncDateImpl ( const std : : string str , const std : : string fmt ) :
m_greg ( boost : : gregorian : : day_clock : : local_day ( ) ) /* Temporarily initialized to today, will be used and adjusted in the code below */
{
auto iter = std : : find_if ( GncDate : : c_formats . cbegin ( ) , GncDate : : c_formats . cend ( ) ,
[ & fmt ] ( const GncDateFormat & v ) { return ( v . m_fmt = = fmt ) ; } ) ;
if ( iter = = GncDate : : c_formats . cend ( ) )
throw std : : invalid_argument ( N_ ( " Unknown date format specifier passed as argument. " ) ) ;
boost : : regex r ( iter - > m_re ) ;
boost : : smatch what ;
if ( ! boost : : regex_search ( str , what , r ) ) // regex didn't find a match
throw std : : invalid_argument ( N_ ( " Value can't be parsed into a date using the selected date format. " ) ) ;
// Bail out if a year was found with a yearless format specifier
auto fmt_has_year = ( fmt . find ( ' y ' ) ! = std : : string : : npos ) ;
if ( ! fmt_has_year & & ( what . length ( " YEAR " ) ! = 0 ) )
throw std : : invalid_argument ( N_ ( " Value appears to contain a year while the selected format forbids this. " ) ) ;
int year ;
if ( fmt_has_year )
{
/* The input dates have a year, so use that one */
year = std : : stoi ( what . str ( " YEAR " ) ) ;
/* We assume two-digit years to be in the range 1969 - 2068. */
if ( year < 69 )
year + = 2000 ;
else if ( year < 100 )
year + = 1900 ;
}
else /* The input dates have no year, so use current year */
year = m_greg . year ( ) ; // Can use m_greg here as it was already initialized in the initializer list earlier
m_greg = Date ( year ,
static_cast < Month > ( std : : stoi ( what . str ( " MONTH " ) ) ) ,
std : : stoi ( what . str ( " DAY " ) ) ) ;
}
2017-04-20 14:42:24 -05:00
ymd
GncDateImpl : : year_month_day ( ) const
2015-04-07 17:36:40 -05:00
{
2017-04-20 14:42:24 -05:00
auto boost_ymd = m_greg . year_month_day ( ) ;
return { boost_ymd . year , boost_ymd . month . as_number ( ) , boost_ymd . day } ;
2015-04-07 17:36:40 -05:00
}
2015-05-04 17:03:54 -05:00
std : : string
2017-04-20 14:42:24 -05:00
GncDateImpl : : format ( const char * format ) const
2015-04-28 12:06:18 -05:00
{
2019-02-08 13:40:21 -06:00
# ifdef __MINGW32__
return win_date_format ( format , to_tm ( m_greg ) ) ;
# else
2017-04-20 14:42:24 -05:00
using Facet = boost : : gregorian : : date_facet ;
std : : stringstream ss ;
//The stream destructor frees the facet, so it must be heap-allocated.
2018-05-28 10:56:01 -05:00
auto output_facet ( new Facet ( normalize_format ( format ) . c_str ( ) ) ) ;
2019-01-06 11:52:43 -06:00
ss . imbue ( std : : locale ( gnc_get_locale ( ) , output_facet ) ) ;
2017-04-20 14:42:24 -05:00
ss < < m_greg ;
return ss . str ( ) ;
2019-02-08 13:40:21 -06:00
# endif
2015-04-28 12:06:18 -05:00
}
2017-04-29 11:01:12 -05:00
bool operator < ( const GncDateImpl & a , const GncDateImpl & b ) { return a . m_greg < b . m_greg ; }
bool operator > ( const GncDateImpl & a , const GncDateImpl & b ) { return a . m_greg > b . m_greg ; }
bool operator = = ( const GncDateImpl & a , const GncDateImpl & b ) { return a . m_greg = = b . m_greg ; }
bool operator < = ( const GncDateImpl & a , const GncDateImpl & b ) { return a . m_greg < = b . m_greg ; }
bool operator > = ( const GncDateImpl & a , const GncDateImpl & b ) { return a . m_greg > = b . m_greg ; }
bool operator ! = ( const GncDateImpl & a , const GncDateImpl & b ) { return a . m_greg ! = b . m_greg ; }
2017-04-20 14:42:24 -05:00
/* =================== Presentation-class Implementations ====================*/
2015-05-04 17:03:54 -05:00
/* GncDateTime */
2015-03-26 19:59:06 -05:00
GncDateTime : : GncDateTime ( ) : m_impl ( new GncDateTimeImpl ) { }
2015-04-26 18:44:39 -05:00
GncDateTime : : GncDateTime ( const time64 time ) :
m_impl ( new GncDateTimeImpl ( time ) ) { }
GncDateTime : : GncDateTime ( const struct tm tm ) :
m_impl ( new GncDateTimeImpl ( tm ) ) { }
2018-02-24 12:55:03 -06:00
GncDateTime : : GncDateTime ( std : : string str ) :
2015-04-28 12:06:18 -05:00
m_impl ( new GncDateTimeImpl ( str ) ) { }
2015-03-26 19:59:06 -05:00
GncDateTime : : ~ GncDateTime ( ) = default ;
2015-04-07 17:36:40 -05:00
2017-04-20 15:58:35 -05:00
GncDateTime : : GncDateTime ( const GncDate & date , DayPart part ) :
m_impl ( new GncDateTimeImpl ( * ( date . m_impl ) , part ) ) { }
2015-04-07 17:36:40 -05:00
void
GncDateTime : : now ( )
{
m_impl - > now ( ) ;
}
2015-04-10 11:33:51 -05:00
GncDateTime : : operator time64 ( ) const
{
return m_impl - > operator time64 ( ) ;
2015-04-07 17:36:40 -05:00
}
2015-04-26 18:44:39 -05:00
GncDateTime : : operator struct tm ( ) const
{
return m_impl - > operator struct tm ( ) ;
}
long
GncDateTime : : offset ( ) const
{
return m_impl - > offset ( ) ;
}
2015-04-26 20:01:23 -05:00
2015-04-28 17:23:26 -05:00
struct tm
GncDateTime : : utc_tm ( ) const
{
return m_impl - > utc_tm ( ) ;
}
2015-04-28 12:06:18 -05:00
GncDate
GncDateTime : : date ( ) const
{
2016-04-07 17:41:15 -05:00
return GncDate ( m_impl - > date ( ) ) ;
2015-04-28 12:06:18 -05:00
}
2015-04-26 20:01:23 -05:00
std : : string
GncDateTime : : format ( const char * format ) const
{
return m_impl - > format ( format ) ;
}
2016-11-06 11:39:21 -06:00
std : : string
GncDateTime : : format_zulu ( const char * format ) const
{
return m_impl - > format_zulu ( format ) ;
}
2017-04-20 14:42:24 -05:00
2018-12-28 18:39:02 -06:00
std : : string
GncDateTime : : format_iso8601 ( ) const
{
return m_impl - > format_iso8601 ( ) ;
}
2019-01-05 16:53:25 -06:00
std : : string
GncDateTime : : timestamp ( )
{
return GncDateTimeImpl : : timestamp ( ) ;
}
2017-04-20 14:42:24 -05:00
/* GncDate */
GncDate : : GncDate ( ) : m_impl { new GncDateImpl } { }
GncDate : : GncDate ( int year , int month , int day ) :
m_impl ( new GncDateImpl ( year , month , day ) ) { }
2017-04-21 09:58:20 -05:00
GncDate : : GncDate ( const std : : string str , const std : : string fmt ) :
m_impl ( new GncDateImpl ( str , fmt ) ) { }
2017-04-20 14:42:24 -05:00
GncDate : : GncDate ( std : : unique_ptr < GncDateImpl > impl ) :
m_impl ( std : : move ( impl ) ) { }
2017-05-02 16:09:23 -05:00
GncDate : : GncDate ( const GncDate & a ) :
m_impl ( new GncDateImpl ( * a . m_impl ) ) { }
2017-04-20 14:42:24 -05:00
GncDate : : GncDate ( GncDate & & ) = default ;
GncDate : : ~ GncDate ( ) = default ;
2017-05-02 16:09:23 -05:00
GncDate &
GncDate : : operator = ( const GncDate & a )
{
m_impl . reset ( new GncDateImpl ( * a . m_impl ) ) ;
return * this ;
}
2017-04-20 14:42:24 -05:00
GncDate &
GncDate : : operator = ( GncDate & & ) = default ;
void
GncDate : : today ( )
{
m_impl - > today ( ) ;
}
std : : string
GncDate : : format ( const char * format )
{
return m_impl - > format ( format ) ;
}
ymd
GncDate : : year_month_day ( ) const
{
return m_impl - > year_month_day ( ) ;
}
2017-04-29 11:01:12 -05:00
bool operator < ( const GncDate & a , const GncDate & b ) { return * ( a . m_impl ) < * ( b . m_impl ) ; }
bool operator > ( const GncDate & a , const GncDate & b ) { return * ( a . m_impl ) > * ( b . m_impl ) ; }
bool operator = = ( const GncDate & a , const GncDate & b ) { return * ( a . m_impl ) = = * ( b . m_impl ) ; }
bool operator < = ( const GncDate & a , const GncDate & b ) { return * ( a . m_impl ) < = * ( b . m_impl ) ; }
bool operator > = ( const GncDate & a , const GncDate & b ) { return * ( a . m_impl ) > = * ( b . m_impl ) ; }
bool operator ! = ( const GncDate & a , const GncDate & b ) { return * ( a . m_impl ) ! = * ( b . m_impl ) ; }