177 lines
5.1 KiB
Ragel
177 lines
5.1 KiB
Ragel
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* Copyright 2017-2018 Danny Robson <danny@nerdcruft.net>
|
|
*/
|
|
|
|
#include <cruft/util/time/parse.hpp>
|
|
|
|
#include <cruft/util/debug/assert.hpp>
|
|
#include <cruft/util/posix/except.hpp>
|
|
|
|
#include <stdexcept>
|
|
#include <iostream>
|
|
|
|
// We generate some really old style C code via ragel here, so we have to
|
|
// disable some noisy warnings (doubly so given -Werror)
|
|
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
%%{
|
|
# based off rfc3339 rather than iso8601 because the former is public
|
|
|
|
machine iso8601;
|
|
|
|
date_fullyear = digit{4};
|
|
date_month = digit{2};
|
|
date_mday = digit{2};
|
|
|
|
time_hour = digit{2};
|
|
time_minute = digit{2};
|
|
time_second = digit{2};
|
|
|
|
time_secfrac = '.' digit{1,} ${ frac *= 10; frac += fc - '0'; };
|
|
time_numoffset = ('+' %{dir=1;} | '-' %{dir=-1;})
|
|
time_hour ${ offset.tm_hour *= 10; offset.tm_hour += fc - '0'; }
|
|
':' time_minute ${ offset.tm_min *= 10; offset.tm_min += fc - '0'; };
|
|
time_offset = 'Z' | time_numoffset;
|
|
|
|
partial_time = time_hour ${ parts.tm_hour *= 10; parts.tm_hour += fc - '0'; }
|
|
':' time_minute ${ parts.tm_min *= 10; parts.tm_min += fc - '0'; }
|
|
':' time_second ${ parts.tm_sec *= 10; parts.tm_sec += fc - '0'; }
|
|
time_secfrac?;
|
|
|
|
full_date = date_fullyear ${ parts.tm_year *= 10; parts.tm_year += fc - '0'; }
|
|
'-' date_month ${ parts.tm_mon *= 10; parts.tm_mon += fc - '0'; }
|
|
'-' date_mday ${ parts.tm_mday *= 10; parts.tm_mday += fc - '0'; };
|
|
|
|
full_time = partial_time time_offset;
|
|
|
|
date_time := (
|
|
full_date 'T' full_time
|
|
)
|
|
>{ success = false; }
|
|
%{ success = true; };
|
|
|
|
write data;
|
|
}%%
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
template <>
|
|
bool
|
|
cruft::debug::validator<tm>::is_valid (const tm &val) noexcept
|
|
{
|
|
// we don't test tm_year anywhere here because there isn't a valid range
|
|
// for years, only that they are expressed as offsets from 1900;
|
|
return val.tm_sec >= 0 && val.tm_sec <= 60 &&
|
|
val.tm_min >= 0 && val.tm_min < 60 &&
|
|
val.tm_hour >= 0 && val.tm_hour < 24 &&
|
|
val.tm_mday > 0 && val.tm_mday <= 31 &&
|
|
val.tm_mon >= 0 && val.tm_mon < 12 &&
|
|
val.tm_wday >= 0 && val.tm_wday < 7 &&
|
|
val.tm_yday >= 0 && val.tm_yday <= 365;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
std::ostream&
|
|
operator<< (std::ostream &os, const tm&)
|
|
{
|
|
return os << "{}";
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
std::chrono::seconds
|
|
to_epoch (const tm &t)
|
|
{
|
|
// TODO: it's assumed the user isn't passing in oddities like 36 months or
|
|
// similar. in the future we can account for this
|
|
CHECK_SANITY (t);
|
|
|
|
constexpr int
|
|
cumulative_days [12] = {
|
|
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
|
|
};
|
|
|
|
constexpr int epoch_year = 1970;
|
|
const int year = 1900 + t.tm_year;
|
|
|
|
// find the number of days since 1970. careful of leap years.
|
|
time_t secs;
|
|
secs = (year - epoch_year) * 365 + cumulative_days[t.tm_mon % 12];
|
|
secs += (year - epoch_year + epoch_year % 4) / 4;
|
|
secs -= (year - epoch_year + epoch_year % 100) / 100;
|
|
secs += (year - epoch_year + epoch_year % 400) / 400;
|
|
|
|
const bool is_leap_year = (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
|
|
if (is_leap_year && t.tm_mon < 2)
|
|
secs--;
|
|
|
|
secs += t.tm_mday - 1;
|
|
|
|
// hours
|
|
secs *= 24;
|
|
secs += t.tm_hour;
|
|
|
|
// minutes
|
|
secs *= 60;
|
|
secs += t.tm_min;
|
|
|
|
// seconds
|
|
secs *= 60;
|
|
secs += t.tm_sec;
|
|
|
|
if (t.tm_isdst)
|
|
secs -= 60 * 60;
|
|
|
|
return std::chrono::seconds {secs};
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
std::chrono::nanoseconds
|
|
cruft::time::iso8601::parse (cruft::view<const char*> str)
|
|
{
|
|
int cs;
|
|
const char *p = std::begin (str);
|
|
const char *pe = std::end (str);
|
|
const char *eof = pe;
|
|
|
|
bool success = false;
|
|
|
|
int dir = 0;
|
|
int64_t frac = 0;
|
|
struct tm parts, offset;
|
|
memset (&parts, 0, sizeof (parts));
|
|
memset (&offset, 0, sizeof (offset));
|
|
|
|
%%write init;
|
|
%%write exec;
|
|
|
|
if (!success)
|
|
throw std::invalid_argument ("invalid date string");
|
|
|
|
parts.tm_year -= 1900;
|
|
parts.tm_mon -= 1;
|
|
|
|
// compute the timezone offset
|
|
std::chrono::seconds diff {
|
|
dir * (offset.tm_hour * 60 * 60 + offset.tm_min * 60)
|
|
};
|
|
|
|
// fractional part
|
|
auto nano_digits = cruft::digits10 (std::nano::den-1);
|
|
auto frac_digits = cruft::digits10 (frac);
|
|
auto shift = cruft::pow (10, unsigned(nano_digits - frac_digits));
|
|
|
|
// sum the time_t, timezone offset, and fractional components
|
|
return to_epoch (parts) - diff + std::chrono::nanoseconds (frac * shift);
|
|
}
|
|
|