From fdcab4eafd0956dedaebe1ba1832e81dbdb788df Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Tue, 19 Mar 2019 16:02:07 +1100 Subject: [PATCH] parse/time: add duration parsing --- CMakeLists.txt | 3 + parse/time.cpp | 130 ++++++++++++++++++++++++++++++++++++++++++++ parse/time.hpp | 27 +++++++++ test/parse/time.cpp | 45 +++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 parse/time.cpp create mode 100644 parse/time.hpp create mode 100644 test/parse/time.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fc472e2..238c8c9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/parse/time.cpp b/parse/time.cpp new file mode 100644 index 00000000..923b7fb6 --- /dev/null +++ b/parse/time.cpp @@ -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 + */ + +#include "parse/time.hpp" + +#include "parse/value.hpp" +#include "../ascii.hpp" + +#include + + +// CXX#20: Define these ratios that aren't currently in all our +// implementations of the stdlib. +using days = std::chrono::duration>; +using weeks = std::chrono::duration>; +using months = std::chrono::duration>; +using years = std::chrono::duration>; + + +/////////////////////////////////////////////////////////////////////////////// +/// Shorthand for converting an arbitrary duration to nanoseconds. +template +std::chrono::nanoseconds to_ns (DurationT val) +{ + return std::chrono::duration_cast (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 +static bool +try_consume_prefix ( + cruft::view &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 +cruft::parse::duration (cruft::view &remain) +{ + auto const count = value (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); +} diff --git a/parse/time.hpp b/parse/time.hpp new file mode 100644 index 00000000..fff460ef --- /dev/null +++ b/parse/time.hpp @@ -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 + */ + +#pragma once + +#include "../expected.hpp" +#include "../view.hpp" + +#include +#include + +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 + duration (cruft::view &); +} diff --git a/test/parse/time.cpp b/test/parse/time.cpp new file mode 100644 index 00000000..0cd71967 --- /dev/null +++ b/test/parse/time.cpp @@ -0,0 +1,45 @@ +#include "tap.hpp" +#include "parse/time.hpp" + + +/////////////////////////////////////////////////////////////////////////////// +using days = std::chrono::duration>; +using weeks = std::chrono::duration>; +using months = std::chrono::duration>; +using years = std::chrono::duration>; + + +/////////////////////////////////////////////////////////////////////////////// +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 (); +} \ No newline at end of file