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 | |
12 | using namespace duckdb; |
13 | using namespace std; |
14 | |
15 | // Taken from MonetDB mtime.c |
16 | |
17 | static int NORMALDAYS[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
18 | static int LEAPDAYS[13] = {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; |
19 | static int CUMDAYS[13] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; |
20 | static 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 | |
37 | static 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 | |
50 | static 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 | |
84 | static 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 | |
103 | static 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 | |
114 | static 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 | |
179 | date_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 | |
189 | date_t Date::FromString(string str, bool strict) { |
190 | return Date::FromCString(str.c_str(), strict); |
191 | } |
192 | |
193 | string 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 | |
203 | string Date::Format(int32_t year, int32_t month, int32_t day) { |
204 | return ToString(Date::FromDate(year, month, day)); |
205 | } |
206 | |
207 | void 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 | |
211 | int32_t Date::FromDate(int32_t year, int32_t month, int32_t day) { |
212 | return date_to_number(year, month, day); |
213 | } |
214 | |
215 | bool Date::IsLeapYear(int32_t year) { |
216 | return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); |
217 | } |
218 | |
219 | bool 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 | |
230 | date_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 | |
235 | int64_t Date::Epoch(date_t date) { |
236 | return ((int64_t)date - EPOCH_DATE) * SECONDS_PER_DAY; |
237 | } |
238 | int32_t Date::(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 | |
244 | int32_t Date::(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 | |
250 | int32_t Date::(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 | |
256 | int32_t Date::(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 | |
265 | int32_t Date::(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 | |
283 | static 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 | |
301 | int32_t Date::(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. |
308 | date_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 | |