The Battle for Wesnoth  1.19.25+dev
format_timespan.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2024 - 2025
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #pragma once
16 
17 #include "formula/string_utils.hpp"
18 #include "serialization/chrono.hpp"
19 
20 #include <array>
21 #include <chrono>
22 #include <string>
23 #include <vector>
24 
25 namespace utils
26 {
27 
28 /**
29  * Formats a timespan into human-readable text for player authentication functions.
30  *
31  * This is generally meant for player-facing text rather than lightweight tasks like
32  * debug logging. The resulting output may differ based on current language settings.
33  *
34  * This is intentionally not a very thorough representation of time intervals.
35  * See <https://github.com/wesnoth/wesnoth/issues/6036> for more information.
36  *
37  * @param span The timespan to format
38  * @param detailed Whether to display more specific values such as "3 months, 2 days,
39  * 30 minutes, and 1 second". If not specified or set to @a false, the
40  * return value will ONLY include most significant time unit (e.g. "3
41  * months").
42  * @return A human-readable timespan description.
43  *
44  * @note The implementation formats the given timespan according to periods defined by
45  * the C++ chrono standard. As such, a year is defined as its average Gregorian
46  * length of 365.2425 days, while months are exactly 1/12 of a year. Furthermore,
47  * it doesn't take into account leap years or leap seconds. If you need to
48  * account for those, you are better off importing a new library and providing it
49  * with more specific information about the start and end times of the interval;
50  * otherwise your next best option is to hire a fortune teller to manually service
51  * your requests every time instead of this function.
52  */
53 template<typename Rep, typename Period>
54 static std::string format_timespan(const std::chrono::duration<Rep, Period>& span, bool detailed = false)
55 {
56  if(span.count() <= 0) {
57  return _("timespan^expired");
58  }
59 
60  std::vector<t_string> display_text;
61  auto [years, months, weeks, days, hours, minutes, seconds] = chrono::deconstruct_duration<chrono::years, chrono::months, chrono::weeks, chrono::days, std::chrono::hours, std::chrono::minutes, std::chrono::seconds>(span);
62 
63  const auto push_description = [&display_text, detailed](const auto& time_component, const auto& fmt_singular, const auto& fmt_plural)
64  {
65  auto amount = time_component.count();
66  if(amount <= 0) {
67  return true; // Continue to next element
68  }
69 
70  display_text.emplace_back(VNGETTEXT(fmt_singular, fmt_plural, amount, {{"num", std::to_string(amount)}}));
71  return detailed;
72  };
73 
74  // TRANSLATORS: The "timespan^$num xxxxx" strings originating from the same file
75  // as the string with this comment MUST be translated following the usual rules
76  // for WML variable interpolation -- that is, without including or translating
77  // the caret^ prefix, and leaving the $num variable specification intact, since
78  // it is technically code. The only translatable natural word to be found here
79  // is the time unit (year, month, etc.) For example, for French you would
80  // translate "timespan^$num years" as "$num ans", thus allowing the game UI to
81  // generate output such as "39 ans" after variable interpolation.
82  push_description(years, N_n("timespan^$num year", "timespan^$num years" )) &&
83  push_description(months, N_n("timespan^$num month", "timespan^$num months" )) &&
84  push_description(weeks, N_n("timespan^$num week", "timespan^$num weeks" )) &&
85  push_description(days, N_n("timespan^$num day", "timespan^$num days" )) &&
86  push_description(hours, N_n("timespan^$num hour", "timespan^$num hours" )) &&
87  push_description(minutes, N_n("timespan^$num minute", "timespan^$num minutes")) &&
88  push_description(seconds, N_n("timespan^$num second", "timespan^$num seconds"));
89 
90  return format_conjunct_list(_("timespan^expired"), display_text);
91 }
92 
93 } // namespace utils
#define VNGETTEXT(msgid, msgid_plural, count,...)
#define N_n(String1, String2)
Definition: gettext.hpp:109
static std::string _(const char *str)
Definition: gettext.hpp:100
std::chrono::duration< int, std::ratio< 2629746 > > months
Definition: chrono.hpp:42
std::chrono::duration< int, std::ratio< 31556952 > > years
Definition: chrono.hpp:43
std::chrono::duration< int, std::ratio< 604800 > > weeks
Definition: chrono.hpp:41
std::chrono::duration< int, std::ratio< 86400 > > days
Definition: chrono.hpp:40
static std::string format_timespan(const std::chrono::duration< Rep, Period > &span, bool detailed=false)
Formats a timespan into human-readable text for player authentication functions.
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
std::string to_string(const Range &range, const Func &op)