1#include "duckdb/common/types/date.hpp"
2
3#include "duckdb/common/exception.hpp"
4#include "duckdb/common/string_util.hpp"
5
6#include <cstring>
7#include <cctype>
8#include <iomanip>
9#include <iostream>
10#include <sstream>
11
12using namespace duckdb;
13using namespace std;
14
15// Taken from MonetDB mtime.c
16
17static int NORMALDAYS[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
18static int LEAPDAYS[13] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
19static int CUMDAYS[13] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
20static int CUMLEAPDAYS[13] = {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
21
22#define YEAR_MAX 5867411
23#define YEAR_MIN (-YEAR_MAX)
24#define MONTHDAYS(m, y) ((m) != 2 ? LEAPDAYS[m] : leapyear(y) ? 29 : 28)
25#define YEARDAYS(y) (leapyear(y) ? 366 : 365)
26#define DD_DATE(d, m, y) \
27 ((m) > 0 && (m) <= 12 && (d) > 0 && (y) != 0 && (y) >= YEAR_MIN && (y) <= YEAR_MAX && (d) <= MONTHDAYS(m, y))
28#define LOWER(c) ((c) >= 'A' && (c) <= 'Z' ? (c) + 'a' - 'A' : (c))
29// 1970-01-01 in date_t format
30#define EPOCH_DATE 719528
31// 1970-01-01 was a Thursday
32#define EPOCH_DAY_OF_THE_WEEK 4
33#define SECONDS_PER_DAY (60 * 60 * 24)
34
35#define leapyear(y) ((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0))
36
37static inline int leapyears(int year) {
38 /* count the 4-fold years that passed since jan-1-0 */
39 int y4 = year / 4;
40
41 /* count the 100-fold years */
42 int y100 = year / 100;
43
44 /* count the 400-fold years */
45 int y400 = year / 400;
46
47 return y4 + y400 - y100 + (year >= 0); /* may be negative */
48}
49
50static inline void number_to_date(int32_t n, int32_t &year, int32_t &month, int32_t &day) {
51 year = n / 365;
52 day = (n - year * 365) - leapyears(year >= 0 ? year - 1 : year);
53 if (n < 0) {
54 year--;
55 while (day >= 0) {
56 year++;
57 day -= YEARDAYS(year);
58 }
59 day = YEARDAYS(year) + day;
60 } else {
61 while (day < 0) {
62 year--;
63 day += YEARDAYS(year);
64 }
65 }
66
67 day++;
68 if (leapyear(year)) {
69 for (month = day / 31 == 0 ? 1 : day / 31; month <= 12; month++)
70 if (day > CUMLEAPDAYS[month - 1] && day <= CUMLEAPDAYS[month]) {
71 break;
72 }
73 day -= CUMLEAPDAYS[month - 1];
74 } else {
75 for (month = day / 31 == 0 ? 1 : day / 31; month <= 12; month++)
76 if (day > CUMDAYS[month - 1] && day <= CUMDAYS[month]) {
77 break;
78 }
79 day -= CUMDAYS[month - 1];
80 }
81 year = (year <= 0) ? year - 1 : year;
82}
83
84static inline int32_t date_to_number(int32_t year, int32_t month, int32_t day) {
85 int32_t n = 0;
86 if (!(DD_DATE(day, month, year))) {
87 throw ConversionException("Date out of range: %d-%d-%d", year, month, day);
88 }
89
90 if (year < 0)
91 year++;
92 n = (int32_t)(day - 1);
93 if (month > 2 && leapyear(year)) {
94 n++;
95 }
96 n += CUMDAYS[month - 1];
97 /* current year does not count as leapyear */
98 n += 365 * year + leapyears(year >= 0 ? year - 1 : year);
99
100 return n;
101}
102
103static bool ParseDoubleDigit(const char *buf, idx_t &pos, int32_t &result) {
104 if (std::isdigit((unsigned char)buf[pos])) {
105 result = buf[pos++] - '0';
106 if (std::isdigit((unsigned char)buf[pos])) {
107 result = (buf[pos++] - '0') + result * 10;
108 }
109 return true;
110 }
111 return false;
112}
113
114static bool TryConvertDate(const char *buf, date_t &result, bool strict = false) {
115 int32_t day = 0, month = -1;
116 int32_t year = 0, yearneg = (buf[0] == '-');
117 idx_t pos = 0;
118 int sep;
119
120 // skip leading spaces
121 while (std::isspace((unsigned char)buf[pos])) {
122 pos++;
123 }
124
125 if (yearneg == 0 && !std::isdigit((unsigned char)buf[pos])) {
126 return false;
127 }
128
129 // first parse the year
130 for (pos = pos + yearneg; std::isdigit((unsigned char)buf[pos]); pos++) {
131 year = (buf[pos] - '0') + year * 10;
132 if (year > YEAR_MAX) {
133 break;
134 }
135 }
136
137 // fetch the separator
138 sep = buf[pos++];
139 if (sep != ' ' && sep != '-' && sep != '/' && sep != '\\') {
140 // invalid separator
141 return false;
142 }
143
144 // parse the month
145 if (!ParseDoubleDigit(buf, pos, month)) {
146 return false;
147 }
148
149 if (buf[pos++] != sep) {
150 return false;
151 }
152
153 // now parse the day
154 if (!ParseDoubleDigit(buf, pos, day)) {
155 return false;
156 }
157
158 // in strict mode, check remaining string for non-space characters
159 if (strict) {
160 // skip trailing spaces
161 while (std::isspace((unsigned char)buf[pos])) {
162 pos++;
163 }
164 // check position. if end was not reached, non-space chars remaining
165 if (pos < strlen(buf)) {
166 return false;
167 }
168 } else {
169 // in non-strict mode, check for any direct trailing digits
170 if (std::isdigit((unsigned char)buf[pos])) {
171 return false;
172 }
173 }
174
175 result = Date::FromDate(yearneg ? -year : year, month, day);
176 return true;
177}
178
179date_t Date::FromCString(const char *buf, bool strict) {
180 date_t result;
181 if (!TryConvertDate(buf, result, strict)) {
182 throw ConversionException("date/time field value out of range: \"%s\", "
183 "expected format is (YYYY-MM-DD)",
184 buf);
185 }
186 return result;
187}
188
189date_t Date::FromString(string str, bool strict) {
190 return Date::FromCString(str.c_str(), strict);
191}
192
193string Date::ToString(int32_t date) {
194 int32_t year, month, day;
195 number_to_date(date, year, month, day);
196 if (year < 0) {
197 return StringUtil::Format("%04d-%02d-%02d (BC)", -year, month, day);
198 } else {
199 return StringUtil::Format("%04d-%02d-%02d", year, month, day);
200 }
201}
202
203string Date::Format(int32_t year, int32_t month, int32_t day) {
204 return ToString(Date::FromDate(year, month, day));
205}
206
207void Date::Convert(int32_t date, int32_t &out_year, int32_t &out_month, int32_t &out_day) {
208 number_to_date(date, out_year, out_month, out_day);
209}
210
211int32_t Date::FromDate(int32_t year, int32_t month, int32_t day) {
212 return date_to_number(year, month, day);
213}
214
215bool Date::IsLeapYear(int32_t year) {
216 return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
217}
218
219bool Date::IsValidDay(int32_t year, int32_t month, int32_t day) {
220 if (month < 1 || month > 12)
221 return false;
222 if (day < 1)
223 return false;
224 if (year < YEAR_MIN || year > YEAR_MAX)
225 return false;
226
227 return IsLeapYear(year) ? day <= LEAPDAYS[month] : day <= NORMALDAYS[month];
228}
229
230date_t Date::EpochToDate(int64_t epoch) {
231 assert((epoch / SECONDS_PER_DAY) + EPOCH_DATE <= numeric_limits<int32_t>::max());
232 return (date_t)((epoch / SECONDS_PER_DAY) + EPOCH_DATE);
233}
234
235int64_t Date::Epoch(date_t date) {
236 return ((int64_t)date - EPOCH_DATE) * SECONDS_PER_DAY;
237}
238int32_t Date::ExtractYear(date_t date) {
239 int32_t out_year, out_month, out_day;
240 Date::Convert(date, out_year, out_month, out_day);
241 return out_year;
242}
243
244int32_t Date::ExtractMonth(date_t date) {
245 int32_t out_year, out_month, out_day;
246 Date::Convert(date, out_year, out_month, out_day);
247 return out_month;
248}
249
250int32_t Date::ExtractDay(date_t date) {
251 int32_t out_year, out_month, out_day;
252 Date::Convert(date, out_year, out_month, out_day);
253 return out_day;
254}
255
256int32_t Date::ExtractDayOfTheYear(date_t date) {
257 int32_t out_year, out_month, out_day;
258 Date::Convert(date, out_year, out_month, out_day);
259 if (out_month >= 1) {
260 out_day += Date::IsLeapYear(out_year) ? CUMLEAPDAYS[out_month - 1] : CUMDAYS[out_month - 1];
261 }
262 return out_day;
263}
264
265int32_t Date::ExtractISODayOfTheWeek(date_t date) {
266 // -1 = 5
267 // 0 = 6
268 // 1 = 7
269 // 2 = 1
270 // 3 = 2
271 // 4 = 3
272 // 5 = 4
273 // 6 = 5
274 // 0 = 6
275 // 1 = 7
276 if (date < 2) {
277 return ((date - 1) % 7) + 7;
278 } else {
279 return ((date - 2) % 7) + 1;
280 }
281}
282
283static int32_t GetWeek(int32_t year, int32_t month, int32_t day) {
284 auto day_of_the_year = (leapyear(year) ? CUMLEAPDAYS[month] : CUMDAYS[month]) + day;
285 // get the first day of the first week of the year
286 // the first week is the week that has the 4th of January in it
287 auto day_of_the_fourth = Date::ExtractISODayOfTheWeek(Date::FromDate(year, 1, 4));
288 // if fourth is monday, then fourth is the first day
289 // if fourth is tuesday, third is the first day
290 // if fourth is wednesday, second is the first day
291 // if fourth is thursday - sunday, first is the first day
292 auto first_day_of_the_first_week = day_of_the_fourth >= 4 ? 0 : 5 - day_of_the_fourth;
293 if (day_of_the_year < first_day_of_the_first_week) {
294 // day is part of last year
295 return GetWeek(year - 1, 12, day);
296 } else {
297 return ((day_of_the_year - first_day_of_the_first_week) / 7) + 1;
298 }
299}
300
301int32_t Date::ExtractWeekNumber(date_t date) {
302 int32_t year, month, day;
303 Date::Convert(date, year, month, day);
304 return GetWeek(year, month - 1, day - 1);
305}
306
307// Returns the date of the monday of the current week.
308date_t Date::GetMondayOfCurrentWeek(date_t date) {
309 int32_t dotw = Date::ExtractISODayOfTheWeek(date);
310
311 int32_t days = date_to_number(Date::ExtractYear(date), Date::ExtractMonth(date), Date::ExtractDay(date));
312
313 days -= dotw - 1;
314
315 int32_t year, month, day;
316 number_to_date(days, year, month, day);
317
318 return (Date::FromDate(year, month, day));
319}
320