The Battle for Wesnoth  1.19.7+dev
format_timespan.hpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2024
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 namespace implementation
28 {
29 static constexpr std::array descriptors {
30  // TRANSLATORS: The "timespan^$num xxxxx" strings originating from the same file
31  // as the string with this comment MUST be translated following the usual rules
32  // for WML variable interpolation -- that is, without including or translating
33  // the caret^ prefix, and leaving the $num variable specification intact, since
34  // it is technically code. The only translatable natural word to be found here
35  // is the time unit (year, month, etc.) For example, for French you would
36  // translate "timespan^$num years" as "$num ans", thus allowing the game UI to
37  // generate output such as "39 ans" after variable interpolation.
38  std::tuple{ N_n("timespan^$num year", "timespan^$num years") },
39  std::tuple{ N_n("timespan^$num month", "timespan^$num months") },
40  std::tuple{ N_n("timespan^$num week", "timespan^$num weeks") },
41  std::tuple{ N_n("timespan^$num day", "timespan^$num days") },
42  std::tuple{ N_n("timespan^$num hour", "timespan^$num hours") },
43  std::tuple{ N_n("timespan^$num minute", "timespan^$num minutes") },
44  std::tuple{ N_n("timespan^$num second", "timespan^$num seconds") },
45 };
46 
47 // Each duration type should have its description at its matching descriptor index
48 static constexpr auto deconstruct_format = std::tuple<
53  std::chrono::hours,
54  std::chrono::minutes,
55  std::chrono::seconds
56 >{};
57 
58 } // namespace implementation
59 
60 /**
61  * Formats a timespan into human-readable text for player authentication functions.
62  *
63  * This is generally meant for player-facing text rather than lightweight tasks like
64  * debug logging. The resulting output may differ based on current language settings.
65  *
66  * This is intentionally not a very thorough representation of time intervals.
67  * See <https://github.com/wesnoth/wesnoth/issues/6036> for more information.
68  *
69  * @param span The timespan to format
70  * @param detailed Whether to display more specific values such as "3 months, 2 days,
71  * 30 minutes, and 1 second". If not specified or set to @a false, the
72  * return value will ONLY include most significant time unit (e.g. "3
73  * months").
74  * @return A human-readable timespan description.
75  *
76  * @note The implementation formats the given timespan according to periods defined by
77  * the C++ chrono standard. As such, a year is defined as its average Gregorian
78  * length of 365.2425 days, while months are exactly 1/12 of a year. Furthermore,
79  * it doesn't take into account leap years or leap seconds. If you need to
80  * account for those, you are better off importing a new library and providing it
81  * with more specific information about the start and end times of the interval;
82  * otherwise your next best option is to hire a fortune teller to manually service
83  * your requests every time instead of this function.
84  */
85 template<typename Rep, typename Period>
86 static std::string format_timespan(const std::chrono::duration<Rep, Period>& span, bool detailed = false)
87 {
88  if(span.count() <= 0) {
89  return _("timespan^expired");
90  }
91 
92  std::vector<t_string> display_text;
93  const auto push_description = [&](const auto& time_component, const auto& description)
94  {
95  auto amount = time_component.count();
96  if(amount <= 0) {
97  return true; // Continue to next element
98  }
99 
100  const auto& [fmt_singular, fmt_plural] = description;
101  display_text.emplace_back(VNGETTEXT(fmt_singular, fmt_plural, amount, {{"num", std::to_string(amount)}}));
102  return detailed;
103  };
104 
105  std::apply(
106  [&push_description](auto&&... args) {
107  std::size_t i{0};
108  (... && push_description(args, implementation::descriptors[i++]));
109  },
111 
112  return format_conjunct_list(_("timespan^expired"), display_text);
113 }
114 
115 } // namespace utils
#define VNGETTEXT(msgid, msgid_plural, count,...)
std::size_t i
Definition: function.cpp:1029
#define N_n(String1, String2)
Definition: gettext.hpp:102
static std::string _(const char *str)
Definition: gettext.hpp:93
std::chrono::duration< int, std::ratio< 2629746 > > months
Definition: chrono.hpp:41
std::chrono::duration< int, std::ratio< 31556952 > > years
Definition: chrono.hpp:42
std::chrono::duration< int, std::ratio< 604800 > > weeks
Definition: chrono.hpp:40
constexpr auto deconstruct_duration(const std::tuple< Ts... > &, const std::chrono::duration< Rep, Period > &span)
Definition: chrono.hpp:76
std::chrono::duration< int, std::ratio< 86400 > > days
Definition: chrono.hpp:39
Contains the implementation details for lexical_cast and shouldn't be used directly.
static constexpr auto deconstruct_format
static constexpr std::array descriptors
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.