1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include "time.h"
5
6#include "dbg.h"
7#include "detect.h"
8#include "str.h"
9
10#include <cmath>
11#include <iomanip> // std::get_time
12#include <sstream> // std::istringstream
13
14static int new_tick = -1;
15
16void set_new_tick()
17{
18 new_tick = 1;
19}
20
21static_assert(std::chrono::steady_clock::is_steady, "Compiler does not support steady clocks, it might be out of date.");
22static_assert(std::chrono::steady_clock::period::den / std::chrono::steady_clock::period::num >= 1000000000, "Compiler has a bad timer precision and might be out of date.");
23static const std::chrono::time_point<std::chrono::steady_clock> GLOBAL_START_TIME = std::chrono::steady_clock::now();
24
25std::chrono::nanoseconds time_get_nanoseconds()
26{
27 return std::chrono::duration_cast<std::chrono::nanoseconds>(d: std::chrono::steady_clock::now() - GLOBAL_START_TIME);
28}
29
30int64_t time_get_impl()
31{
32 return time_get_nanoseconds().count();
33}
34
35int64_t time_get()
36{
37 static int64_t last = 0;
38 if(new_tick == 0)
39 return last;
40 if(new_tick != -1)
41 new_tick = 0;
42
43 last = time_get_impl();
44 return last;
45}
46
47int64_t time_timestamp()
48{
49 return time(timer: nullptr);
50}
51
52static tm time_localtime_threadlocal(time_t *time_data)
53{
54#if defined(CONF_FAMILY_WINDOWS)
55 // The result of localtime is thread-local on Windows
56 // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-localtime32-localtime64
57 tm *time = localtime(time_data);
58#else
59 // Thread-local buffer for the result of localtime_r
60 thread_local tm time_info_buf; // NOLINT(misc-use-internal-linkage) // TODO: remove NOLINT when updating clang-tidy version
61 tm *time = localtime_r(timer: time_data, tp: &time_info_buf);
62#endif
63 dbg_assert(time != nullptr, "Failed to get local time for time data %" PRId64, (int64_t)time_data);
64 return *time;
65}
66
67int time_houroftheday()
68{
69 time_t time_data;
70 time(timer: &time_data);
71 const tm time_info = time_localtime_threadlocal(time_data: &time_data);
72 return time_info.tm_hour;
73}
74
75static bool time_iseasterday(time_t time_data, tm time_info)
76{
77 // compute Easter day (Sunday) using https://en.wikipedia.org/w/index.php?title=Computus&oldid=890710285#Anonymous_Gregorian_algorithm
78 int Y = time_info.tm_year + 1900;
79 int a = Y % 19;
80 int b = Y / 100;
81 int c = Y % 100;
82 int d = b / 4;
83 int e = b % 4;
84 int f = (b + 8) / 25;
85 int g = (b - f + 1) / 3;
86 int h = (19 * a + b - d - g + 15) % 30;
87 int i = c / 4;
88 int k = c % 4;
89 int L = (32 + 2 * e + 2 * i - h - k) % 7;
90 int m = (a + 11 * h + 22 * L) / 451;
91 int month = (h + L - 7 * m + 114) / 31;
92 int day = ((h + L - 7 * m + 114) % 31) + 1;
93
94 // (now-1d ≤ easter ≤ now+2d) <=> (easter-2d ≤ now ≤ easter+1d) <=> (Good Friday ≤ now ≤ Easter Monday)
95 for(int day_offset = -1; day_offset <= 2; day_offset++)
96 {
97 time_data = time_data + day_offset * 60 * 60 * 24;
98 const tm offset_time_info = time_localtime_threadlocal(time_data: &time_data);
99 if(offset_time_info.tm_mon == month - 1 && offset_time_info.tm_mday == day)
100 return true;
101 }
102 return false;
103}
104
105ETimeSeason time_season()
106{
107 time_t time_data;
108 time(timer: &time_data);
109 const tm time_info = time_localtime_threadlocal(time_data: &time_data);
110
111 if((time_info.tm_mon == 11 && time_info.tm_mday == 31) || (time_info.tm_mon == 0 && time_info.tm_mday == 1))
112 {
113 return ETimeSeason::NEWYEAR;
114 }
115 else if(time_info.tm_mon == 11 && time_info.tm_mday >= 24 && time_info.tm_mday <= 26)
116 {
117 return ETimeSeason::XMAS;
118 }
119 else if((time_info.tm_mon == 9 && time_info.tm_mday == 31) || (time_info.tm_mon == 10 && time_info.tm_mday == 1))
120 {
121 return ETimeSeason::HALLOWEEN;
122 }
123 else if(time_iseasterday(time_data, time_info))
124 {
125 return ETimeSeason::EASTER;
126 }
127
128 switch(time_info.tm_mon)
129 {
130 case 11:
131 case 0:
132 case 1:
133 return ETimeSeason::WINTER;
134 case 2:
135 case 3:
136 case 4:
137 return ETimeSeason::SPRING;
138 case 5:
139 case 6:
140 case 7:
141 return ETimeSeason::SUMMER;
142 case 8:
143 case 9:
144 case 10:
145 return ETimeSeason::AUTUMN;
146 default:
147 dbg_assert_failed("Invalid month: %d", time_info.tm_mon);
148 }
149}
150
151#ifdef __GNUC__
152#pragma GCC diagnostic push
153#pragma GCC diagnostic ignored "-Wformat-nonliteral"
154#endif
155void str_timestamp(char *buffer, int buffer_size)
156{
157 str_timestamp_format(buffer, buffer_size, format: TimestampFormat::NOSPACE);
158}
159
160void str_timestamp_format(char *buffer, int buffer_size, const char *format)
161{
162 time_t time_data;
163 time(timer: &time_data);
164 str_timestamp_ex(time: time_data, buffer, buffer_size, format);
165}
166
167void str_timestamp_ex(time_t time_data, char *buffer, int buffer_size, const char *format)
168{
169 const tm time_info = time_localtime_threadlocal(time_data: &time_data);
170 strftime(s: buffer, maxsize: buffer_size, format: format, tp: &time_info);
171 buffer[buffer_size - 1] = 0; /* assure null termination */
172}
173
174bool timestamp_from_str(const char *string, const char *format, time_t *timestamp)
175{
176 std::tm tm{};
177 tm.tm_isdst = -1; // determine DST from parsed date
178 std::istringstream ss(string);
179 ss >> std::get_time(tmb: &tm, fmt: format);
180 if(ss.fail() || !ss.eof())
181 return false;
182
183 time_t result = mktime(tp: &tm);
184 if(result < 0)
185 return false;
186
187 *timestamp = result;
188 return true;
189}
190#ifdef __GNUC__
191#pragma GCC diagnostic pop
192#endif
193
194int64_t time_milliseconds_from_seconds(float seconds)
195{
196 return static_cast<int64_t>(std::round(x: static_cast<double>(seconds) * 1000.0));
197}
198
199int str_time(int64_t centisecs, ETimeFormat format, char *buffer, int buffer_size)
200{
201 dbg_assert(buffer_size > 0, "Invalid buffer size: %d", buffer_size);
202
203 const int sec = 100;
204 const int min = 60 * sec;
205 const int hour = 60 * min;
206 const int day = 24 * hour;
207
208 if(centisecs < 0)
209 centisecs = 0;
210
211 switch(format)
212 {
213 case ETimeFormat::DAYS:
214 if(centisecs >= day)
215 return str_format(buffer, buffer_size, "%" PRId64 "d %02" PRId64 ":%02" PRId64 ":%02" PRId64, centisecs / day,
216 (centisecs % day) / hour, (centisecs % hour) / min, (centisecs % min) / sec);
217 [[fallthrough]];
218 case ETimeFormat::HOURS:
219 if(centisecs >= hour)
220 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64 ":%02" PRId64, centisecs / hour,
221 (centisecs % hour) / min, (centisecs % min) / sec);
222 [[fallthrough]];
223 case ETimeFormat::MINS:
224 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64, centisecs / min,
225 (centisecs % min) / sec);
226 case ETimeFormat::HOURS_CENTISECS:
227 if(centisecs >= hour)
228 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64 ":%02" PRId64 ".%02" PRId64, centisecs / hour,
229 (centisecs % hour) / min, (centisecs % min) / sec, centisecs % sec);
230 [[fallthrough]];
231 case ETimeFormat::MINS_CENTISECS:
232 if(centisecs >= min)
233 return str_format(buffer, buffer_size, "%02" PRId64 ":%02" PRId64 ".%02" PRId64, centisecs / min,
234 (centisecs % min) / sec, centisecs % sec);
235 [[fallthrough]];
236 case ETimeFormat::SECS_CENTISECS:
237 return str_format(buffer, buffer_size, "%02" PRId64 ".%02" PRId64, (centisecs % min) / sec, centisecs % sec);
238 default:
239 dbg_assert_failed("Invalid time format: %d", (int)format);
240 }
241}
242
243int str_time_float(float secs, ETimeFormat format, char *buffer, int buffer_size)
244{
245 int64_t millis = time_milliseconds_from_seconds(seconds: secs);
246 return str_time(centisecs: millis / 10, format, buffer, buffer_size);
247}
248