parse/time: add duration parsing

This commit is contained in:
Danny Robson 2019-03-19 16:02:07 +11:00
parent 48c4866705
commit fdcab4eafd
4 changed files with 205 additions and 0 deletions

View File

@ -389,6 +389,8 @@ list (
nocopy.hpp
parallel/queue.cpp
parallel/queue.hpp
parse/time.cpp
parse/time.hpp
parse/value.cpp
parse/value.hpp
parse/si.cpp
@ -607,6 +609,7 @@ if (TESTS)
memory/deleter
parallel/queue
parse/value
parse/time
parse/si
point
polynomial

130
parse/time.cpp Normal file
View File

@ -0,0 +1,130 @@
/*
* 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 2019 Danny Robson <danny@nerdcruft.net>
*/
#include "parse/time.hpp"
#include "parse/value.hpp"
#include "../ascii.hpp"
#include <chrono>
// CXX#20: Define these ratios that aren't currently in all our
// implementations of the stdlib.
using days = std::chrono::duration<std::int_fast32_t, std::ratio<86400>>;
using weeks = std::chrono::duration<std::int_fast32_t, std::ratio<604800>>;
using months = std::chrono::duration<std::int_fast32_t, std::ratio<2629746>>;
using years = std::chrono::duration<std::int_fast32_t, std::ratio<31556952>>;
///////////////////////////////////////////////////////////////////////////////
/// Shorthand for converting an arbitrary duration to nanoseconds.
template <typename DurationT>
std::chrono::nanoseconds to_ns (DurationT val)
{
return std::chrono::duration_cast<std::chrono::nanoseconds> (val);
}
///////////////////////////////////////////////////////////////////////////////
enum plural_t {
PLURAL, SINGULAR
};
///----------------------------------------------------------------------------
/// Try to consume a prefix of a string view, optionally allowing for
/// pluralisation.
///
/// If the prefix is detected the provided view is modified to point
/// immediately past the prefix, and the value true is returned. Else, the view
/// is untouched and false is returned.
///
/// \tparam _N The number of characters in the null-terminated prefix.
/// \tparam N The number of displayable characters in the prefix.
/// \param str The view to be checked for the prefix.
/// \param prefix The prefix we are checking for.
/// \param plural Whether to (optionall) perform pluralisation on the prefix.
/// \return True IFF the prefix was found and the view was updated.
template <std::size_t _N, std::size_t N = _N - 1>
static bool
try_consume_prefix (
cruft::view<char const*> &str,
char const (&prefix)[_N],
plural_t plural = SINGULAR)
{
static_assert (N > 0);
// Ensure both sequences have the same prefix
if (str.size () < N)
return false;
for (size_t i = 0; i < N; ++i)
if (str[i] != prefix[i])
return false;
// Eat the prefix into a temporary
auto res = str.consume (N);
// Each the plural suffix if we support one
if (plural == PLURAL && res.size () >= 1 && res[0] == 's')
res = res.consume (1);
// If, after the above, we are empty or we have space then we have been
// successful...
if (res.empty () || cruft::ascii::is_space (str[0])) {
str = res;
return true;
}
// ... otherwise we probably have a trailing unprocessed suffix;
// eg, 's' with a trailing 'econds'
return false;
}
///////////////////////////////////////////////////////////////////////////////
cruft::expected<std::chrono::nanoseconds, std::errc>
cruft::parse::duration (cruft::view<char const*> &remain)
{
auto const count = value<std::intmax_t> (remain);
if (remain.empty ())
return to_ns (std::chrono::seconds (count));
while (remain[0] == ' ') {
remain = remain.consume (1);
if (remain.empty ())
return to_ns (std::chrono::seconds (count));
}
if (try_consume_prefix (remain, "ns")) return std::chrono::nanoseconds (count);
if (try_consume_prefix (remain, "us")) return to_ns (std::chrono::microseconds (count));
if (try_consume_prefix (remain, "ms")) return to_ns (std::chrono::milliseconds (count));
if (try_consume_prefix (remain, "s")) return to_ns (std::chrono::seconds (count));
if (try_consume_prefix (remain, "m")) return to_ns (std::chrono::minutes (count));
if (try_consume_prefix (remain, "h")) return to_ns (std::chrono::hours (count));
if (try_consume_prefix (remain, "second", PLURAL))
return to_ns (std::chrono::seconds (count));
if (try_consume_prefix (remain, "minute", PLURAL))
return to_ns (std::chrono::minutes (count));
if (try_consume_prefix (remain, "hour", PLURAL))
return to_ns (std::chrono::hours (count));
if (try_consume_prefix (remain, "day", PLURAL))
return to_ns (days (count));
if (try_consume_prefix (remain, "week", PLURAL))
return to_ns (weeks (count));
if (try_consume_prefix (remain, "month", PLURAL))
return to_ns (months (count));
if (try_consume_prefix (remain, "year", PLURAL))
return to_ns (years (count));
return cruft::unexpected (std::errc::invalid_argument);
}

27
parse/time.hpp Normal file
View File

@ -0,0 +1,27 @@
/*
* 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 2019 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "../expected.hpp"
#include "../view.hpp"
#include <chrono>
#include <system_error>
namespace cruft::parse {
/// Parse a number that represents a duration. eg, "1s", "5 minutes".
///
/// When there is no suffix it is assumed the quantity is in second.
///
/// Note: The quantities are as defined by the standard std::chrono
/// durations. This means that "1 month" will equal just under 30.5 days.
/// Thus the utility is not universally useful for offsetting.
expected<std::chrono::nanoseconds, std::errc>
duration (cruft::view<char const*> &);
}

45
test/parse/time.cpp Normal file
View File

@ -0,0 +1,45 @@
#include "tap.hpp"
#include "parse/time.hpp"
///////////////////////////////////////////////////////////////////////////////
using days = std::chrono::duration<std::int_fast32_t, std::ratio<86400>>;
using weeks = std::chrono::duration<std::int_fast32_t, std::ratio<604800>>;
using months = std::chrono::duration<std::int_fast32_t, std::ratio<2629746>>;
using years = std::chrono::duration<std::int_fast32_t, std::ratio<31556952>>;
///////////////////////////////////////////////////////////////////////////////
static struct {
char const *str;
std::chrono::nanoseconds val;
char const *msg;
} TESTS[] = {
{ .str = "1", .val = std::chrono::seconds (1), .msg = "bare value" },
{ .str = "1ns", .val = std::chrono::nanoseconds (1), .msg = "1 nanosecond" },
{ .str = "1us", .val = std::chrono::microseconds (1), .msg = "1 microsecond" },
{ .str = "1ms", .val = std::chrono::milliseconds (1), .msg = "1 millisecond" },
{ .str = "1s", .val = std::chrono::seconds (1), .msg = "1 second" },
{ .str = "1m", .val = std::chrono::minutes (1), .msg = "1 minute" },
{ .str = "1h", .val = std::chrono::hours (1), .msg = "1 hour" },
{ .str = "1 days", .val = days (1), .msg = "1 day" },
{ .str = "1 weeks", .val = weeks (1), .msg = "1 week" },
{ .str = "1 months", .val = months (1), .msg = "1 month" },
{ .str = "1 years", .val = years (1), .msg = "1 year" },
};
///////////////////////////////////////////////////////////////////////////////
int main ()
{
cruft::TAP::logger tap;
for (auto const &[str,val,msg]: TESTS) {
cruft::view src (str);
auto res = cruft::parse::duration (src);
tap.expect (src.empty () && res && val == *res, "%! %! == %!", msg, val.count (), res->count());
}
return tap.status ();
}