Program Listing for File Time.cpp¶
↰ Return to documentation for file (core/src/Time.cpp)
#include "include/Time.hpp"
#include <regex>
#include <sstream>
#include <stdexcept>
namespace Nextsim {
const std::string TimePoint::ymdFormat = "%Y-%m-%d";
const std::string TimePoint::doyFormat = "%Y-%j";
const std::string TimePoint::hmsFormat = "T%H:%M:%SZ";
const std::string TimePoint::ymdhmsFormat = ymdFormat + hmsFormat;
const std::string TimePoint::doyhmsFormat = doyFormat + hmsFormat;
std::array<bool, TimeOptions::COUNT> TimeOptions::m_opt = { false, false };
static const int minuteSeconds = 60;
static const int hourSeconds = minuteSeconds * 60;
static const int daySeconds = hourSeconds * 24;
static const int yearSeconds = daySeconds * 365;
static const int tmEpochYear = 1900;
static const int unixEpochYear = 1970;
std::tm& tmDoy(std::tm& tm)
{
int month0th[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
bool isLeap = ((tm.tm_year % 4 == 0) && (tm.tm_year % 100 != 0)) || (tm.tm_year % 400 == 0);
int bissextile = (isLeap && tm.tm_mon >= 2) ? 1 : 0;
tm.tm_yday = month0th[tm.tm_mon] + tm.tm_mday + bissextile;
return tm;
}
std::time_t mkgmtime(std::tm* tm, bool recalculateDoy)
{
if (recalculateDoy)
tmDoy(*tm);
std::time_t sum = tm->tm_sec;
sum += tm->tm_min * minuteSeconds;
sum += tm->tm_hour * hourSeconds;
sum += (tm->tm_yday - 1) * daySeconds;
int year = tmEpochYear + tm->tm_year;
std::time_t unixYear = year - unixEpochYear;
sum += unixYear * yearSeconds;
// Handle the effect of leap days on the first day of the year. Proleptic Gregorian.
int julianLeapDays = (year - 1) / 4 - (unixEpochYear - 1) / 4;
sum += julianLeapDays * daySeconds;
// Skipped Gregorian leap days
sum += (julianGregorianShiftDays(year) - julianGregorianShiftDays(unixEpochYear)) * daySeconds;
return sum;
}
int julianGregorianShiftDays(int year)
{
// Note the subtraction of 1 year, as xx00 behaves like (xx-1)99 and not
// necessarily like xx01
int centurySinceGreg = (year - 1) / 100 - 15;
int leaps = (3 * centurySinceGreg) / 4 + 10;
return -leaps;
}
bool isYMDFormat(const std::string& iso)
{
const std::regex ymd("^\\d+-\\d+-\\d+($|T)"); // Search for the month
return std::regex_search(iso, ymd);
}
bool isDOYFormat(const std::string& iso)
{
const std::regex doy("^\\d+-\\d+($|T)"); // Search for the day of year
bool isDOY = std::regex_search(iso, doy);
if (!isYMDFormat(iso) && !isDOY)
throw std::invalid_argument("Unrecognized date format: " + iso);
if (TimeOptions::useDOY() && !isDOY)
throw std::invalid_argument("Inconsistent date format: " + iso
+ " with useDOY = " + (TimeOptions::useDOY() ? "true" : "false"));
return isDOY;
}
std::vector<std::string> splitString(const std::string& str, char delim)
{
std::stringstream ss(str);
std::string s;
std::vector<std::string> vs;
while (std::getline(ss, s, delim)) {
vs.push_back(s);
}
return vs;
}
std::string addTimeLeadingZeros(const std::string& in)
{
// Linux stdlibc++ doesn't like time components without leading zeros
std::vector<std::string> dateTime = splitString(in, 'T');
std::string out = dateTime[0];
if (dateTime.size() > 1) {
std::vector<std::string> hms = splitString(dateTime[1], ':');
std::stringstream timeStream;
timeStream << std::setfill('0');
for (size_t i = 0; i < hms.size(); ++i) {
timeStream << std::setw(2) << std::stoi(hms[i]);
if (i != hms.size() - 1)
timeStream << ':';
}
out += "T" + timeStream.str();
}
return out;
}
std::tm getTimTime(const std::string& in, bool isDOY)
{
std::tm tm;
// Reset the time values
tm.tm_hour = 0;
tm.tm_min = 0;
tm.tm_sec = 0;
std::string iso = addTimeLeadingZeros(in);
if (isDOY) {
// Parse the string manually to deal with broken %j format in libstdc++
// Split the string into date and time and prepare to parse the time portion later
std::vector<std::string> dateTime = splitString(iso, 'T');
if (dateTime.size() > 1) {
std::stringstream timeStream("T" + dateTime[1]);
timeStream >> std::get_time(&tm, TimePoint::hmsFormat.c_str());
}
// Parse the date portion by splitting on the (lone) hyphen
std::vector<std::string> yearDoy = splitString(dateTime[0], '-');
tm.tm_year = std::stoi(yearDoy[0]) - tmEpochYear;
tm.tm_yday = std::stoi(yearDoy[1]);
} else {
std::stringstream(iso) >> std::get_time(&tm, TimePoint::ymdhmsFormat.c_str());
}
return tm;
}
std::time_t timeFromISO(const std::string& iso)
{
bool isDOY = isDOYFormat(iso);
std::tm tm = getTimTime(iso, isDOY);
return mkgmtime(&tm, !isDOY);
}
std::time_t timeFromISO(std::istream& is)
{
std::string iso;
is >> iso;
return timeFromISO(iso);
}
Duration durationFromISO(const std::string& iso, int sign = +1)
{
if (isYMDFormat(iso)) {
throw std::invalid_argument(
"Duration does not accept months as they have arbitrary length");
}
bool isDOY = isDOYFormat(iso);
std::tm tm = getTimTime(iso, isDOY);
// Make up the time duration, analogously to mkgmtime()
size_t sum = tm.tm_sec;
sum += tm.tm_min * minuteSeconds;
sum += tm.tm_hour * hourSeconds;
if (isDOY) {
sum += tm.tm_yday * daySeconds;
} else {
// 30 day months until real calendar intervals are implemented
sum += tm.tm_mon * 30 * daySeconds;
sum += tm.tm_mday * daySeconds;
}
sum += (tmEpochYear + tm.tm_year) * yearSeconds;
Duration::Basis dura(std::chrono::seconds(sign * sum));
return Duration(dura);
}
Duration durationFromISO(std::istream& is, int sign = +1)
{
std::string iso;
is >> iso;
return durationFromISO(iso, sign);
}
std::istream& Duration::parse(std::istream& is)
{
// read the first character, check it is the ISO standard P
char possibleP;
is >> possibleP;
if (possibleP != 'P') {
std::string restOf;
is >> restOf;
restOf = possibleP + restOf;
// If the remaining string is a valid double, then interpret that as a
// duration in seconds, else throw an invalid argument exception.
// Double regex courtesy of https://stackoverflow.com/a/56502134
std::regex rx(R"(^([+-]?(?:[[:d:]]+\.?|[[:d:]]*\.[[:d:]]+))(?:[Ee][+-]?[[:d:]]+)?$)");
bool isYMD = std::regex_search(restOf, rx);
if (!isYMD)
throw std::invalid_argument(
"The duration should be an ISO 8601 duration (P…) or a number of seconds. Got: "
+ restOf);
double sec = std::stod(restOf);
// Assign the seconds value to the Duration
setDurationSeconds(sec);
return is;
}
// Peek at the next character, to see if it is a -
bool isNegative = (is.peek() == '-');
if (isNegative) {
// pop the negative sign, then parse the rest
char sign;
is >> sign;
}
*this = durationFromISO(is, isNegative ? -1 : 1);
// Temporary conversion from system_clock to int
return is;
}
Duration::Duration(const std::string& str) { this->parse(str); }
Duration::Duration(double seconds) { setDurationSeconds(seconds); }
void Duration::setDurationSeconds(double secs)
{
std::chrono::duration<double> sec(secs);
m_d = std::chrono::duration_cast<Basis>(sec);
}
TimePoint Duration::operator+(const TimePoint& t) const { return t + *this; }
TimePoint Duration::operator-(const TimePoint& t) const { return t - *this; }
std::tm* TimePoint::gmtime() const
{
auto tt = Clock::to_time_t(m_t);
return std::gmtime(&tt);
}
}