212 lines
6.2 KiB
C++
212 lines
6.2 KiB
C++
#include "DateTime.h"
|
|
|
|
#include "libs.h"
|
|
#include <stdio.h>
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
#include <tuple>
|
|
#include <utility>
|
|
|
|
static char month_days[2][12] = {
|
|
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
|
|
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
|
|
};
|
|
|
|
static bool is_leap_year(int year)
|
|
{
|
|
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
|
|
}
|
|
|
|
enum {
|
|
// we're using signed 64-bit integers for timestamps
|
|
// divide by 366 (let's just pretend every year is a leap year!)
|
|
// subtract 5000 years for a safety margin, and because timestamp 0 is not at year 0
|
|
APPROX_MAX_YEAR = (INT64_MAX / (366 * Time::Day)) - 5000
|
|
};
|
|
|
|
// C99 and C++11 specify that division truncates toward zero,
|
|
// this apparently came from fortran, and is *really unhelpful*,
|
|
//
|
|
// For a very useful paper on this, see:
|
|
// Division and Modulus for Computer Scientists --- Daan Leijen
|
|
//
|
|
// Also see Guido van Rossum's explanation of why he chose different
|
|
// behaviour for Python 3's integer division operator (//):
|
|
// http://python-history.blogspot.co.uk/2010/08/why-pythons-integer-division-floors.html
|
|
//
|
|
// divmod_euclid provides a division operation that always gives a non-negative remainder
|
|
template <typename T>
|
|
static std::pair<T, T> divmod_euclid(const T dividend, const T divisor)
|
|
{
|
|
T quot = dividend / divisor, rem = dividend % divisor;
|
|
if (rem < 0) {
|
|
if (divisor > 0) {
|
|
quot -= 1;
|
|
rem += divisor;
|
|
} else {
|
|
quot += 1;
|
|
rem -= divisor;
|
|
}
|
|
}
|
|
return std::make_pair(quot, rem);
|
|
}
|
|
|
|
template <typename T>
|
|
static std::pair<T, T> divmod_trunc(const T a, const T b)
|
|
{
|
|
return std::make_pair(a / b, a % b);
|
|
}
|
|
|
|
Time::DateTime::DateTime(int year, int month, int day, int hour, int minute, int second, int microsecond)
|
|
{
|
|
// minimum year is just a sanity check; the code should work for earlier years
|
|
assert(year >= 1600 && year <= APPROX_MAX_YEAR);
|
|
assert(month >= 1 && month <= 12);
|
|
assert(day >= 1 && day <= month_days[is_leap_year(year)][month - 1]);
|
|
assert(hour >= 0 && hour < 24);
|
|
assert(minute >= 0 && minute < 60);
|
|
assert(second >= 0 && second < 60);
|
|
assert(microsecond >= 0 && microsecond < 1000000);
|
|
|
|
const int yoffset = (year - 2001);
|
|
// C99 and C++11 specify that integer division truncates toward zero
|
|
const int nleap = (yoffset >= 0) ? (yoffset / 400 - yoffset / 100 + yoffset / 4) : ((yoffset - 399) / 400 - (yoffset - 99) / 100 + (yoffset - 3) / 4);
|
|
|
|
int days = 365 * yoffset + nleap;
|
|
|
|
// month offset
|
|
const bool leap = is_leap_year(year);
|
|
for (int i = 1; i < month; ++i) {
|
|
days += month_days[leap][i - 1];
|
|
}
|
|
|
|
// day offset
|
|
days += (day - 1);
|
|
|
|
// final timestamp
|
|
m_timestamp = days * Time::Day + hour * Time::Hour + minute * Time::Minute + second * Time::Second + microsecond * Time::Microsecond;
|
|
}
|
|
|
|
Time::DateTime::DateTime(double gameTime) :
|
|
DateTime(3200, 1, 1, 0, 0, 0)
|
|
{
|
|
*this += Time::TimeDelta(gameTime, Time::Second);
|
|
}
|
|
|
|
void Time::DateTime::GetDateParts(int *out_year, int *out_month, int *out_day) const
|
|
{
|
|
if (out_year || out_month || out_day) {
|
|
static_assert(Time::Day > (Sint64(1) << 32),
|
|
"code below assumes that the 'date' part of a 64-bit timestamp fits in 32 bits");
|
|
|
|
// number of days from the epoch to the beginning of the stored date
|
|
int days = divmod_euclid(m_timestamp, Sint64(Time::Day)).first;
|
|
|
|
// work out how many completed cycles, centuries, 'quads' and years we've measured since the epoch
|
|
// computed such that n400 may be negative, but all other values must be positive
|
|
|
|
int n400, n100, n4, n1;
|
|
std::tie(n400, days) = divmod_euclid(days, 365 * 400 + 97);
|
|
// days must be non-negative after this, so we can use truncating division from here
|
|
std::tie(n100, days) = divmod_trunc(days, 365 * 100 + 24);
|
|
std::tie(n4, days) = divmod_trunc(days, 365 * 4 + 1);
|
|
std::tie(n1, days) = divmod_trunc(days, 365);
|
|
|
|
int year = 2001 + 400 * n400 + 100 * n100 + 4 * n4 + n1;
|
|
int day = days;
|
|
|
|
// the last day in a 400-year or a 4-year cycle are handled incorrectly,
|
|
// because n100 is calculated assuming each century has 24 (not 25) leaps,
|
|
// and n1 is calculated assuming each year has 365 (not 366) days
|
|
// adjust for those mistakes here
|
|
if (n100 == 4 || n1 == 4) {
|
|
assert(!((n100 == 4) && (n1 == 4)));
|
|
--year;
|
|
day += 365;
|
|
assert(is_leap_year(year));
|
|
}
|
|
|
|
bool leap = is_leap_year(year);
|
|
|
|
int month = 0;
|
|
while (day >= month_days[leap][month]) {
|
|
day -= month_days[leap][month++];
|
|
assert(month < 12);
|
|
}
|
|
|
|
if (out_year) {
|
|
*out_year = year;
|
|
}
|
|
if (out_month) {
|
|
*out_month = month + 1;
|
|
}
|
|
if (out_day) {
|
|
*out_day = day + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Time::DateTime::GetTimeParts(int *out_hour, int *out_minute, int *out_second, int *out_microsecond) const
|
|
{
|
|
if (out_hour || out_minute || out_second || out_microsecond) {
|
|
const Sint64 tstamp = divmod_euclid(m_timestamp, Sint64(Time::Day)).second;
|
|
assert(tstamp >= 0);
|
|
|
|
if (out_microsecond) {
|
|
*out_microsecond = (tstamp / Time::Microsecond) % 1000000;
|
|
}
|
|
|
|
const int seconds = (tstamp / Time::Second);
|
|
assert(seconds >= 0 && seconds < 24 * 60 * 60);
|
|
|
|
if (out_hour) {
|
|
*out_hour = (seconds / 3600);
|
|
}
|
|
if (out_minute) {
|
|
*out_minute = (seconds / 60) % 60;
|
|
}
|
|
if (out_second) {
|
|
*out_second = (seconds / 1) % 60;
|
|
}
|
|
}
|
|
}
|
|
|
|
double Time::DateTime::ToGameTime() const
|
|
{
|
|
const Time::DateTime base(3200, 1, 1, 0, 0, 0);
|
|
Time::TimeDelta tstamp = (*this - base);
|
|
if (*this < base) {
|
|
// adjustment to give correct rounding for GetTotalSeconds()
|
|
tstamp -= Time::TimeDelta(Time::Second - 1, Time::TimeUnit(1));
|
|
}
|
|
return double(tstamp.GetTotalSeconds());
|
|
}
|
|
|
|
std::string Time::DateTime::ToDateString() const
|
|
{
|
|
char buf[32];
|
|
int year, month, day;
|
|
GetDateParts(&year, &month, &day);
|
|
snprintf(buf, sizeof(buf), "%04d-%02d-%02d", year, month, day);
|
|
return std::string(buf);
|
|
}
|
|
|
|
std::string Time::DateTime::ToTimeString() const
|
|
{
|
|
char buf[16];
|
|
int hour, minute, second;
|
|
GetTimeParts(&hour, &minute, &second);
|
|
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour, minute, second);
|
|
return std::string(buf);
|
|
}
|
|
|
|
std::string Time::DateTime::ToStringISO8601() const
|
|
{
|
|
char buf[64];
|
|
int year, month, day, hour, minute, second;
|
|
GetDateParts(&year, &month, &day);
|
|
GetTimeParts(&hour, &minute, &second);
|
|
snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02dZ", year, month, day, hour, minute, second);
|
|
return std::string(buf);
|
|
}
|