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