From 54fb6b9f24a3dd9c7f8e04be548e6faeb3a75c10 Mon Sep 17 00:00:00 2001 From: Jessica Date: Sat, 30 Oct 2021 22:21:44 -0500 Subject: [PATCH] Add `duration.hpp` and associated tests --- src/include/duration.hpp | 299 +++++++++++++++++++++++++++++++++++++++ src/test/duration.cpp | 226 +++++++++++++++++++++++++++++ 2 files changed, 525 insertions(+) create mode 100644 src/include/duration.hpp create mode 100644 src/test/duration.cpp diff --git a/src/include/duration.hpp b/src/include/duration.hpp new file mode 100644 index 0000000..743f8fa --- /dev/null +++ b/src/include/duration.hpp @@ -0,0 +1,299 @@ +/** + * Copyright (C) 2021 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#pragma once + +#include +#include +#include + +namespace jessilib { + +namespace impl { + +constexpr const char* suffix_helper(const char* begin, const char* end, const char* ignored_suffix) { + while (begin != end + && *begin == *ignored_suffix) { + ++begin; + ++ignored_suffix; + } + + return begin; +} + +} // namespace impl + +// Base 10 only +// y = year (31556952 seconds == 365.2425 day) +// mo = month (2629746 seconds == year/12) +// w = weeks +// d[ay] = days +// h[r] = hours +// m[in] = minutes +// s = seconds +// ms = milliseconds +// us = microseconds +// ns = nanoseconds +// trailing: DurationT + +template +struct duration_from_string_result { + const char* itr{}; + DurationT duration{}; +}; + +// At time of writing, days, weeks, months, years seem inconsistently available +using days = std::chrono::duration>; +using weeks = std::chrono::duration>; +using months = std::chrono::duration>; +using years = std::chrono::duration>; + +template +constexpr duration_from_string_result duration_from_string(const char* begin, const char* end) { + duration_from_string_result result{ begin, {} }; + auto& itr = result.itr; + + // Sanity check + if (end <= itr) { + return result; + } + + while (itr != end) { + // Advance through any whitespace preceding type specifier + while (*itr == ' ') { + ++itr; + + if (itr == end) { + // no text remains + return result; + } + } + + typename DurationT::rep segment{}; + + // Parse duration segment (i.e: [0-9]+[ms][] + while (*itr >= '0' && *itr <= '9') { + segment *= 10; + segment += *itr - '0'; + ++itr; + + if (itr == end) { + // end of string; always treat ending digits as base unit (DurationT) + result.duration += DurationT{ segment }; + return result; + } + } + + // Advance past any whitespace preceding type specifier + while (*itr == ' ') { + ++itr; + + if (itr == end) { + // end of string; always treat ending digits as base unit (DurationT) + result.duration += DurationT{ segment }; + return result; + } + } + + // after switch, 'itr' shall point to first unparsed character, or end + switch (*itr) { + case 'y': // year + ++itr; // 'y' + result.duration += std::chrono::duration_cast(years{ segment }); + + // "yr" + if (itr != end + && *itr == 'r') { + // ignore trailing 'r' + ++itr; + break; + } + + // "year" + itr = impl::suffix_helper(itr, end, "ear"); + break; + + case 'w': // week + ++itr; // 'w' + result.duration += std::chrono::duration_cast(weeks{ segment }); + + if (itr != end + && *itr == 'k') { + // "wk" + ++itr; + break; + } + + // "week" + itr = impl::suffix_helper(itr, end, "eek"); + break; + + case 'd': // day + ++itr; // 'd' + result.duration += std::chrono::duration_cast(days{ segment }); + itr = impl::suffix_helper(itr, end, "ay"); + break; + + case 'h': // hour + ++itr; // 'h' + result.duration += std::chrono::duration_cast(std::chrono::hours{ segment }); + + if (itr != end + && *itr == 'r') { + // "hr" + ++itr; + break; + } + + // "hour" + itr = impl::suffix_helper(itr, end, "our"); + break; + + case 'm': // min/month/milli + ++itr; // 'm' + + if (itr == end) { + // "m" end of string; treat as minutes + result.duration += std::chrono::duration_cast(std::chrono::minutes{ segment }); + return result; + } + + if (*itr == 'i') { + // "minutes" or "milliseconds" + ++itr; + + if (itr != end + && *itr == 'n') { + // minutes + result.duration += std::chrono::duration_cast(std::chrono::minutes{ segment }); + itr = impl::suffix_helper(itr, end, "nutes"); + break; + } + + // anything not starting with "min", assume milliseconds + result.duration += std::chrono::duration_cast(std::chrono::milliseconds{ segment }); + itr = impl::suffix_helper(itr, end, "lliseconds"); + break; + } + + if (*itr == 's') { + // ms + result.duration += std::chrono::duration_cast(std::chrono::milliseconds{ segment }); + ++itr; + break; + } + + if (*itr == 'o') { + // mo + result.duration += std::chrono::duration_cast(months{ segment }); + ++itr; + + // "month" + itr = impl::suffix_helper(itr, end, "nth"); + break; + } + + // Valid scenario for "m "; invalid specifier otherwise + break; + + case 's': + ++itr; // 's' + result.duration += std::chrono::duration_cast(std::chrono::seconds{ segment }); + itr = impl::suffix_helper(itr, end, "econd"); + break; + + case 'u': // micro + ++itr; // 'u' + result.duration += std::chrono::duration_cast(std::chrono::microseconds{ segment }); + itr = impl::suffix_helper(itr, end, "s"); + break; + + case 'n': // nano + ++itr; // 'n' + result.duration += std::chrono::duration_cast(std::chrono::nanoseconds{ segment }); + itr = impl::suffix_helper(itr, end, "s"); + break; + + default: + // unexpected specifier; assume DurationT and cease parsing + result.duration += DurationT{ segment }; + return result; + } + + if (itr == end) { + return result; + } + + // Remove any trailing 's' at end of unit (i.e: years, months, etc) + if (*itr == 's') { + ++itr; + } + } + + return result; +} + +template +constexpr duration_from_string_result duration_from_string(const std::string_view& in_duration) { + return duration_from_string(in_duration.data(), in_duration.data() + in_duration.size()); +} + +namespace literals { + +constexpr std::chrono::nanoseconds operator ""_duration_ns(const char* in_str, size_t in_str_length) { + return duration_from_string(in_str, in_str + in_str_length).duration; +} + +constexpr std::chrono::microseconds operator ""_duration_us(const char* in_str, size_t in_str_length) { + return duration_from_string(in_str, in_str + in_str_length).duration; +} + +constexpr std::chrono::milliseconds operator ""_duration_ms(const char* in_str, size_t in_str_length) { + return duration_from_string(in_str, in_str + in_str_length).duration; +} + +constexpr std::chrono::seconds operator ""_duration_s(const char* in_str, size_t in_str_length) { + return duration_from_string(in_str, in_str + in_str_length).duration; +} + +constexpr std::chrono::minutes operator ""_duration_m(const char* in_str, size_t in_str_length) { + return duration_from_string(in_str, in_str + in_str_length).duration; +} + +constexpr std::chrono::hours operator ""_duration_h(const char* in_str, size_t in_str_length) { + return duration_from_string(in_str, in_str + in_str_length).duration; +} + +constexpr days operator ""_duration_d(const char* in_str, size_t in_str_length) { + return duration_from_string(in_str, in_str + in_str_length).duration; +} + +constexpr weeks operator ""_duration_w(const char* in_str, size_t in_str_length) { + return duration_from_string(in_str, in_str + in_str_length).duration; +} + +constexpr months operator ""_duration_mo(const char* in_str, size_t in_str_length) { + return duration_from_string(in_str, in_str + in_str_length).duration; +} + +constexpr years operator ""_duration_y(const char* in_str, size_t in_str_length) { + return duration_from_string(in_str, in_str + in_str_length).duration; +} + +} // namespace literals +} // namespace jessilib diff --git a/src/test/duration.cpp b/src/test/duration.cpp new file mode 100644 index 0000000..d3df9ee --- /dev/null +++ b/src/test/duration.cpp @@ -0,0 +1,226 @@ +/** + * Copyright (C) 2018 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include "duration.hpp" +#include "test.hpp" + +using namespace jessilib; +using namespace std::literals; +using namespace jessilib::literals; + +/* + * ns + * us + * ms + * s + * m + * h + * d + * w + * mo + * y + */ + +// Basic compile-time tests +static_assert("1234ns"_duration_ns.count() == 1234, "1234ns Test Failed"); +static_assert("1234us"_duration_us.count() == 1234, "1234us Test Failed"); +static_assert("1234ms"_duration_ms.count() == 1234, "1234ms Test Failed"); +static_assert("1234s"_duration_s.count() == 1234, "1234s Test Failed"); +static_assert("1234m"_duration_m.count() == 1234, "1234m Test Failed"); +static_assert("1234h"_duration_h.count() == 1234, "1234h Test Failed"); +static_assert("1234d"_duration_d.count() == 1234, "1234d Test Failed"); +static_assert("1234w"_duration_w.count() == 1234, "1234w Test Failed"); +static_assert("1234mo"_duration_mo.count() == 1234, "1234mo Test Failed"); +static_assert("1234y"_duration_y.count() == 1234, "1234y Test Failed"); + +// Ensure string_view variant compiles +static_assert(duration_from_string("1234s"sv).duration.count() == 1234, "1234s string_view test failed"); + +TEST(duration, as_seconds) { + EXPECT_EQ(duration_from_string("1234"sv).duration.count(), 1234); + EXPECT_EQ(duration_from_string("0s"sv).duration.count(), 0); + EXPECT_EQ(duration_from_string("1s"sv).duration.count(), 1); + EXPECT_EQ(duration_from_string("5000ms"sv).duration.count(), 5); + EXPECT_EQ(duration_from_string("5000000us"sv).duration.count(), 5); + EXPECT_EQ(duration_from_string("5000000000ns"sv).duration.count(), 5); + EXPECT_EQ(duration_from_string("1h"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1hr"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1hour"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1hours"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1hrs"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1d"sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string("1day"sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string("1days"sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string("1w"sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string("1week"sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string("1weeks"sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string("1mo"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1mon"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1month"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1months"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1y"sv).duration.count(), 31556952); + EXPECT_EQ(duration_from_string("1year"sv).duration.count(), 31556952); + EXPECT_EQ(duration_from_string("1years"sv).duration.count(), 31556952); +} + +TEST(duration, as_seconds_spaced) { + EXPECT_EQ(duration_from_string("0 s"sv).duration.count(), 0); + EXPECT_EQ(duration_from_string("1 s"sv).duration.count(), 1); + EXPECT_EQ(duration_from_string("5000 ms"sv).duration.count(), 5); + EXPECT_EQ(duration_from_string("5000000 us"sv).duration.count(), 5); + EXPECT_EQ(duration_from_string("5000000000 ns"sv).duration.count(), 5); + EXPECT_EQ(duration_from_string("1 h"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1 hr"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1 hour"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1 hours"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1 hrs"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1 d"sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string("1 day"sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string("1 days"sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string("1 w"sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string("1 week"sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string("1 weeks"sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string("1 mo"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1 mon"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1 month"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1 months"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1 y"sv).duration.count(), 31556952); + EXPECT_EQ(duration_from_string("1 year"sv).duration.count(), 31556952); + EXPECT_EQ(duration_from_string("1 years"sv).duration.count(), 31556952); +} + +TEST(duration, as_seconds_front_spaced) { + EXPECT_EQ(duration_from_string(" 1234"sv).duration.count(), 1234); + EXPECT_EQ(duration_from_string(" 0s"sv).duration.count(), 0); + EXPECT_EQ(duration_from_string(" 1s"sv).duration.count(), 1); + EXPECT_EQ(duration_from_string(" 5000ms"sv).duration.count(), 5); + EXPECT_EQ(duration_from_string(" 5000000us"sv).duration.count(), 5); + EXPECT_EQ(duration_from_string(" 5000000000ns"sv).duration.count(), 5); + EXPECT_EQ(duration_from_string(" 1h"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string(" 1hr"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string(" 1hour"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string(" 1hours"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string(" 1hrs"sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string(" 1d"sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string(" 1day"sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string(" 1days"sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string(" 1w"sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string(" 1week"sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string(" 1weeks"sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string(" 1mo"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string(" 1mon"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string(" 1month"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string(" 1months"sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string(" 1y"sv).duration.count(), 31556952); + EXPECT_EQ(duration_from_string(" 1year"sv).duration.count(), 31556952); + EXPECT_EQ(duration_from_string(" 1years"sv).duration.count(), 31556952); +} + +TEST(duration, as_seconds_back_spaced) { + EXPECT_EQ(duration_from_string("1234 "sv).duration.count(), 1234); + EXPECT_EQ(duration_from_string("0s "sv).duration.count(), 0); + EXPECT_EQ(duration_from_string("1s "sv).duration.count(), 1); + EXPECT_EQ(duration_from_string("5000ms "sv).duration.count(), 5); + EXPECT_EQ(duration_from_string("5000000us "sv).duration.count(), 5); + EXPECT_EQ(duration_from_string("5000000000ns "sv).duration.count(), 5); + EXPECT_EQ(duration_from_string("1h "sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1hr "sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1hour "sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1hours "sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1hrs "sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string("1d "sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string("1day "sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string("1days "sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string("1w "sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string("1week "sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string("1weeks "sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string("1mo "sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1mon "sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1month "sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1months "sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string("1y "sv).duration.count(), 31556952); + EXPECT_EQ(duration_from_string("1year "sv).duration.count(), 31556952); + EXPECT_EQ(duration_from_string("1years "sv).duration.count(), 31556952); +} + +TEST(duration, as_seconds_all_spaced) { + EXPECT_EQ(duration_from_string(" 1234 "sv).duration.count(), 1234); + EXPECT_EQ(duration_from_string(" 0 s "sv).duration.count(), 0); + EXPECT_EQ(duration_from_string(" 1 s "sv).duration.count(), 1); + EXPECT_EQ(duration_from_string(" 5000 ms "sv).duration.count(), 5); + EXPECT_EQ(duration_from_string(" 5000000 us "sv).duration.count(), 5); + EXPECT_EQ(duration_from_string(" 5000000000 ns "sv).duration.count(), 5); + EXPECT_EQ(duration_from_string(" 1 h "sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string(" 1 hr "sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string(" 1 hour "sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string(" 1 hours "sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string(" 1 hrs "sv).duration.count(), 3600); + EXPECT_EQ(duration_from_string(" 1 d "sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string(" 1 day "sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string(" 1 days "sv).duration.count(), 86400); + EXPECT_EQ(duration_from_string(" 1 w "sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string(" 1 week "sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string(" 1 weeks "sv).duration.count(), 604800); + EXPECT_EQ(duration_from_string(" 1 mo "sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string(" 1 mon "sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string(" 1 month "sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string(" 1 months "sv).duration.count(), 2629746); + EXPECT_EQ(duration_from_string(" 1 y "sv).duration.count(), 31556952); + EXPECT_EQ(duration_from_string(" 1 year "sv).duration.count(), 31556952); + EXPECT_EQ(duration_from_string(" 1 years "sv).duration.count(), 31556952); +} + +template +constexpr std::chrono::seconds to_seconds(DurationT in_duration) { + return std::chrono::duration_cast(in_duration); +} + +TEST(duration, combinations) { + EXPECT_EQ(duration_from_string("1d 4 weeks"sv).duration.count(), + to_seconds(days{1} + weeks{4}).count()); + EXPECT_EQ(duration_from_string("1d 4 weeks 1 mon"sv).duration.count(), + to_seconds(days{1} + weeks{4} + months{1}).count()); + EXPECT_EQ(duration_from_string("1d 4 weeks 1 mon 4 min"sv).duration.count(), + to_seconds(days{1} + weeks{4} + months{1} + 4min).count()); + EXPECT_EQ(duration_from_string("1d 4 weeks 1 mon 4 min 36 sec"sv).duration.count(), + to_seconds(days{1} + weeks{4} + months{1} + 4min + 36s).count()); + EXPECT_EQ(duration_from_string("1d 4 weeks 1 mon 4 min 36 sec 12hr"sv).duration.count(), + to_seconds(days{1} + weeks{4} + months{1} + 4min + 36s + 12h).count()); + EXPECT_EQ(duration_from_string("1d 4 weeks 1 mon 4 min 36 sec 12hr 5s"sv).duration.count(), + to_seconds(days{1} + weeks{4} + months{1} + 4min + 36s + 12h + 5s).count()); + + // Consider refactoring such that the expressions are equal without usage of to_seconds for types smaller than seconds + EXPECT_EQ(duration_from_string("1d 4 weeks 1 mon 4 min 36 sec 12hr 5s 4900ms"sv).duration.count(), + to_seconds(days{1} + weeks{4} + months{1} + 4min + 36s + 12h + 5s + to_seconds(4900ms)).count()); + EXPECT_EQ(duration_from_string("1d 4 weeks 1 mon 4 min 36 sec 12hr 5s 4900ms 6900000us"sv).duration.count(), + to_seconds(days{1} + weeks{4} + months{1} + 4min + 36s + 12h + 5s + to_seconds(4900ms) + to_seconds(6900000us)).count()); + EXPECT_EQ(duration_from_string("1d 4 weeks 1 mon 4 min 36 sec 12hr 5s 4900ms 6900000us 900000000ns"sv).duration.count(), + to_seconds(days{1} + weeks{4} + months{1} + 4min + 36s + 12h + 5s + to_seconds(4900ms) + to_seconds(6900000us) + to_seconds(900000000ns)).count()); + EXPECT_EQ(duration_from_string("1d 4 weeks 1 mon 4 min 36 sec 12hr 5s 4900ms 6900000us 900000000ns 7days 6years"sv).duration.count(), + to_seconds(days{1} + weeks{4} + months{1} + 4min + 36s + 12h + 5s + to_seconds(4900ms) + to_seconds(6900000us) + to_seconds(900000000ns) + days{7} + years {6}).count()); +} + +TEST(duration, invalid) { + EXPECT_EQ(duration_from_string("Jessica was here"sv).duration.count(),0); + EXPECT_EQ(duration_from_string("and here"sv).duration.count(),0); + EXPECT_EQ(duration_from_string("second guessing code"sv).duration.count(),0); + EXPECT_EQ(duration_from_string("year round"sv).duration.count(),0); + + EXPECT_EQ(duration_from_string("1yr 3 da 4 mi invalid 77 months"sv).duration.count(), + to_seconds(years{1} + days{3}).count()); // "4 mi" is actually valid, but is rounded down to zero on conversion +}