parse/time: add duration parsing
This commit is contained in:
parent
48c4866705
commit
fdcab4eafd
@ -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
130
parse/time.cpp
Normal 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
27
parse/time.hpp
Normal 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
45
test/parse/time.cpp
Normal 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 ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user