Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include <AK/StringBuilder.h>
28#include <LibCore/DateTime.h>
29#include <sys/time.h>
30#include <time.h>
31
32namespace Core {
33
34DateTime DateTime::now()
35{
36 return from_timestamp(time(nullptr));
37}
38
39DateTime DateTime::create(unsigned year, unsigned month, unsigned day, unsigned hour, unsigned minute, unsigned second)
40{
41 DateTime dt;
42 dt.m_year = year;
43 dt.m_month = month;
44 dt.m_day = day;
45 dt.m_hour = hour;
46 dt.m_minute = minute;
47 dt.m_second = second;
48
49 struct tm tm = {};
50 tm.tm_sec = (int)second;
51 tm.tm_min = (int)minute;
52 tm.tm_hour = (int)hour;
53 tm.tm_mday = (int)day;
54 tm.tm_mon = (int)month - 1;
55 tm.tm_year = (int)year - 1900;
56 tm.tm_wday = (int)dt.weekday();
57 tm.tm_yday = (int)dt.day_of_year();
58 dt.m_timestamp = mktime(&tm);
59
60 return dt;
61}
62
63DateTime DateTime::from_timestamp(time_t timestamp)
64{
65 struct tm tm;
66 localtime_r(×tamp, &tm);
67 DateTime dt;
68 dt.m_year = tm.tm_year + 1900;
69 dt.m_month = tm.tm_mon + 1;
70 dt.m_day = tm.tm_mday;
71 dt.m_hour = tm.tm_hour;
72 dt.m_minute = tm.tm_min;
73 dt.m_second = tm.tm_sec;
74 dt.m_timestamp = timestamp;
75 return dt;
76}
77
78unsigned DateTime::weekday() const
79{
80 int target_year = m_year;
81 static const int seek_table[] = { 0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4 };
82 if (m_month < 3)
83 --target_year;
84
85 return (target_year + target_year / 4 - target_year / 100 + target_year / 400 + seek_table[m_month - 1] + m_day) % 7;
86}
87
88unsigned DateTime::days_in_month() const
89{
90 bool is_long_month = (m_month == 1 || m_month == 3 || m_month == 5 || m_month == 7 || m_month == 8 || m_month == 10 || m_month == 12);
91
92 if (m_month == 2)
93 return is_leap_year() ? 29 : 28;
94
95 return is_long_month ? 31 : 30;
96}
97
98unsigned DateTime::day_of_year() const
99{
100 static const int seek_table[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
101 int day_of_year = seek_table[m_month - 1] + m_day;
102
103 if (is_leap_year() && m_month > 3)
104 day_of_year++;
105
106 return day_of_year - 1;
107}
108
109bool DateTime::is_leap_year() const
110{
111 return ((m_year % 400 == 0) || (m_year % 4 == 0 && m_year % 100 != 0));
112}
113
114void DateTime::set_time(unsigned year, unsigned month, unsigned day, unsigned hour, unsigned minute, unsigned second)
115{
116 m_year = year;
117 m_month = month;
118 m_day = day;
119 m_hour = hour;
120 m_minute = minute;
121 m_second = second;
122
123 struct tm tm = {};
124 tm.tm_sec = (int)second;
125 tm.tm_min = (int)minute;
126 tm.tm_hour = (int)hour;
127 tm.tm_mday = (int)day;
128 tm.tm_mon = (int)month - 1;
129 tm.tm_year = (int)year - 1900;
130 tm.tm_wday = (int)weekday();
131 tm.tm_yday = (int)day_of_year();
132 m_timestamp = mktime(&tm);
133}
134
135String DateTime::to_string(const String& format) const
136{
137
138 const char wday_short_names[7][4] = {
139 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
140 };
141 const char wday_long_names[7][10] = {
142 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
143 };
144 const char mon_short_names[12][4] = {
145 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
146 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
147 };
148 const char mon_long_names[12][10] = {
149 "January", "February", "March", "April", "May", "June",
150 "July", "Auguest", "September", "October", "November", "December"
151 };
152
153 struct tm tm;
154 localtime_r(&m_timestamp, &tm);
155 StringBuilder builder;
156 const int format_len = format.length();
157
158 for (int i = 0; i < format_len; ++i) {
159 if (format[i] != '%') {
160 builder.append(format[i]);
161 } else {
162 if (++i == format_len)
163 return String();
164
165 switch (format[i]) {
166 case 'a':
167 builder.append(wday_short_names[tm.tm_wday]);
168 break;
169 case 'A':
170 builder.append(wday_long_names[tm.tm_wday]);
171 break;
172 case 'b':
173 builder.append(mon_short_names[tm.tm_mon]);
174 break;
175 case 'B':
176 builder.append(mon_long_names[tm.tm_mon]);
177 break;
178 case 'C':
179 builder.appendf("%02d", (tm.tm_year + 1900) / 100);
180 break;
181 case 'd':
182 builder.appendf("%02d", tm.tm_mday);
183 break;
184 case 'D':
185 builder.appendf("%02d/%02d/%02d", tm.tm_mon + 1, tm.tm_mday, (tm.tm_year + 1900) % 100);
186 break;
187 case 'e':
188 builder.appendf("%2d", tm.tm_mday);
189 break;
190 case 'h':
191 builder.append(mon_short_names[tm.tm_mon]);
192 break;
193 case 'H':
194 builder.appendf("%02d", tm.tm_hour);
195 break;
196 case 'I':
197 builder.appendf("%02d", tm.tm_hour % 12);
198 break;
199 case 'j':
200 builder.appendf("%03d", tm.tm_yday + 1);
201 break;
202 case 'm':
203 builder.appendf("%02d", tm.tm_mon + 1);
204 break;
205 case 'M':
206 builder.appendf("%02d", tm.tm_min);
207 break;
208 case 'n':
209 builder.append('\n');
210 break;
211 case 'p':
212 builder.append(tm.tm_hour < 12 ? "a.m." : "p.m.");
213 break;
214 case 'r':
215 builder.appendf("%02d:%02d:%02d %s", tm.tm_hour % 12, tm.tm_min, tm.tm_sec, tm.tm_hour < 12 ? "a.m." : "p.m.");
216 break;
217 case 'R':
218 builder.appendf("%02d:%02d", tm.tm_hour, tm.tm_min);
219 break;
220 case 'S':
221 builder.appendf("%02d", tm.tm_sec);
222 break;
223 case 't':
224 builder.append('\t');
225 break;
226 case 'T':
227 builder.appendf("%02d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
228 break;
229 case 'u':
230 builder.appendf("%d", tm.tm_wday ? tm.tm_wday : 7);
231 break;
232 case 'U': {
233 const int wday_of_year_beginning = (tm.tm_wday + 6 * tm.tm_yday) % 7;
234 const int week_number = (tm.tm_yday + wday_of_year_beginning) / 7;
235 builder.appendf("%02d", week_number);
236 break;
237 }
238 case 'V': {
239 const int wday_of_year_beginning = (tm.tm_wday + 6 + 6 * tm.tm_yday) % 7;
240 int week_number = (tm.tm_yday + wday_of_year_beginning) / 7 + 1;
241 if (wday_of_year_beginning > 3) {
242 if (tm.tm_yday >= 7 - wday_of_year_beginning)
243 --week_number;
244 else {
245 const bool last_year_is_leap = ((tm.tm_year + 1900 - 1) % 4 == 0 && (tm.tm_year + 1900 - 1) % 100 != 0) || (tm.tm_year + 1900 - 1) % 400 == 0;
246 const int days_of_last_year = 365 + last_year_is_leap;
247 const int wday_of_last_year_beginning = (wday_of_year_beginning + 6 * days_of_last_year) % 7;
248 week_number = (days_of_last_year + wday_of_last_year_beginning) / 7 + 1;
249 if (wday_of_last_year_beginning > 3)
250 --week_number;
251 }
252 }
253 builder.appendf("%02d", week_number);
254 break;
255 }
256 case 'w':
257 builder.appendf("%d", tm.tm_wday);
258 break;
259 case 'W': {
260 const int wday_of_year_beginning = (tm.tm_wday + 6 + 6 * tm.tm_yday) % 7;
261 const int week_number = (tm.tm_yday + wday_of_year_beginning) / 7;
262 builder.appendf("%02d", week_number);
263 break;
264 }
265 case 'y':
266 builder.appendf("%02d", (tm.tm_year + 1900) % 100);
267 break;
268 case 'Y':
269 builder.appendf("%d", tm.tm_year + 1900);
270 break;
271 case '%':
272 builder.append('%');
273 break;
274 default:
275 return String();
276 }
277 }
278 }
279
280 return builder.build();
281}
282
283const LogStream& operator<<(const LogStream& stream, const DateTime& value)
284{
285 return stream << value.to_string();
286}
287}