libcruft-util/parse/time.cpp

150 lines
5.1 KiB
C++

/*
* 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::consume (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);
}
//-----------------------------------------------------------------------------
cruft::expected<std::chrono::nanoseconds, std::errc>
cruft::parse::duration::from (cruft::view<char const*> const &str)
{
auto remain = str;
// Try to parse from the temporary view.
auto res = ::cruft::parse::duration::consume (remain);
if (!res)
return res;
// Ensure it was totally consumed.
if (!remain.empty ())
return cruft::unexpected (std::errc::invalid_argument);
return res;
}