Danny Robson
f6056153e3
This places, at long last, the core library code into the same namespace as the extended library code.
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 "time/parse.hpp"
|
|
|
|
#include "debug.hpp"
|
|
#include "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);
|
|
}
|
|
|