netxsimdg
Loading...
Searching...
No Matches
Time.cpp
Go to the documentation of this file.
1
8#include "include/Time.hpp"
9
10#include <regex>
11#include <sstream>
12#include <stdexcept>
13
14namespace Nextsim {
15
16const std::string TimePoint::ymdFormat = "%Y-%m-%d";
17const std::string TimePoint::doyFormat = "%Y-%j";
18const std::string TimePoint::hmsFormat = "T%H:%M:%SZ";
19const std::string TimePoint::ymdhmsFormat = ymdFormat + hmsFormat;
20const std::string TimePoint::doyhmsFormat = doyFormat + hmsFormat;
21std::array<bool, TimeOptions::COUNT> TimeOptions::m_opt = { false, false };
22
23static const int minuteSeconds = 60;
24static const int hourSeconds = minuteSeconds * 60;
25static const int daySeconds = hourSeconds * 24;
26static const int yearSeconds = daySeconds * 365;
27static const int tmEpochYear = 1900;
28static const int unixEpochYear = 1970;
29
30std::tm& tmDoy(std::tm& tm)
31{
32 int month0th[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
33 bool isLeap = ((tm.tm_year % 4 == 0) && (tm.tm_year % 100 != 0)) || (tm.tm_year % 400 == 0);
34 int bissextile = (isLeap && tm.tm_mon >= 2) ? 1 : 0;
35 tm.tm_yday = month0th[tm.tm_mon] + tm.tm_mday + bissextile;
36 return tm;
37}
38
39std::time_t mkgmtime(std::tm* tm, bool recalculateDoy)
40{
41 if (recalculateDoy)
42 tmDoy(*tm);
43 std::time_t sum = tm->tm_sec;
44 sum += tm->tm_min * minuteSeconds;
45 sum += tm->tm_hour * hourSeconds;
46 sum += (tm->tm_yday - 1) * daySeconds;
47 int year = tmEpochYear + tm->tm_year;
48 std::time_t unixYear = year - unixEpochYear;
49 sum += unixYear * yearSeconds;
50
51 // Handle the effect of leap days on the first day of the year. Proleptic Gregorian.
52 int julianLeapDays = (year - 1) / 4 - (unixEpochYear - 1) / 4;
53 sum += julianLeapDays * daySeconds;
54 // Skipped Gregorian leap days
55 sum += (julianGregorianShiftDays(year) - julianGregorianShiftDays(unixEpochYear)) * daySeconds;
56
57 return sum;
58}
59
61{
62 // Note the subtraction of 1 year, as xx00 behaves like (xx-1)99 and not
63 // necessarily like xx01
64 int centurySinceGreg = (year - 1) / 100 - 15;
65 int leaps = (3 * centurySinceGreg) / 4 + 10;
66 return -leaps;
67}
68
69bool isDOYFormat(const std::string& iso)
70{
71 const std::regex ymd("^\\d+-\\d+-\\d+($|T)"); // Search for the month
72 const std::regex doy("^\\d+-\\d+($|T)"); // Search for the day of year
73
74 bool isYMD = std::regex_search(iso, ymd);
75 bool isDOY = std::regex_search(iso, doy);
76
77 if (!isYMD && !isDOY)
78 throw std::invalid_argument("Unrecognized date format: " + iso);
79
80 if (TimeOptions::useDOY() && !isDOY)
81 throw std::invalid_argument("Inconsistent date format: " + iso
82 + " with useDOY = " + (TimeOptions::useDOY() ? "true" : "false"));
83
84 return isDOY;
85}
86
87std::vector<std::string> splitString(const std::string& str, char delim)
88{
89 std::stringstream ss(str);
90 std::string s;
91 std::vector<std::string> vs;
92 while (std::getline(ss, s, delim)) {
93 vs.push_back(s);
94 }
95 return vs;
96}
97
98std::string addTimeLeadingZeros(const std::string& in)
99{
100 // Linux stdlibc++ doesn't like time components without leading zeros
101 std::vector<std::string> dateTime = splitString(in, 'T');
102 std::string out = dateTime[0];
103 if (dateTime.size() > 1) {
104 std::vector<std::string> hms = splitString(dateTime[1], ':');
105 std::stringstream timeStream;
106 timeStream << std::setfill('0');
107 for (size_t i = 0; i < hms.size(); ++i) {
108 timeStream << std::setw(2) << std::stoi(hms[i]);
109 if (i != hms.size() - 1)
110 timeStream << ':';
111 }
112 out += "T" + timeStream.str();
113 }
114
115 return out;
116}
117std::tm getTimTime(const std::string& in, bool isDOY)
118{
119 std::tm tm;
120 // Reset the time values
121 tm.tm_hour = 0;
122 tm.tm_min = 0;
123 tm.tm_sec = 0;
124
125 std::string iso = addTimeLeadingZeros(in);
126 if (isDOY) {
127 // Parse the string manually to deal with broken %j format in libstdc++
128 // Split the string into date and time and prepare to parse the time portion later
129 std::vector<std::string> dateTime = splitString(iso, 'T');
130 if (dateTime.size() > 1) {
131 std::stringstream timeStream("T" + dateTime[1]);
132 timeStream >> std::get_time(&tm, TimePoint::hmsFormat.c_str());
133 }
134 // Parse the date portion by splitting on the (lone) hyphen
135 std::vector<std::string> yearDoy = splitString(dateTime[0], '-');
136 tm.tm_year = std::stoi(yearDoy[0]) - tmEpochYear;
137 tm.tm_yday = std::stoi(yearDoy[1]);
138 } else {
139 std::stringstream(iso) >> std::get_time(&tm, TimePoint::ymdhmsFormat.c_str());
140 }
141 return tm;
142}
143
144std::time_t timeFromISO(const std::string& iso)
145{
146 bool isDOY = isDOYFormat(iso);
147 std::tm tm = getTimTime(iso, isDOY);
148 return mkgmtime(&tm, !isDOY);
149}
150
151std::time_t timeFromISO(std::istream& is)
152{
153 std::string iso;
154 is >> iso;
155 return timeFromISO(iso);
156}
157
158Duration durationFromISO(const std::string& iso, int sign = +1)
159{
160 bool isDOY = isDOYFormat(iso);
161 std::tm tm = getTimTime(iso, isDOY);
162 // Make up the time duration, analogously to mkgmtime()
163 size_t sum = tm.tm_sec;
164 sum += tm.tm_min * minuteSeconds;
165 sum += tm.tm_hour * hourSeconds;
166 if (isDOY) {
167 sum += tm.tm_yday * daySeconds;
168 } else {
169 // 30 day months until real calendar intervals are implemented
170 sum += tm.tm_mon * 30 * daySeconds;
171 sum += tm.tm_mday * daySeconds;
172 }
173 sum += (tmEpochYear + tm.tm_year) * yearSeconds;
174 Duration::Basis dura(std::chrono::seconds(sign * sum));
175 return Duration(dura);
176}
177
178Duration durationFromISO(std::istream& is, int sign = +1)
179{
180 std::string iso;
181 is >> iso;
182 return durationFromISO(iso, sign);
183}
184
185std::istream& Duration::parse(std::istream& is)
186{
187 // read the first character, check it is the ISO standard P
188 char possibleP;
189 is >> possibleP;
190 if (possibleP != 'P') {
191 std::string restOf;
192 is >> restOf;
193 restOf = possibleP + restOf;
194 // If the remaining string is a valid double, then interpret that as a
195 // duration in seconds, else throw an invalid argument exception.
196
197 // Double regex courtesy of https://stackoverflow.com/a/56502134
198 std::regex rx(R"(^([+-]?(?:[[:d:]]+\.?|[[:d:]]*\.[[:d:]]+))(?:[Ee][+-]?[[:d:]]+)?$)");
199 bool isYMD = std::regex_search(restOf, rx);
200 if (!isYMD)
201 throw std::invalid_argument(
202 "The duration should be an ISO 8601 duration (P…) or a number of seconds. Got: "
203 + restOf);
204 double sec = std::stod(restOf);
205 // Assign the seconds value to the Duration
206 setDurationSeconds(sec);
207 return is;
208 }
209
210 // Peek at the next character, to see if it is a -
211 bool isNegative = (is.peek() == '-');
212 if (isNegative) {
213 // pop the negative sign, then parse the rest
214 char sign;
215 is >> sign;
216 }
217 *this = durationFromISO(is, isNegative ? -1 : 1);
218 // Temporary conversion from system_clock to int
219 return is;
220}
221
222Duration::Duration(const std::string& str) { this->parse(str); }
223
224Duration::Duration(double seconds) { setDurationSeconds(seconds); }
225
226void Duration::setDurationSeconds(double secs)
227{
228 std::chrono::duration<double> sec(secs);
229 m_d = std::chrono::duration_cast<Basis>(sec);
230}
231
232TimePoint Duration::operator+(const TimePoint& t) const { return t + *this; }
233
234std::tm* TimePoint::gmtime() const
235{
236 auto tt = Clock::to_time_t(m_t);
237 return std::gmtime(&tt);
238}
239}
std::time_t timeFromISO(const std::string &iso)
Returns a std::time_t from a given ISO date string of a specific format.
Definition Time.cpp:144
int julianGregorianShiftDays(int year)
Returns the number of days between the Julian and Gregorian years.
Definition Time.cpp:60
std::time_t mkgmtime(std::tm *time, bool recalculateDoy=true)
converts the std::tm struct to a std::time_t value assuming UTC.
Definition Time.cpp:39
const double s
Salinity of sea ice. [g kg⁻¹].
Definition constants.hpp:66