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_freq()
48{
49 using namespace std::chrono_literals;
50 return std::chrono::nanoseconds(1s).count();
51}
52
53int64_t time_timestamp()
54{
55 return time(timer: nullptr);
56}
57
58static tm time_localtime_threadlocal(time_t *time_data)
59{
60#if defined(CONF_FAMILY_WINDOWS)
61 // The result of localtime is thread-local on Windows
62 // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-localtime32-localtime64
63 tm *time = localtime(time_data);
64#else
65 // Thread-local buffer for the result of localtime_r
66 thread_local tm time_info_buf;
67 tm *time = localtime_r(timer: time_data, tp: &time_info_buf);
68#endif
69 dbg_assert(time != nullptr, "Failed to get local time for time data %" PRId64, (int64_t)time_data);
70 return *time;
71}
72
73int time_houroftheday()
74{
75 time_t time_data;
76 time(timer: &time_data);
77 const tm time_info = time_localtime_threadlocal(time_data: &time_data);
78 return time_info.tm_hour;
79}
80
81static bool time_iseasterday(time_t time_data, tm time_info)
82{
83 // compute Easter day (Sunday) using https://en.wikipedia.org/w/index.php?title=Computus&oldid=890710285#Anonymous_Gregorian_algorithm
84 int Y = time_info.tm_year + 1900;
85 int a = Y % 19;
86 int b = Y / 100;
87 int c = Y % 100;
88 int d = b / 4;
89 int e = b % 4;
90 int f = (b + 8) / 25;
91 int g = (b - f + 1) / 3;
92 int h = (19 * a + b - d - g + 15) % 30;
93 int i = c / 4;
94 int k = c % 4;
95 int L = (32 + 2 * e + 2 * i - h - k) % 7;
96 int m = (a + 11 * h + 22 * L) / 451;
97 int month = (h + L - 7 * m + 114) / 31;
98 int day = ((h + L - 7 * m + 114) % 31) + 1;
99
100 // (now-1d ≤ easter ≤ now+2d) <=> (easter-2d ≤ now ≤ easter+1d) <=> (Good Friday ≤ now ≤ Easter Monday)
101 for(int day_offset = -1; day_offset <= 2; day_offset++)
102 {
103 time_data = time_data + day_offset * 60 * 60 * 24;
104 const tm offset_time_info = time_localtime_threadlocal(time_data: &time_data);
105 if(offset_time_info.tm_mon == month - 1 && offset_time_info.tm_mday == day)
106 return true;
107 }
108 return false;
109}
110
111ETimeSeason time_season()
112{
113 time_t time_data;
114 time(timer: &time_data);
115 const tm time_info = time_localtime_threadlocal(time_data: &time_data);
116
117 if((time_info.tm_mon == 11 && time_info.tm_mday == 31) || (time_info.tm_mon == 0 && time_info.tm_mday == 1))
118 {
119 return ETimeSeason::NEWYEAR;
120 }
121 else if(time_info.tm_mon == 11 && time_info.tm_mday >= 24 && time_info.tm_mday <= 26)
122 {
123 return ETimeSeason::XMAS;
124 }
125 else if((time_info.tm_mon == 9 && time_info.tm_mday == 31) || (time_info.tm_mon == 10 && time_info.tm_mday == 1))
126 {
127 return ETimeSeason::HALLOWEEN;
128 }
129 else if(time_iseasterday(time_data, time_info))
130 {
131 return ETimeSeason::EASTER;
132 }
133
134 switch(time_info.tm_mon)
135 {
136 case 11:
137 case 0:
138 case 1:
139 return ETimeSeason::WINTER;
140 case 2:
141 case 3:
142 case 4:
143 return ETimeSeason::SPRING;
144 case 5:
145 case 6:
146 case 7:
147 return ETimeSeason::SUMMER;
148 case 8:
149 case 9:
150 case 10:
151 return ETimeSeason::AUTUMN;
152 default:
153 dbg_assert_failed("Invalid month: %d", time_info.tm_mon);
154 }
155}
156
157#ifdef __GNUC__
158#pragma GCC diagnostic push
159#pragma GCC diagnostic ignored "-Wformat-nonliteral"
160#endif
161void str_timestamp(char *buffer, int buffer_size)
162{
163 str_timestamp_format(buffer, buffer_size, format: TimestampFormat::NOSPACE);
164}
165
166void str_timestamp_format(char *buffer, int buffer_size, const char *format)
167{
168 time_t time_data;
169 time(timer: &time_data);
170 str_timestamp_ex(time: time_data, buffer, buffer_size, format);
171}
172
173void str_timestamp_ex(time_t time_data, char *buffer, int buffer_size, const char *format)
174{
175 const tm time_info = time_localtime_threadlocal(time_data: &time_data);
176 strftime(s: buffer, maxsize: buffer_size, format: format, tp: &time_info);
177 buffer[buffer_size - 1] = 0; /* assure null termination */
178}
179
180bool timestamp_from_str(const char *string, const char *format, time_t *timestamp)
181{
182 std::tm tm{};
183 std::istringstream ss(string);
184 ss >> std::get_time(tmb: &tm, fmt: format);
185 if(ss.fail() || !ss.eof())
186 return false;
187
188 time_t result = mktime(tp: &tm);
189 if(result < 0)
190 return false;
191
192 *timestamp = result;
193 return true;
194}
195#ifdef __GNUC__
196#pragma GCC diagnostic pop
197#endif
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 return str_time(centisecs: static_cast<int64_t>(std::roundf(x: secs * 1000) / 10), format, buffer, buffer_size);
246}
247