Line data Source code
1 : /*
2 : * Present - Date/Time Library
3 : *
4 : * Implementation of the Date methods
5 : *
6 : * Licensed under the MIT License.
7 : * For details, see LICENSE.
8 : */
9 :
10 : #include <assert.h>
11 : #include <stddef.h>
12 : #include <string.h>
13 :
14 : #include "present.h"
15 :
16 : #include "utils/constants.h"
17 : #include "utils/impl-utils.h"
18 : #include "utils/time-utils.h"
19 :
20 : /**
21 : * Get the week number of the last week of a given year (either 52 or 53).
22 : */
23 : static int_week_of_year
24 46 : last_week_of_year(int_year year)
25 : {
26 : int_week_of_year week;
27 : time_t time;
28 : struct tm tm;
29 : int_day_of_week day_of_week;
30 :
31 : /* https://en.wikipedia.org/wiki/ISO_week_date#Weeks_per_year */
32 46 : week = 52;
33 :
34 : /* Get some information on Jan. 1 of this year */
35 46 : time = unix_timestamp_to_time_t(to_unix_timestamp(year, 1, 1, 0, 0, 0));
36 46 : time_t_to_struct_tm(&time, &tm);
37 :
38 46 : day_of_week = tm.tm_wday;
39 46 : if (day_of_week == DAY_OF_WEEK_SUNDAY_COMPAT) {
40 1 : day_of_week = DAY_OF_WEEK_SUNDAY;
41 : }
42 :
43 : /* If the year starts on a Thursday, it has 53 weeks */
44 46 : if (day_of_week == DAY_OF_WEEK_THURSDAY) {
45 10 : week = 53;
46 : }
47 :
48 : /* If this year is a leap year, and it starts on a Wednesday,
49 : it has 53 weeks */
50 46 : if (IS_LEAP_YEAR(year) && day_of_week == DAY_OF_WEEK_WEDNESDAY) {
51 5 : week = 53;
52 : }
53 :
54 46 : return week;
55 : }
56 :
57 : /**
58 : * Make sure that year, month, and day are valid, and set day_of_year and
59 : * day_of_week to their correct values.
60 : */
61 : static void
62 3792 : check_date_data(struct PresentDateData * const data)
63 : {
64 : time_t time;
65 : struct tm tm;
66 :
67 3792 : time = unix_timestamp_to_time_t(to_unix_timestamp(
68 : data->year, data->month, data->day, 0, 0, 0));
69 3792 : time_t_to_struct_tm(&time, &tm);
70 :
71 3792 : data->year = (int_year)tm.tm_year + STRUCT_TM_YEAR_OFFSET;
72 3792 : data->month = (int_month)tm.tm_mon + STRUCT_TM_MONTH_OFFSET;
73 3792 : data->day = (int_day)tm.tm_mday;
74 3792 : data->day_of_year = (int_day_of_year)tm.tm_yday +
75 : STRUCT_TM_DAY_OF_YEAR_OFFSET;
76 3792 : data->day_of_week = (int_day_of_week)tm.tm_wday;
77 3792 : if (data->day_of_week == DAY_OF_WEEK_SUNDAY_COMPAT) {
78 540 : data->day_of_week = DAY_OF_WEEK_SUNDAY;
79 : }
80 3792 : }
81 :
82 : /**
83 : * Initialize a new Date instance based on its data parameters.
84 : */
85 : static void
86 3769 : init_date(
87 : struct Date * const result,
88 : int_year year,
89 : int_month month,
90 : int_day day)
91 : {
92 : static const int_day DAYS_PER_MONTH[13] = {
93 : 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
94 : };
95 :
96 : int_day days_in_month;
97 :
98 3769 : assert(result != NULL);
99 3769 : CLEAR(result);
100 :
101 3769 : if (month < 1 || month > 12) {
102 5 : result->has_error = 1;
103 5 : result->errors.month_out_of_range = 1;
104 : }
105 :
106 3769 : days_in_month = (IS_LEAP_YEAR(year) && month == 2) ? 29 :
107 : DAYS_PER_MONTH[month];
108 3769 : if (day < 1 || day > days_in_month) {
109 10 : result->has_error = 1;
110 10 : result->errors.day_out_of_range = 1;
111 : }
112 :
113 3769 : if (!result->has_error) {
114 3757 : result->data_.year = year;
115 3757 : result->data_.month = month;
116 3757 : result->data_.day = day;
117 3757 : check_date_data(&result->data_);
118 : }
119 3769 : }
120 :
121 : /**
122 : * Initialize a new Date instance based on its data parameters, without
123 : * checking any bounds.
124 : */
125 : static void
126 20 : init_date_no_bounds_check(
127 : struct Date * const result,
128 : int_year year,
129 : int_month month,
130 : int_day day)
131 : {
132 20 : assert(result != NULL);
133 20 : CLEAR(result);
134 :
135 20 : result->data_.year = year;
136 20 : result->data_.month = month;
137 20 : result->data_.day = day;
138 20 : check_date_data(&result->data_);
139 20 : }
140 :
141 :
142 : struct Date
143 1 : Date_from_year(int_year year)
144 : {
145 : struct Date result;
146 1 : init_date(&result, year, 1, 1);
147 1 : return result;
148 : }
149 :
150 : void
151 6 : Date_ptr_from_year(struct Date * const result, int_year year)
152 : {
153 6 : init_date(result, year, 1, 1);
154 6 : }
155 :
156 : struct Date
157 2 : Date_from_year_month(int_year year, int_month month)
158 : {
159 : struct Date result;
160 2 : init_date(&result, year, month, 1);
161 2 : return result;
162 : }
163 :
164 : void
165 7 : Date_ptr_from_year_month(
166 : struct Date * const result,
167 : int_year year,
168 : int_month month)
169 : {
170 7 : init_date(result, year, month, 1);
171 7 : }
172 :
173 : struct Date
174 2 : Date_from_year_month_day(int_year year, int_month month, int_day day)
175 : {
176 : struct Date result;
177 2 : init_date(&result, year, month, day);
178 2 : return result;
179 : }
180 :
181 : void
182 3751 : Date_ptr_from_year_month_day(
183 : struct Date * const result,
184 : int_year year,
185 : int_month month,
186 : int_day day)
187 : {
188 3751 : init_date(result, year, month, day);
189 3751 : }
190 :
191 : struct Date
192 1 : Date_from_year_day(int_year year, int_day_of_year day_of_year)
193 : {
194 : struct Date result;
195 1 : init_date_no_bounds_check(&result, year, 1, (int_day)(day_of_year));
196 1 : return result;
197 : }
198 :
199 : void
200 19 : Date_ptr_from_year_day(
201 : struct Date * const result,
202 : int_year year,
203 : int_day_of_year day_of_year)
204 : {
205 19 : init_date_no_bounds_check(result, year, 1, (int_day)(day_of_year));
206 19 : }
207 :
208 : struct Date
209 3 : Date_from_year_week_day(
210 : int_year year,
211 : int_week_of_year week_of_year,
212 : int_day_of_week day_of_week)
213 : {
214 : struct Date result;
215 3 : Date_ptr_from_year_week_day(&result, year, week_of_year, day_of_week);
216 3 : return result;
217 : }
218 :
219 : void
220 22 : Date_ptr_from_year_week_day(
221 : struct Date * const result,
222 : int_year year,
223 : int_week_of_year week_of_year,
224 : int_day_of_week day_of_week)
225 : {
226 : time_t time;
227 : struct tm tm;
228 : int_day_of_week jan_4_day_of_week;
229 : int_day_of_year ordinal_date;
230 :
231 22 : assert(result != NULL);
232 22 : CLEAR(result);
233 :
234 22 : if (week_of_year < 1 || week_of_year > last_week_of_year(year)) {
235 7 : result->has_error = 1;
236 7 : result->errors.week_of_year_out_of_range = 1;
237 : }
238 :
239 22 : if (day_of_week == DAY_OF_WEEK_SUNDAY_COMPAT) {
240 1 : day_of_week = DAY_OF_WEEK_SUNDAY;
241 : }
242 22 : if (day_of_week < 1 || day_of_week > 7) {
243 4 : result->has_error = 1;
244 4 : result->errors.day_of_week_out_of_range = 1;
245 : }
246 :
247 22 : if (!result->has_error) {
248 : /* Get the weekday of Jan. 4 of this year */
249 11 : time = unix_timestamp_to_time_t(to_unix_timestamp(
250 : year, 1, 4, 0, 0, 0));
251 11 : time_t_to_struct_tm(&time, &tm);
252 :
253 11 : jan_4_day_of_week = tm.tm_wday;
254 11 : if (jan_4_day_of_week == DAY_OF_WEEK_SUNDAY_COMPAT) {
255 3 : jan_4_day_of_week = DAY_OF_WEEK_SUNDAY;
256 : }
257 11 : assert(jan_4_day_of_week >= 1 && jan_4_day_of_week <= 7);
258 :
259 : /* Calculate the date using voodoo magic
260 : https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
261 : */
262 11 : ordinal_date = week_of_year * 7 + day_of_week - (jan_4_day_of_week + 3);
263 11 : if (ordinal_date < 1) {
264 1 : year -= 1;
265 1 : ordinal_date += DAYS_IN_YEAR(year);
266 : }
267 11 : if (ordinal_date > DAYS_IN_YEAR(year)) {
268 4 : ordinal_date -= DAYS_IN_YEAR(year);
269 4 : year += 1;
270 : }
271 :
272 11 : Date_ptr_from_year_day(result, year, ordinal_date);
273 : }
274 22 : }
275 :
276 : int_year
277 3788 : Date_year(const struct Date * const self)
278 : {
279 3788 : assert(self != NULL);
280 3788 : assert(self->has_error == 0);
281 :
282 3788 : return self->data_.year;
283 : }
284 :
285 : int_month
286 3788 : Date_month(const struct Date * const self)
287 : {
288 3788 : assert(self != NULL);
289 3788 : assert(self->has_error == 0);
290 :
291 3788 : return self->data_.month;
292 : }
293 :
294 : int_day
295 3788 : Date_day(const struct Date * const self)
296 : {
297 3788 : assert(self != NULL);
298 3788 : assert(self->has_error == 0);
299 :
300 3788 : return self->data_.day;
301 : }
302 :
303 : int_day_of_year
304 5 : Date_day_of_year(const struct Date * const self)
305 : {
306 5 : assert(self != NULL);
307 5 : assert(self->has_error == 0);
308 :
309 5 : return self->data_.day_of_year;
310 : }
311 :
312 : struct PresentWeekYear
313 21 : Date_week_of_year(const struct Date * const self)
314 : {
315 : int_year year;
316 : int_week_of_year week;
317 : struct PresentWeekYear p;
318 :
319 21 : assert(self != NULL);
320 21 : assert(self->has_error == 0);
321 :
322 : /* Caculate the week number using pure voodoo magic
323 : https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_of_a_given_date
324 : */
325 21 : year = self->data_.year;
326 21 : week = (self->data_.day_of_year - self->data_.day_of_week + 10) / 7;
327 :
328 21 : if (week == 0) {
329 : /* It's the last week of the previous year */
330 7 : year -= 1;
331 7 : week = last_week_of_year(year);
332 : }
333 21 : if (week > last_week_of_year(year)) {
334 : /* It's the first week of the next year */
335 2 : year += 1;
336 2 : week = 1;
337 : }
338 :
339 21 : p.week = week;
340 21 : p.year = year;
341 21 : return p;
342 : }
343 :
344 : int_day_of_week
345 13 : Date_day_of_week(const struct Date * const self)
346 : {
347 13 : assert(self != NULL);
348 13 : assert(self->has_error == 0);
349 :
350 13 : return self->data_.day_of_week;
351 : }
352 :
353 : struct DayDelta
354 4 : Date_difference(
355 : const struct Date * const self,
356 : const struct Date * const other)
357 : {
358 : struct ClockTime noon;
359 : struct Timestamp self_timestamp, other_timestamp;
360 : struct TimeDelta time_delta;
361 :
362 4 : assert(self != NULL);
363 4 : assert(self->has_error == 0);
364 4 : assert(other != NULL);
365 4 : assert(other->has_error == 0);
366 :
367 4 : noon = ClockTime_noon();
368 4 : self_timestamp = Timestamp_create_utc(self, &noon);
369 4 : other_timestamp = Timestamp_create_utc(other, &noon);
370 4 : time_delta = Timestamp_difference(&self_timestamp, &other_timestamp);
371 4 : return TimeDelta_to_DayDelta_truncated(&time_delta);
372 : }
373 :
374 : struct DayDelta
375 2 : Date_absolute_difference(
376 : const struct Date * const self,
377 : const struct Date * const other)
378 : {
379 : struct DayDelta delta;
380 :
381 2 : assert(self != NULL);
382 2 : assert(self->has_error == 0);
383 2 : assert(other != NULL);
384 2 : assert(other->has_error == 0);
385 :
386 2 : delta = Date_difference(self, other);
387 2 : if (DayDelta_is_negative(&delta)) {
388 1 : DayDelta_negate(&delta);
389 : }
390 2 : return delta;
391 : }
392 :
393 : void
394 4 : Date_add_DayDelta(
395 : struct Date * const self,
396 : const struct DayDelta * const delta)
397 : {
398 4 : assert(self != NULL);
399 4 : assert(self->has_error == 0);
400 4 : assert(delta != NULL);
401 :
402 4 : self->data_.day += delta->data_.delta_days;
403 4 : check_date_data(&self->data_);
404 4 : }
405 :
406 : void
407 5 : Date_add_MonthDelta(
408 : struct Date * const self,
409 : const struct MonthDelta * const delta)
410 : {
411 5 : assert(self != NULL);
412 5 : assert(self->has_error == 0);
413 5 : assert(delta != NULL);
414 :
415 5 : self->data_.month += delta->data_.delta_months;
416 5 : check_date_data(&self->data_);
417 5 : }
418 :
419 : void
420 3 : Date_subtract_DayDelta(
421 : struct Date * const self,
422 : const struct DayDelta * const delta)
423 : {
424 3 : assert(self != NULL);
425 3 : assert(self->has_error == 0);
426 3 : assert(delta != NULL);
427 :
428 3 : self->data_.day -= delta->data_.delta_days;
429 3 : check_date_data(&self->data_);
430 3 : }
431 :
432 : void
433 3 : Date_subtract_MonthDelta(
434 : struct Date * const self,
435 : const struct MonthDelta * const delta)
436 : {
437 3 : assert(self != NULL);
438 3 : assert(self->has_error == 0);
439 3 : assert(delta != NULL);
440 :
441 3 : self->data_.month -= delta->data_.delta_months;
442 3 : check_date_data(&self->data_);
443 3 : }
444 :
445 : short
446 1276 : Date_compare(
447 : const struct Date * const lhs,
448 : const struct Date * const rhs)
449 : {
450 1276 : assert(lhs != NULL);
451 1276 : assert(lhs->has_error == 0);
452 1276 : assert(rhs != NULL);
453 1276 : assert(rhs->has_error == 0);
454 :
455 7581 : return
456 7581 : STRUCT_COMPARE(year,
457 : STRUCT_COMPARE(month,
458 : STRUCT_COMPARE(day, 0)));
459 : }
460 :
461 1272 : STRUCT_COMPARISON_OPERATORS(Date)
462 :
|