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 "
{
# include "config.h"
# 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>
2015-03-26 19:59:06 -05:00
# include <memory>
2015-12-15 15:38:50 -06:00
# include <iostream>
# include <sstream>
2015-03-26 19:59:06 -05:00
# include "gnc-timezone.hpp"
# include "gnc-datetime.hpp"
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 ;
static const TimeZoneProvider tzp ;
// 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
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
/** Private implementation of GncDate. See the documentation for that class.
*/
class GncDateImpl
{
public :
2015-04-07 17:36:40 -05:00
GncDateImpl ( ) : m_greg ( unix_epoch . date ( ) ) { }
2015-03-26 19:59:06 -05:00
GncDateImpl ( const int year , const int month , const int day ) :
2015-04-28 19:53:35 -05:00
m_greg ( year , static_cast < Month > ( month ) , day ) { }
2015-04-07 17:36:40 -05:00
GncDateImpl ( Date d ) : m_greg ( d ) { }
void today ( ) { m_greg = boost : : gregorian : : day_clock : : local_day ( ) ; }
2015-04-30 14:23:25 -05:00
ymd year_month_day ( ) const ;
2015-05-04 17:03:54 -05:00
std : : string format ( const char * format ) const ;
2015-03-26 19:59:06 -05:00
private :
Date m_greg ;
} ;
2015-04-28 12:06:18 -05:00
ymd
2015-04-30 14:23:25 -05:00
GncDateImpl : : year_month_day ( ) const
2015-04-28 12:06:18 -05:00
{
auto boost_ymd = m_greg . year_month_day ( ) ;
return { boost_ymd . year , boost_ymd . month . as_number ( ) , boost_ymd . day } ;
}
2015-05-04 17:03:54 -05:00
std : : string
GncDateImpl : : format ( const char * format ) const
{
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 ( format ) ) ;
ss . imbue ( std : : locale ( std : : locale ( ) , output_facet ) ) ;
ss < < m_greg ;
return ss . str ( ) ;
}
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 ) ) ;
auto tz = tzp . get ( temp . date ( ) . year ( ) ) ;
return LDT ( temp , tz ) ;
2015-04-28 17:23:26 -05:00
}
catch ( boost : : gregorian : : bad_year )
{
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
}
2015-04-26 18:44:39 -05:00
static LDT
LDT_from_struct_tm ( const struct tm tm )
{
2015-04-28 17:23:26 -05:00
try
{
2015-04-28 19:53:35 -05:00
auto tdate = boost : : gregorian : : date_from_tm ( tm ) ;
auto tdur = boost : : posix_time : : time_duration ( tm . tm_hour , tm . tm_min ,
tm . tm_sec , 0 ) ;
auto tz = tzp . get ( tdate . year ( ) ) ;
return LDT ( PTime ( tdate , tdur ) , tz ) ;
2015-04-28 17:23:26 -05:00
}
catch ( boost : : gregorian : : bad_year )
{
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-04-26 18:44:39 -05:00
}
2015-04-07 17:36:40 -05:00
class GncDateTimeImpl
{
public :
GncDateTimeImpl ( ) : m_time ( unix_epoch , tzp . get ( unix_epoch . date ( ) . year ( ) ) ) { }
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 ) ) { }
2015-04-28 12:06:18 -05:00
GncDateTimeImpl ( const std : : string str ) ;
2015-04-07 17:36:40 -05:00
GncDateTimeImpl ( PTime & & pt ) : m_time ( pt , tzp . get ( pt . date ( ) . year ( ) ) ) { }
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 ;
2015-04-07 17:36:40 -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 ;
2015-04-07 17:36:40 -05:00
private :
LDT m_time ;
} ;
2015-04-10 11:33:51 -05:00
2015-04-28 12:06:18 -05:00
GncDateTimeImpl : : GncDateTimeImpl ( const std : : string str ) :
m_time ( unix_epoch , utc_zone )
{
if ( str . empty ( ) ) return ;
using std : : string ;
using PTZ = boost : : local_time : : posix_time_zone ;
TZ_Ptr tzptr ;
auto tzpos = str . find_first_of ( " +- " , str . find ( " : " ) ) ;
2016-06-28 18:22:53 -05:00
int offset = 0L ;
2015-04-28 12:06:18 -05:00
if ( tzpos ! = str . npos )
{
2015-04-28 19:53:35 -05:00
string tzstr = " XXX " + str . substr ( tzpos ) ;
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
2016-06-28 18:22:53 -05:00
{
2015-04-28 19:53:35 -05:00
tzstr . insert ( 9 , " : " ) ;
2016-06-28 18:22:53 -05: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 looking for minutes - only offsets and
* making the appropriate correction .
*/
if ( tzstr . compare ( 7 , 8 , " 00 " ) & &
( tzstr . length ( ) > 10 ? tzstr [ 10 ] ! = ' 0 ' :
! tzstr . compare ( 10 , 11 , " 00 " ) ) )
{
offset = stoi ( tzstr . substr ( 10 , 11 ) ) ;
if ( offset & & tzpos = = ' - ' )
offset = - offset ;
tzstr . replace ( 10 , 11 , " 00 " ) ;
}
}
2015-04-28 19:53:35 -05:00
tzptr . reset ( new PTZ ( tzstr ) ) ;
if ( str [ tzpos - 1 ] = = ' ' ) - - tzpos ;
2015-04-28 12:06:18 -05:00
}
else
{
2015-04-28 19:53:35 -05:00
tzptr = utc_zone ;
}
try
{
2015-12-15 15:38:50 -06:00
using Facet = boost : : posix_time : : time_input_facet ;
//The stream destructor frees the facet, so it must be heap-allocated.
auto input_facet ( new Facet ( ) ) ;
std : : istringstream ss ( str . substr ( 0 , tzpos ) ) ;
ss . imbue ( std : : locale ( std : : locale ( ) , input_facet ) ) ;
input_facet - > set_iso_extended_format ( ) ;
PTime pdt ( not_a_date_time ) ;
ss > > pdt ;
2015-04-28 19:53:35 -05:00
m_time = LDT ( pdt . date ( ) , pdt . time_of_day ( ) , tzptr ,
LDTBase : : NOT_DATE_TIME_ON_ERROR ) ;
}
catch ( boost : : gregorian : : bad_year )
{
throw ( std : : invalid_argument ( " The date string was outside of the supported year range. " ) ) ;
2015-04-28 12:06:18 -05:00
}
2016-06-28 18:22:53 -05:00
if ( offset )
m_time - = boost : : posix_time : : minutes ( offset ) ;
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 ( ) ) ) ;
}
2015-04-26 20:01:23 -05:00
std : : string
GncDateTimeImpl : : format ( const char * format ) const
{
using Facet = boost : : local_time : : local_time_facet ;
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 < < m_time ;
return ss . str ( ) ;
}
2015-04-07 17:36:40 -05:00
/* =================== Presentation-class Implementations ====================*/
2015-05-04 17:03:54 -05:00
/* GncDate */
2015-03-26 19:59:06 -05:00
GncDate : : GncDate ( ) : m_impl { new GncDateImpl } { }
GncDate : : GncDate ( int year , int month , int day ) :
m_impl ( new GncDateImpl ( year , month , day ) ) { }
2015-04-28 12:06:18 -05:00
GncDate : : GncDate ( std : : unique_ptr < GncDateImpl > impl ) :
m_impl ( std : : move ( impl ) ) { }
GncDate : : GncDate ( GncDate & & ) = default ;
2015-03-26 19:59:06 -05:00
GncDate : : ~ GncDate ( ) = default ;
2015-04-28 12:06:18 -05:00
GncDate &
GncDate : : operator = ( GncDate & & ) = default ;
2015-04-07 17:36:40 -05:00
void
GncDate : : today ( )
{
m_impl - > today ( ) ;
}
2015-05-04 17:03:54 -05:00
std : : string
GncDate : : format ( const char * format )
{
return m_impl - > format ( format ) ;
}
2015-04-28 12:06:18 -05:00
ymd
2015-04-30 14:23:25 -05:00
GncDate : : year_month_day ( ) const
2015-04-28 12:06:18 -05:00
{
2015-04-30 14:23:25 -05:00
return m_impl - > year_month_day ( ) ;
2015-04-28 12:06:18 -05:00
}
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 ) ) { }
2015-04-28 12:06:18 -05:00
GncDateTime : : GncDateTime ( const std : : string str ) :
m_impl ( new GncDateTimeImpl ( str ) ) { }
2015-03-26 19:59:06 -05:00
GncDateTime : : ~ GncDateTime ( ) = default ;
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 ) ;
}