//---------------------------------------------------------------------------------------
#include <iostream>
#include <string>
//---------------------------------------------------------------------------------------

#define CSTR

using   i8                      = char;
using   u8                      = unsigned char;
using   i16                     = short;
using   u16                     = unsigned short;
using   i32                     = int;
using   u32                     = unsigned int;
using   i64                     = long long;
using   u64                     = unsigned long long;

using   f32                     = float;
using   f64                     = double;

#define rigid                   constexpr

//---------------------------------------------------------------------------------------

rigid   u32 CHAR_BITS           = 8;

template< typename T >
inline  rigid   auto    msb()
{
    return  sizeof( T ) * CHAR_BITS - 1;
}

template< typename T >
inline  rigid   auto    msb_value()
{
    return  (T)1 << msb< T >();
}

#define msb_pos( var ) msb< decltype( var ) >()
#define msb_val( var ) msb_value< decltype( var ) >()

template< typename T0, typename T1 >
inline  void    set_msbs( T0& dst, const T1 bits )
{
    *( (T1*)&dst + ( sizeof dst / sizeof bits - 1 ) ) |= bits;
}
//---------------------------------------------------------------------------------------

template< typename TR >
inline  TR      atou( char*& s )
{
    TR value;
    for( value = 0; *s <= '9' && *s >= '0'; ++s )
        value = value * 10 + ( *s & 0xF );
    return  value;
}

template< typename TR >
inline  TR      atoi( char*& s )
{
    if( *s == '-' )
        return  (TR)( -1 * atou< TR >( ++s ) );
    return  (TR)atou< TR >( s );
}

#include <cmath>
template< typename TR = f64 >
inline  TR      atof( char*& s )
{
    char* p   = s;
    u32 sign  = 0;
    TR  digit = 0.;
    TR  base  = 1.;
    TR  step  = 1.;

    for( i8 token; token = *p++; )
    {
        switch( token )
        {
        case '-':
            sign = msb_val( sign );
            break;
        case '.':
            step = 10;
            break;
        case 'E':
        case 'e':
            digit *= (TR)std::pow( 10., (f64)atof< TR >( s = p ) );
            goto exit;
        default:
            if( token <= '9' && token >= '0' )
            {
                digit = digit * 10 + ( token & 0xF );
                base *= step;
                continue;
            }
            goto exit;
        }
    }
exit:
    if( s < p - 1 ) s = p - 1;
    set_msbs( digit, sign );
    return  TR( digit / base );
}
//---------------------------------------------------------------------------------------

#include <string>
#include <stdarg.h>

template< u32 MAX_SIZE = 1024 >
inline  std::string     format_string( const char* format, ... )
{
    char    temp[ MAX_SIZE ];
    va_list va;
    va_start( va, format );
    vsprintf( temp, format, va );
    va_end( va );
    return  temp;
} 
//---------------------------------------------------------------------------------------

#include <type_traits>

template< typename T >
inline  char*   parse_number( const char* string, T& param )
{
    char* s = (char*)string;
    while( *s <= ' ' || *s == '/' || *s == ':' ) s++;

    if( std::is_floating_point< T >() )
        param = atof< T >( s );
    else
    {
        if( std::is_signed< T >() )
            param = atoi< T >( s );
        else
            param = atou< T >( s );
    }
    return  s;
}

template< typename T, typename... Ts >
inline  char*   parse_number( const char* string, T& param1, Ts&... rest )
{
    return  parse_number( parse_number( string, param1 ), rest... );
}
//---------------------------------------------------------------------------------------

rigid   i32  msecs_in_1sec      = 1000;
rigid   i32   secs_in_1min      = 60;
rigid   i32   mins_in_1hour     = 60;
rigid   i32  hours_in_1day      = 24;
rigid   i32   secs_in_1hour     = mins_in_1hour     * secs_in_1min;
rigid   i32   mins_in_1day      = hours_in_1day     * mins_in_1hour;
rigid   i32   secs_in_1day      = mins_in_1day      * secs_in_1min;
rigid   i32  msecs_in_1day      = secs_in_1day      * msecs_in_1sec;
rigid   i32  value_of_1day      = secs_in_1day;

rigid   i32   days_in_1year     = 365;
rigid   i32   days_in_4years    = days_in_1year     * 4  + 1;
rigid   i32   days_in_100years  = days_in_4years    * 25 - 1;
rigid   i32   days_in_400years  = days_in_100years  * 4  + 1;
rigid   i32   secs_in_1year     = days_in_1year     * secs_in_1day;
                                  
rigid   i64  years_in_bc        = 100000000000LL;
rigid   i64   days_in_bc        = years_in_bc / 400 * days_in_400years;
rigid   i64   secs_in_bc        = days_in_bc        * secs_in_1day;
rigid   i64   days_in_bc_greg   = days_in_400years  * years_in_bc / 400;
rigid   i64   days_in_bc_juli   = days_in_4years    * years_in_bc / 4;
rigid   i64   juli_to_greg      = days_in_bc_greg   - days_in_bc_juli - 2;

rigid   i32 final_day_fragment  = (u64)-1 - (u64)-1 / value_of_1day * value_of_1day;

rigid   u32 sum_days100[ 4 ][ 14 ]
{
    { 0,    0,   31,   59,   90,  120,  151,  181,  212,  243,  273,  304,  334,  365, },
    { 0,  365,  396,  424,  455,  485,  516,  546,  577,  608,  638,  669,  699,  730, },
    { 0,  730,  761,  789,  820,  850,  881,  911,  942,  973, 1003, 1034, 1064, 1095, },
    { 0, 1095, 1126, 1154, 1185, 1215, 1246, 1276, 1307, 1338, 1368, 1399, 1429, 1460, },
};

rigid   u32 sum_days[ 4 ][ 14 ]
{
    { 0,    0,   31,   59,   90,  120,  151,  181,  212,  243,  273,  304,  334,  365, },
    { 0,  365,  396,  424,  455,  485,  516,  546,  577,  608,  638,  669,  699,  730, },
    { 0,  730,  761,  789,  820,  850,  881,  911,  942,  973, 1003, 1034, 1064, 1095, },
    { 0, 1095, 1126, 1155, 1186, 1216, 1247, 1277, 1308, 1339, 1369, 1400, 1430, 1461, },
};

rigid   char*   day_names[]
{
    "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
};
//---------------------------------------------------------------------------------------

template< typename T0, typename T1, typename T2 >
inline  auto                    times( const T0 hours, const T1 mins, const T2 secs )
{
    return  hours * secs_in_1hour + mins * secs_in_1min + secs;
}

template< typename T >
inline  u64                     days( const T days )
{
    return  days * secs_in_1day;
}

inline  u64                     seconds( const u64 secs )
{
    return  secs;
}
///--------------------------------------------------------------------------------------

enum    class                   DATE_METHOD
{
    julian,
    gregorian,
};

template< DATE_METHOD method = DATE_METHOD::gregorian >
class   DATE_
{
private:
    u64                         secs;

    void                        date_parsing( const char* date_string )
    {
        i64     year;
        u32     month, day;
        parse_number( date_string, year, month, day );
        encode( year, month, day );
    }

public:
    CSTR                        DATE_() : secs( 0 )
    {}
    CSTR    explicit            DATE_( const u64 secs ) : secs( secs )
    {}
    CSTR                        DATE_( const i64 year, const u32 mm, const u32 dd )
    {
        encode( year, mm, dd );
    }
    CSTR                        DATE_( const i64 year, const u32 mm, const u32 dd,
                                       const u32 hh, const u32 nn, const u32 ss )
    {
        encode( year, mm, dd );
        secs += times( hh, nn, ss );
    }
    CSTR    explicit            DATE_( const char* date_string )
    {
        date_parsing( date_string );
    }
    CSTR    explicit            DATE_( const std::string date_string )
    {
        date_parsing( date_string.c_str() );
    }

    void                        encode( const i64 year, const u32 month, const u32 day )
    {
        u64     xx  = year + years_in_bc - ( year > 0 );
        if( method == DATE_METHOD::gregorian )
        {
            secs    = xx / 400 * days_in_400years;
            u32 x2  = xx % 400;

            u32 y1  = x2 / 100 * days_in_100years;
            u32 x1  = x2 % 100;

            u32 y0  = x1 / 4 * days_in_4years;
            u32 x0  = x1 % 4;

            auto s  = sum_days[ x0 ];
            if( x2 != 399 && x1 == 99 )
                 s  = sum_days100[ x0 ];

            secs   += y1 + y0 + s[ month ] + day - 1;
        }
        else
        {
            secs    = xx / 4 * days_in_4years +
                      ( sum_days[ xx % 4 ][ month ] + day + ( juli_to_greg - 1 ) );
        }
        secs       *= value_of_1day;
    }

    void                        decode( i64& year, u32& month, u32& day ) const
    {
        u64     xx  = secs / value_of_1day;
        u32     x, y;
        year        = -years_in_bc;
        auto    s   = sum_days;
        if( method == DATE_METHOD::gregorian )
        {
            year   += xx / days_in_400years * 400;
            u32 x2  = xx % days_in_400years;

            u32 y1  = x2 / days_in_100years - x2 / ( days_in_100years * 4 );
            u32 x1  = x2 - days_in_100years * y1;
                y1  = y1 * 100;

            u32 y0  = x1 / days_in_4years * 4;
            x       = x1 % days_in_4years;

            y       = x / days_in_1year - x / ( days_in_1year * 4 );

            if( y1 < 300 && y0 + y == 99  )
                s   = sum_days100;

            year   += y1 + y0 + y;
        }
        else
        {
            xx     -= juli_to_greg;

            u64 y0  = xx / days_in_4years * 4;
            x       = xx % days_in_4years;

            y       = x / days_in_1year - x / ( days_in_1year * 4 );

            year   += y0 + y;
        }

        month       = ( x - y * days_in_1year ) / 29;
        month      += x >= s[ y ][ month + 1 ];
        day         = x - s[ y ][ month ] + 1;
        year       += year >= 0;
    }
    ///----------------------------------------------------------------------------------

    const   auto                name() const
    {
        return  day_names[ secs / value_of_1day % 7 ];
    }

    const   auto                date( bool ad_bc = false ) const
    {
        i64     year;
        u32     month, day;
        decode( year, month, day );
        if( !ad_bc )
            return  format_string( "%lld/%02u/%02u", year, month, day );
        else
            return  format_string( "%s.%llu/%02u/%02u", year > 0 ? "AD" : "BC",
                    std::abs( year ), month, day );
    }

    const   auto                time( bool am_pm = false ) const
    {
        u32     hh, mm, ss;
        ss      = time_seconds();
        hh      = ss / secs_in_1hour;
        ss      = ss % secs_in_1hour;
        mm      = ss / secs_in_1min;
        ss      = ss % secs_in_1min;
        return  format_string( "%02u:%02u:%02u", hh, mm, ss ) +
                ( am_pm ? ( hh < 12 ? " am" : " pm" ) : "" );
    }

    const   auto                date_time() const
    {
        return  date() + " " + time();
    }

    const   auto                long_date() const
    {
        return  std::string( method == DATE_METHOD::gregorian ? "G:" : "J:" ) +
            date( true ) + " " + name();
    }

    const   auto                long_date_time() const
    {
        return  long_date() + " " + time( true );
    }
    ///----------------------------------------------------------------------------------

    const   auto                seconds() const
    {
        return  secs;
    }

    const   u32                 time_seconds() const
    {
        return  secs % secs_in_1day;
    }

    const   auto                days() const
    {
        return  secs / secs_in_1day;
    }
    ///----------------------------------------------------------------------------------

    template< DATE_METHOD M >
    const   auto                operator-( const DATE_< M >& rhs ) const
    {
        return  DATE_< method >( secs - rhs.seconds() );
    }

    template< DATE_METHOD M >
    const   auto                operator+( const DATE_< M >& rhs ) const
    {
        return  DATE_< method >( secs + rhs.seconds() );
    }

    const   auto                operator-( const u64 duration ) const
    {
        return  DATE_< method >( secs - duration );
    }

    const   auto                operator+( const u64 duration ) const
    {
        return  DATE_< method >( secs + duration );
    }

    auto&                       operator-=( const DATE_& rhs )
    {
        return  secs -= rhs.secs, *this;
    }

    auto&                       operator+=( const DATE_& rhs )
    {
        return  secs += rhs.secs, *this;
    }

    auto&                       operator-=( const u64 duration )
    {
        return  secs -= duration, *this;
    }

    auto&                       operator+=( const u64 duration )
    {
        return  secs += duration, *this;
    }

    auto&                       operator--()
    {
        return  *this -= secs_in_1day;
    }

    auto&                       operator++()
    {
        return  *this += secs_in_1day;
    }

    auto                        operator--( int )
    {
    	DATE_< method > back( secs );
        secs -= secs_in_1day;
        return  back;
    }

    auto                        operator++( int )
    {
    	DATE_< method > back( secs );
        secs += secs_in_1day;
        return  back;
    }

    const   bool                operator<( const DATE_& rhs ) const
    {
        return  secs < rhs.secs;
    }

    friend  std::ostream&       operator<<( std::ostream& os, const DATE_& date )
    {
        return  os << date.long_date_time();
    }
};
//---------------------------------------------------------------------------------------

using   DATE  = DATE_< DATE_METHOD::gregorian >;
using   DATEJ = DATE_< DATE_METHOD::julian >;

inline  auto    historical_date( const DATE gregorian_date )
{
    const   auto    julian_to_gregorian_date = DATE( 1582,10,15 );
    if( gregorian_date < julian_to_gregorian_date )
        return  DATEJ( gregorian_date.seconds() ).long_date();
    return  gregorian_date.long_date();
}
//---------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------

using   namespace   std;

int main()
{
    cout << sizeof( DATE ) <<
        " bytes DATE_< Julian & Gregorian > type" << endl << endl;

	cout << "Julian date range" << endl;
    cout << DATEJ( 0ULL ) << " ~ " << endl;
    cout << DATEJ( 0xFFFFFFFFFFFFFFFFULL ) << endl;
    cout << endl;

	cout << "Gregorian date range" << endl;
    cout << DATE( 0ULL ) << " ~ " << endl;
    cout << DATE( (u64)-1 ) << endl;
    cout << endl;

	cout << "First day of AD unary" << endl;
    cout << ++DATEJ( -1,12,31 )  << " == " << DATEJ( 1, 1, 1 ).seconds() <<
        " seconds " << endl;
    cout << ++DATE( "-1/12/31" ) << " == " << DATE( "1/ 1/ 1" ).seconds() <<
        " seconds " << endl;
    cout << endl;

    cout << "First day of AD conversion" << endl;
    cout << DATEJ( 1, 1, 1 ) << " == " <<
        DATE( DATEJ( 1, 1, 1 ).seconds() ).days() << " th day " << endl;
    cout << DATE( 1, 1, 1 ) << " == " <<
        DATEJ( DATE( 1, 1, 1 ).seconds() ).days() << " th day " << endl;
    cout << endl;

    cout << "Historical seam of Julian to Gregorian" << endl;
    cout << DATE( 1582, 10, 14 ) << " == " <<
        historical_date( DATE( "1582/10/14" ) ) << endl;
    cout << DATE( 1582, 10, 15 ) << " == " <<
        historical_date( DATE( "1582/10/15" ) ) << endl;
    cout << endl;

    cout << "Time methods" << endl;
    cout << DATE( times( 23, 59, 30 ) ).time() << endl;
    cout << DATE( 2016, 3, 30, 9, 50, 45 ) << endl;
    cout << endl;

    cout << "Adder test" << endl;
    DATE date( DATE() + days( days_in_bc ) + times( 23, 59, 30 ) + seconds( 30 ) );
    cout << date++.date_time() << endl;
    cout << (++date).date_time() << endl;
    cout << endl;

    getchar();
    return 0;
}
//---------------------------------------------------------------------------------------