Bonita Montero
unread,Jun 8, 2022, 12:01:32 AM6/8/22You do not have permission to delete messages in this group
Either email addresses are anonymous for this group or you need the view member email addresses permission to view the original message
to
The following code resembles SYSTEM_TIME and FILETIME in a portable
way.
// system_time.h
#pragma once
#if defined(_MSC_VER)
#define NOMINMAX
#include <Windows.h>
#endif
#include <cstdint>
#include <compare>
#include <ctime>
#include <stdexcept>
#include <compare>
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable: 26812) // prefer enum class over enum
#endif
struct system_time_t
{
std::uint16_t year;
std::uint8_t weekday, month, day;
std::uint8_t hour, minute, second;
std::uint32_t ns100;
};
struct system_time : public system_time_t
{
system_time() {}
system_time( system_time const & ) = default;
system_time( std::uint16_t year, std::uint8_t month, std::uint8_t day,
std::uint8_t hour, std::uint8_t minute, std::uint8_t second,
std::uint32_t ns100 );
system_time( std::uint64_t timestamp );
system_time( struct tm const &tm );
#if defined(_MSC_VER)
system_time( SYSTEMTIME const &st );
#endif
operator std::uint64_t() const;
system_time &operator =( std::uint64_t timestamp );
system_time &operator =( system_time const & ) = default;
operator tm() const;
system_time &operator =( struct tm const &tm );
#if defined(_MSC_VER)
operator SYSTEMTIME() const;
system_time &operator =( SYSTEMTIME const &st );
#endif
void adjust_weekday();
static std::uint8_t get_weekday( std::uint64_t timestamp );
static_assert(sizeof(time_t) == 8, "time_t must be 64 bit");
static time_t timestamp_to_time_t( uint64_t timestamp );
static uint64_t time_t_to_timestamp( time_t time );
struct date_error : public std::invalid_argument
{
enum reason_t : uint8_t
{
TIMESTAMP_BEYOND_63_BIT = 1,
YEAR_OUT_OF_RANGE,
MONTH_OUT_OF_RANGE,
DAY_OUT_OF_RANGE,
HOUR_OUT_OF_RANGE,
MINUTE_OUT_OF_RANGE,
SECOND_OUT_OF_RANGE,
NS100_OUT_OF_RANGE,
INVALID_WEEKDAY,
TIMESTAMP_BEFORE_TIME_T
};
reason_t reason();
private:
friend struct system_time;
date_error( reason_t reason );
reason_t m_reason;
};
private:
void assignFileTime( std::uint64_t timestamp );
static uint64_t const
MILLISECOND = 10'000,
SECOND = 1'000 * MILLISECOND,
MINUTE = 60 * SECOND,
HOUR = 60 * MINUTE,
DAY = 24 * HOUR,
WEEK = 7 * DAY,
NON_LEAP_YEAR = 365 * DAY,
LEAP_YEAR = 366 * DAY,
FOUR_YEARS_W_LJ = LEAP_YEAR + 3 * NON_LEAP_YEAR,
FOUR_YEARS_WO_LJ = 4 * NON_LEAP_YEAR,
FIRST_QUARTER_CENTURY = 25 * FOUR_YEARS_W_LJ,
REMAINING_QUARTER_CENUTRIES = 25 * FOUR_YEARS_W_LJ - DAY,
FOUR_HUNDRED_YEARS = FIRST_QUARTER_CENTURY + 3 *
REMAINING_QUARTER_CENUTRIES,
TIME_T_IN_TIMESTAMP_BEGIN = 0x19DB1DED53E800,
LAST_TIMESTAMP_IN_TIME_T = 0xD69433CCD5;
};
// may throw invalid_argument
inline
system_time::system_time( std::uint64_t timestamp )
{
assignFileTime( timestamp );
}
// may throw invalid_argument
inline
system_time &system_time::operator =( std::uint64_t timestamp )
{
assignFileTime( timestamp );
return *this;
}
inline
system_time::system_time( struct tm const &tm )
{
*this = tm;
}
#if defined(_MSC_VER)
inline
system_time::system_time( SYSTEMTIME const &st )
{
*this = st;
}
#endif
inline
typename system_time::date_error::reason_t system_time::date_error::reason()
{
return m_reason;
}
// may throw date_error
inline
void system_time::adjust_weekday()
{
weekday = get_weekday( (uint64_t)*this );
}
inline
uint8_t system_time::get_weekday( uint64_t timestamp )
{
return (std::uint32_t)((timestamp - timestamp / WEEK * WEEK) / DAY + 1)
% 7;
}
// may throw date_error
inline
std::strong_ordering operator <=>( system_time_t const &left,
system_time_t const &right )
{
return (uint64_t)(system_time const &)left <=> (uint64_t)(system_time
const &)right;
}
// may throw date_error
inline
std::strong_ordering operator <=>( system_time_t const &left,
std::uint64_t right )
{
return (uint64_t)(system_time const &)left <=> right;
}
// may throw date_error
inline
std::strong_ordering operator <=>( std::uint64_t left, system_time_t
const &right )
{
return left <=> (uint64_t)(system_time const &)right;
}
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
//
#include <stdexcept>
#include <cassert>
#include <limits>
#include <algorithm>
#include <array>
#include <system_error>
#if defined(__cpp_lib_hardware_interference_size)
#include <new>
#endif
#include "system_time.h"
using namespace std;
#if defined(__cpp_lib_hardware_interference_size)
#define CL_SIZE std::hardware_destructive_interference_size
#else
#define CL_SIZE 64
#endif
//#define SYSTEM_TIME_BINARY_SEARCH
// may throw date_error
system_time::system_time( std::uint16_t year, std::uint8_t month,
std::uint8_t day, std::uint8_t hour, std::uint8_t minute, std::uint8_t
second, std::uint32_t ns100 )
{
this->year = year;
this->month = month;
this->day = day;
this->hour = hour;
this->minute = minute;
this->second = second;
this->ns100 = ns100;
this->weekday = get_weekday( (uint64_t)*this );
}
// may throw date_error
void system_time::assignFileTime( uint64_t timestamp )
{
if( timestamp > (uint64_t)numeric_limits<int64_t>::max() )
throw date_error( date_error::TIMESTAMP_BEYOND_63_BIT );
weekday = get_weekday( timestamp );
timestamp += LEAP_YEAR; // 1601 - 1600 = leap year
uint64_t tsCalc = timestamp;
uint16_t y400, y100, y4, y;
y400 = (uint16_t)(tsCalc / FOUR_HUNDRED_YEARS);
tsCalc -= y400 * FOUR_HUNDRED_YEARS;
bool isLeapYear;
auto leapQuad = [&]()
{
if( tsCalc >= LEAP_YEAR ) [[likely]]
// y >= 1
tsCalc -= LEAP_YEAR,
y = (uint16_t)(1 + tsCalc / NON_LEAP_YEAR), // 1 ... 3
tsCalc -= tsCalc / NON_LEAP_YEAR * NON_LEAP_YEAR,
isLeapYear = false;
else
// y == 0
y = 0,
isLeapYear = true;
};
if( tsCalc >= FIRST_QUARTER_CENTURY ) [[likely]]
{
// (y % 400) >= 100
y100 = (uint16_t)(1 + (tsCalc - FIRST_QUARTER_CENTURY) /
REMAINING_QUARTER_CENUTRIES); // 1 ... 3
tsCalc -= FIRST_QUARTER_CENTURY + (tsCalc - FIRST_QUARTER_CENTURY) /
REMAINING_QUARTER_CENUTRIES * REMAINING_QUARTER_CENUTRIES;
if( tsCalc >= FOUR_YEARS_WO_LJ ) [[likely]]
// (y % 400) >= 100 && (y % 100) >= 4
y4 = (uint16_t)(1 + (tsCalc - FOUR_YEARS_WO_LJ) / FOUR_YEARS_W_LJ),
// 1 ... 24
tsCalc -= FOUR_YEARS_WO_LJ + (tsCalc - FOUR_YEARS_WO_LJ) /
FOUR_YEARS_W_LJ * FOUR_YEARS_W_LJ,
leapQuad();
else
// (y % 400) >= 100 && (y % 100) < 4
y4 = 0,
y = (uint16_t)(tsCalc / NON_LEAP_YEAR),
tsCalc -= tsCalc / NON_LEAP_YEAR * NON_LEAP_YEAR,
isLeapYear = false;
}
else
// (y % 400) < 100
y100 = 0,
y4 = (uint16_t)(tsCalc / FOUR_YEARS_W_LJ),
tsCalc -= tsCalc / FOUR_YEARS_W_LJ * FOUR_YEARS_W_LJ,
leapQuad();
year = 1600 + 400 * y400 + 100 * y100 + 4 * y4 + y;
{
static
uint64_t const monthOffsets[2][12 + 1] alignas(CL_SIZE) =
{
{ 0 * DAY, 31 * DAY, 59 * DAY, 90 * DAY, 120 * DAY, 151 * DAY, 181 *
DAY, 212 * DAY, 243 * DAY, 273 * DAY, 304 * DAY, 334 * DAY, 999 * DAY },
{ 0 * DAY, 31 * DAY, 60 * DAY, 91 * DAY, 121 * DAY, 152 * DAY, 182 *
DAY, 213 * DAY, 244 * DAY, 274 * DAY, 305 * DAY, 335 * DAY, 999 * DAY }
};
uint64_t const *pMonthOffsets = monthOffsets[isLeapYear];
#if defined(SYSTEM_TIME_BINARY_SEARCH)
size_t lower = 0, upper = 12, hit = -1, mid;
do
{
mid = (lower + upper) / 2;
if( pMonthOffsets[mid] <= tsCalc )
hit = mid,
lower = mid + 1;
else
upper = mid;
} while( lower != upper );
#else
size_t hit;
for( hit = 0; tsCalc >= pMonthOffsets[hit + 1]; ++hit );
#endif
assert(hit != -1);
uint8_t dy = (uint8_t)((tsCalc - pMonthOffsets[hit]) / DAY);
tsCalc -= pMonthOffsets[hit] + dy * DAY;
month = 1 + (uint8_t)hit;
day = 1 + dy;
}
hour = (uint8_t)(tsCalc / HOUR);
tsCalc %= HOUR;
minute = (uint8_t)(tsCalc / MINUTE);
tsCalc %= MINUTE;
second = (uint8_t)(tsCalc / SECOND);
tsCalc %= SECOND;
ns100 = (uint32_t)tsCalc;
}
// may throw date_error
system_time::operator std::uint64_t() const
{
if( year < 1601 || year > 30828 ) [[unlikely]]
throw date_error( date_error::YEAR_OUT_OF_RANGE );
if( month < 1 || month > 12 ) [[unlikely]]
throw date_error( date_error::MONTH_OUT_OF_RANGE );
bool isLeapYear = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
static
uint8_t const monthLengths[2][1 + 12] alignas(CL_SIZE) =
{
{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
if( day == 0 || day > monthLengths[isLeapYear][month] ) [[unlikely]]
throw date_error( date_error::DAY_OUT_OF_RANGE );
if( hour >= 24 ) [[unlikely]]
throw date_error( date_error::HOUR_OUT_OF_RANGE );
if( minute >= 60 ) [[unlikely]]
throw date_error( date_error::MINUTE_OUT_OF_RANGE );
if( second >= 60 ) [[unlikely]]
throw date_error( date_error::SECOND_OUT_OF_RANGE );
if( ns100 >= 10'000'000 ) [[unlikely]]
throw date_error( date_error::NS100_OUT_OF_RANGE );
uint16_t yr = year - 1600;
uint64_t timestamp = yr / 400 * FOUR_HUNDRED_YEARS;
yr %= 400;
auto leapQuad = [&]()
{
timestamp += yr / 4 * FOUR_YEARS_W_LJ;
yr %= 4;
if( yr >= 1 ) [[likely]]
timestamp += LEAP_YEAR + (yr - 1) * NON_LEAP_YEAR;
};
if( yr >= 100 ) [[likely]]
{
timestamp += FIRST_QUARTER_CENTURY;
yr -= 100;
timestamp += yr / 100 * REMAINING_QUARTER_CENUTRIES;
yr %= 100;
if( yr >= 4 ) [[likely]]
timestamp += FOUR_YEARS_WO_LJ,
yr -= 4,
leapQuad();
else
timestamp += yr * NON_LEAP_YEAR;
}
else
leapQuad();
timestamp -= LEAP_YEAR; // - (1.1.1601 - 1.1.1600)
static
uint16_t const monthOffsets[2][1 + 12] alignas(CL_SIZE) =
{
{ 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
{ 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
};
timestamp += (monthOffsets[isLeapYear][month] + day - 1) * DAY;
timestamp += hour * HOUR;
timestamp += minute * MINUTE;
timestamp += second * SECOND;
timestamp += ns100;
if( timestamp > (uint64_t)numeric_limits<int64_t>::max() )
throw date_error( date_error::TIMESTAMP_BEYOND_63_BIT );
return timestamp;
}
#if defined(_MSC_VER)
// may throw date_error
system_time::operator SYSTEMTIME() const
{
SYSTEMTIME st;
st.wYear = year;
st.wDay = day;
st.wHour = hour;
st.wMinute = minute;
st.wSecond = second;
st.wMilliseconds = (WORD)(ns100 / 10'000);
st.wDayOfWeek = get_weekday( (uint64_t)*this );;
return st;
}
// may throw date_error
system_time &system_time::operator =( SYSTEMTIME const &st )
{
system_time stAux;
stAux.year = st.wYear;
stAux.month = (uint8_t)st.wMonth;
stAux.day = (uint8_t)st.wDay;
stAux.hour = (uint8_t)st.wHour;
stAux.minute = (uint8_t)st.wMinute;
stAux.second = (uint8_t)st.wSecond;
stAux.ns100 = (uint32_t)st.wMilliseconds * 10'000;
stAux.weekday = get_weekday( (uint64_t)stAux );
return *this = stAux;
}
#endif
// may throw date_error
system_time::operator tm() const
{
if( year < 1900 || year - 1900u > (unsigned)numeric_limits<int>::max()
) [[unlikely]]
throw date_error( date_error::YEAR_OUT_OF_RANGE );
uint8_t wd = get_weekday( (uint64_t)*this );
struct tm tm;
tm.tm_year = year - 1900;
tm.tm_mon = month - 1;
tm.tm_mday = day;
uint16_t const monthOffsets[2][1 + 12] alignas(CL_SIZE) =
{
{ 0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 },
{ 0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335 }
};
tm.tm_yday = monthOffsets[year % 4 == 0 && year % 100 != 0 || year %
400 == 0][month] + day - 1;
tm.tm_hour = hour;
tm.tm_min = minute;
tm.tm_sec = second;
tm.tm_wday = wd;
return tm;
}
// may throw date_error
system_time &system_time::operator =( struct tm const &tm )
{
if( tm.tm_year < 1601 - 1900 || tm.tm_year > 30828 - 1900 ) [[unlikely]]
throw date_error( date_error::YEAR_OUT_OF_RANGE );
system_time stAux;
stAux.year = tm.tm_year + 1900;
stAux.month = tm.tm_mon + 1;
stAux.day = tm.tm_mday;
stAux.hour = tm.tm_hour;
stAux.minute = tm.tm_min;
stAux.second = tm.tm_sec;
stAux.ns100 = 0;
uint64_t timestamp = (uint64_t)stAux;
*this = stAux;
weekday = get_weekday( timestamp );
return *this;
}
// may throw date_error
time_t system_time::timestamp_to_time_t( uint64_t timestamp )
{
if( timestamp < TIME_T_IN_TIMESTAMP_BEGIN )
throw date_error( date_error::TIMESTAMP_BEFORE_TIME_T );
return (timestamp - TIME_T_IN_TIMESTAMP_BEGIN) / SECOND;
}
// may throw date_error
uint64_t system_time::time_t_to_timestamp( time_t time )
{
if( time > LAST_TIMESTAMP_IN_TIME_T )
throw date_error( date_error::TIMESTAMP_BEYOND_63_BIT );
return TIME_T_IN_TIMESTAMP_BEGIN + time * SECOND;
}
system_time::date_error::date_error( reason_t reason ) :
invalid_argument(
[]( reason_t reason ) -> char const *
{
struct err_map_t
{
reason_t reason;
char const *what;
};
static
initializer_list<err_map_t> const &errMaps =
{
err_map_t { TIMESTAMP_BEYOND_63_BIT, "system_time - timestamp beyond
63 bit (14.9.30828 2:48:5.4775807)" },
err_map_t { YEAR_OUT_OF_RANGE, "system_time - year out of range" },
err_map_t { MONTH_OUT_OF_RANGE, "system_time - month out of range" },
err_map_t { DAY_OUT_OF_RANGE, "system_time - day out of range" },
err_map_t { HOUR_OUT_OF_RANGE, "system_time - hour out of range" },
err_map_t { MINUTE_OUT_OF_RANGE, "system_time - minute out of range" },
err_map_t { SECOND_OUT_OF_RANGE, "system_time - second out of range" },
err_map_t { NS100_OUT_OF_RANGE, "system_time - 100ns-steps out of
range" },
err_map_t { INVALID_WEEKDAY, "system_time - invalid weekday" },
err_map_t { TIMESTAMP_BEFORE_TIME_T, "system_time - timestamp before
time_t (1.1.1970 00:00.0)" }
};
for( err_map_t const &errMap : errMaps )
if( errMap.reason == reason )
return errMap.what;
return "";
}( reason ) ),
m_reason( reason )
{
}