1 /*
2 * Date and time functions
3 *
4 * Copyright (C) 2011-2021, Joachim Metz <joachim.metz@gmail.com>
5 *
6 * Refer to AUTHORS for acknowledgements.
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22 #include <common.h>
23 #include <byte_stream.h>
24 #include <types.h>
25
26 #include "pybde_datetime.h"
27 #include "pybde_python.h"
28
29 #include <datetime.h>
30
31 /* Creates a new datetime object from date and time elements
32 * Returns a Python object if successful or NULL on error
33 */
pybde_datetime_new_from_time_elements(uint16_t year,uint64_t number_of_days,uint8_t hours,uint8_t minutes,uint8_t seconds,uint32_t micro_seconds)34 PyObject *pybde_datetime_new_from_time_elements(
35 uint16_t year,
36 uint64_t number_of_days,
37 uint8_t hours,
38 uint8_t minutes,
39 uint8_t seconds,
40 uint32_t micro_seconds )
41 {
42 PyObject *datetime_object = NULL;
43 static char *function = "pybde_datetime_new_from_time_elements";
44 uint32_t days_in_century = 0;
45 uint16_t days_in_year = 0;
46 uint8_t day_of_month = 0;
47 uint8_t days_in_month = 0;
48 uint8_t month = 0;
49
50 while( number_of_days > 0 )
51 {
52 if( ( year % 400 ) == 0 )
53 {
54 days_in_century = 36525;
55 }
56 else
57 {
58 days_in_century = 36524;
59 }
60 if( number_of_days <= days_in_century )
61 {
62 break;
63 }
64 number_of_days -= days_in_century;
65
66 year += 100;
67 }
68 while( number_of_days > 0 )
69 {
70 /* Check for a leap year
71 * The year is ( ( dividable by 4 ) and ( not dividable by 100 ) ) or ( dividable by 400 )
72 */
73 if( ( ( ( year % 4 ) == 0 )
74 && ( ( year % 100 ) != 0 ) )
75 || ( ( year % 400 ) == 0 ) )
76 {
77 days_in_year = 366;
78 }
79 else
80 {
81 days_in_year = 365;
82 }
83 if( number_of_days <= days_in_year )
84 {
85 break;
86 }
87 number_of_days -= days_in_year;
88
89 year += 1;
90 }
91 /* Determine the month correct the value to days within the month
92 */
93 month = 1;
94
95 while( number_of_days > 0 )
96 {
97 /* February (2)
98 */
99 if( month == 2 )
100 {
101 if( ( ( ( year % 4 ) == 0 )
102 && ( ( year % 100 ) != 0 ) )
103 || ( ( year % 400 ) == 0 ) )
104 {
105 days_in_month = 29;
106 }
107 else
108 {
109 days_in_month = 28;
110 }
111 }
112 /* April (4), June (6), September (9), November (11)
113 */
114 else if( ( month == 4 )
115 || ( month == 6 )
116 || ( month == 9 )
117 || ( month == 11 ) )
118 {
119 days_in_month = 30;
120 }
121 /* January (1), March (3), May (5), July (7), August (8), October (10), December (12)
122 */
123 else if( ( month == 1 )
124 || ( month == 3 )
125 || ( month == 5 )
126 || ( month == 7 )
127 || ( month == 8 )
128 || ( month == 10 )
129 || ( month == 12 ) )
130 {
131 days_in_month = 31;
132 }
133 /* This should never happen, but just in case
134 */
135 else
136 {
137 PyErr_Format(
138 PyExc_IOError,
139 "%s: unsupported month: %" PRIu8 ".",
140 function,
141 month );
142
143 return( NULL );
144 }
145 if( number_of_days <= days_in_month )
146 {
147 break;
148 }
149 number_of_days -= days_in_month;
150
151 month += 1;
152 }
153 /* Determine the day
154 */
155 day_of_month = (uint8_t) number_of_days;
156
157 PyDateTime_IMPORT;
158
159 datetime_object = (PyObject *) PyDateTime_FromDateAndTime(
160 (int) year,
161 (int) month,
162 (int) day_of_month,
163 (int) hours,
164 (int) minutes,
165 (int) seconds,
166 (int) micro_seconds );
167
168 return( datetime_object );
169 }
170
171 /* Creates a new datetime object from a FAT date time
172 * Returns a Python object if successful or NULL on error
173 */
pybde_datetime_new_from_fat_date_time(uint32_t fat_date_time)174 PyObject *pybde_datetime_new_from_fat_date_time(
175 uint32_t fat_date_time )
176 {
177 PyObject *datetime_object = NULL;
178 static char *function = "pybde_datetime_new_from_fat_date_time";
179 uint16_t year = 0;
180 uint8_t day_of_month = 0;
181 uint8_t days_in_month = 0;
182 uint8_t hours = 0;
183 uint8_t minutes = 0;
184 uint8_t month = 0;
185 uint8_t seconds = 0;
186
187 /* The day of month is stored in the next 5 bits
188 */
189 day_of_month = fat_date_time & 0x1f;
190 fat_date_time >>= 5;
191
192 /* The month is stored in the next 4 bits
193 */
194 month = fat_date_time & 0x0f;
195 fat_date_time >>= 4;
196
197 /* The year is stored in the next 7 bits starting at 1980
198 */
199 year = 1980 + ( fat_date_time & 0x7f );
200 fat_date_time >>= 7;
201
202 /* The number of seconds are stored in the lower 5 bits
203 * in intervals of 2 seconds
204 */
205 seconds = ( fat_date_time & 0x1f ) * 2;
206 fat_date_time >>= 5;
207
208 /* The number of minutes are stored in the next 6 bits
209 */
210 minutes = fat_date_time & 0x3f;
211 fat_date_time >>= 6;
212
213 /* The number of hours are stored in the next 5 bits
214 */
215 hours = fat_date_time & 0x1f;
216
217 /* February (2)
218 */
219 if( month == 2 )
220 {
221 if( ( ( ( year % 4 ) == 0 )
222 && ( ( year % 100 ) != 0 ) )
223 || ( ( year % 400 ) == 0 ) )
224 {
225 days_in_month = 29;
226 }
227 else
228 {
229 days_in_month = 28;
230 }
231 }
232 /* April (4), June (6), September (9), November (11)
233 */
234 else if( ( month == 4 )
235 || ( month == 6 )
236 || ( month == 9 )
237 || ( month == 11 ) )
238 {
239 days_in_month = 30;
240 }
241 /* January (1), March (3), May (5), July (7), August (8), October (10), December (12)
242 */
243 else if( ( month == 1 )
244 || ( month == 3 )
245 || ( month == 5 )
246 || ( month == 7 )
247 || ( month == 8 )
248 || ( month == 10 )
249 || ( month == 12 ) )
250 {
251 days_in_month = 31;
252 }
253 else
254 {
255 PyErr_Format(
256 PyExc_IOError,
257 "%s: unsupported month: %" PRIu8 ".",
258 function,
259 month );
260
261 return( NULL );
262 }
263 if( ( day_of_month == 0 )
264 || ( day_of_month > days_in_month ) )
265 {
266 PyErr_Format(
267 PyExc_IOError,
268 "%s: unsupported day of month: %" PRIu8 ".",
269 function,
270 day_of_month );
271
272 return( NULL );
273 }
274 PyDateTime_IMPORT;
275
276 datetime_object = (PyObject *) PyDateTime_FromDateAndTime(
277 (int) year,
278 (int) month,
279 (int) day_of_month,
280 (int) hours,
281 (int) minutes,
282 (int) seconds,
283 0 );
284
285 return( datetime_object );
286 }
287
288 /* Creates a new datetime object from a FILETIME
289 * Returns a Python object if successful or NULL on error
290 */
pybde_datetime_new_from_filetime(uint64_t filetime)291 PyObject *pybde_datetime_new_from_filetime(
292 uint64_t filetime )
293 {
294 PyObject *datetime_object = NULL;
295 uint32_t micro_seconds = 0;
296 uint16_t year = 0;
297 uint8_t hours = 0;
298 uint8_t minutes = 0;
299 uint8_t seconds = 0;
300
301 /* The timestamp is in units of 100 nano seconds correct the value to seconds
302 */
303 micro_seconds = (uint32_t) ( filetime % 10000000 ) / 10;
304 filetime /= 10000000;
305
306 /* There are 60 seconds in a minute correct the value to minutes
307 */
308 seconds = (uint8_t) ( filetime % 60 );
309 filetime /= 60;
310
311 /* There are 60 minutes in an hour correct the value to hours
312 */
313 minutes = (uint8_t) ( filetime % 60 );
314 filetime /= 60;
315
316 /* There are 24 hours in a day correct the value to days
317 */
318 hours = (uint8_t) ( filetime % 24 );
319 filetime /= 24;
320
321 /* Add 1 day to compensate that Jan 1 1601 is represented as 0
322 */
323 filetime += 1;
324
325 /* Determine the number of years starting at '1 Jan 1601 00:00:00'
326 * correct the value to days within the year
327 */
328 year = 1601;
329
330 if( filetime >= 36159 )
331 {
332 year = 1700;
333
334 filetime -= 36159;
335 }
336 datetime_object = pybde_datetime_new_from_time_elements(
337 year,
338 filetime,
339 hours,
340 minutes,
341 seconds,
342 micro_seconds );
343
344 return( datetime_object );
345 }
346
347 /* Creates a new datetime object from a floatingtime
348 * Returns a Python object if successful or NULL on error
349 */
pybde_datetime_new_from_floatingtime(uint64_t floatingtime)350 PyObject *pybde_datetime_new_from_floatingtime(
351 uint64_t floatingtime )
352 {
353 byte_stream_float64_t timestamp;
354
355 PyObject *datetime_object = NULL;
356 static char *function = "pybde_datetime_new_from_floatingtime";
357 uint32_t days_in_century = 0;
358 uint32_t micro_seconds = 0;
359 uint16_t days_in_year = 0;
360 uint16_t year = 0;
361 uint8_t day_of_month = 0;
362 uint8_t days_in_month = 0;
363 uint8_t hours = 0;
364 uint8_t minutes = 0;
365 uint8_t month = 0;
366 uint8_t seconds = 0;
367
368 timestamp.integer = floatingtime;
369
370 /* Determine the number of years starting at '30 Dec 1899 00:00:00'
371 * correct the value to days within the year
372 */
373 year = 1899;
374
375 if( timestamp.floating_point >= 2 )
376 {
377 year = 1900;
378
379 timestamp.floating_point -= 2;
380 }
381 while( timestamp.floating_point > 0 )
382 {
383 if( ( year % 400 ) == 0 )
384 {
385 days_in_century = 36525;
386 }
387 else
388 {
389 days_in_century = 36524;
390 }
391 if( timestamp.floating_point <= days_in_century )
392 {
393 break;
394 }
395 timestamp.floating_point -= days_in_century;
396
397 year += 100;
398 }
399 while( timestamp.floating_point > 0 )
400 {
401 /* Check for a leap year
402 * The year is ( ( dividable by 4 ) and ( not dividable by 100 ) ) or ( dividable by 400 )
403 */
404 if( ( ( ( year % 4 ) == 0 )
405 && ( ( year % 100 ) != 0 ) )
406 || ( ( year % 400 ) == 0 ) )
407 {
408 days_in_year = 366;
409 }
410 else
411 {
412 days_in_year = 365;
413 }
414 if( timestamp.floating_point <= days_in_year )
415 {
416 break;
417 }
418 timestamp.floating_point -= days_in_year;
419
420 year += 1;
421 }
422 /* Determine the month correct the value to days within the month
423 */
424 month = 1;
425
426 while( timestamp.floating_point > 0 )
427 {
428 /* February (2)
429 */
430 if( month == 2 )
431 {
432 if( ( ( ( year % 4 ) == 0 )
433 && ( ( year % 100 ) != 0 ) )
434 || ( ( year % 400 ) == 0 ) )
435 {
436 days_in_month = 29;
437 }
438 else
439 {
440 days_in_month = 28;
441 }
442 }
443 /* April (4), June (6), September (9), November (11)
444 */
445 else if( ( month == 4 )
446 || ( month == 6 )
447 || ( month == 9 )
448 || ( month == 11 ) )
449 {
450 days_in_month = 30;
451 }
452 /* January (1), March (3), May (5), July (7), August (8), October (10), December (12)
453 */
454 else if( ( month == 1 )
455 || ( month == 3 )
456 || ( month == 5 )
457 || ( month == 7 )
458 || ( month == 8 )
459 || ( month == 10 )
460 || ( month == 12 ) )
461 {
462 days_in_month = 31;
463 }
464 /* This should never happen, but just in case
465 */
466 else
467 {
468 PyErr_Format(
469 PyExc_IOError,
470 "%s: unsupported month: %" PRIu8 ".",
471 function,
472 month );
473
474 return( NULL );
475 }
476 if( timestamp.floating_point <= days_in_month )
477 {
478 break;
479 }
480 timestamp.floating_point -= days_in_month;
481
482 month += 1;
483 }
484 /* Determine the day
485 */
486 day_of_month = (uint8_t) timestamp.floating_point;
487 timestamp.floating_point -= day_of_month;
488
489 /* There are 24 hours in a day correct the value to hours
490 */
491 timestamp.floating_point *= 24;
492 hours = (uint8_t) timestamp.floating_point;
493 timestamp.floating_point -= hours;
494
495 /* There are 60 minutes in an hour correct the value to minutes
496 */
497 timestamp.floating_point *= 60;
498 minutes = (uint8_t) timestamp.floating_point;
499 timestamp.floating_point -= minutes;
500
501 /* There are 60 seconds in a minute correct the value to seconds
502 */
503 timestamp.floating_point *= 60;
504 seconds = (uint8_t) timestamp.floating_point;
505 timestamp.floating_point -= seconds;
506
507 /* There are 1000 micro seconds in a seconds correct the value to micro seconds
508 */
509 timestamp.floating_point *= 1000000;
510 micro_seconds = (uint8_t) timestamp.floating_point;
511 timestamp.floating_point -= micro_seconds;
512
513 PyDateTime_IMPORT;
514
515 datetime_object = (PyObject *) PyDateTime_FromDateAndTime(
516 (int) year,
517 (int) month,
518 (int) day_of_month,
519 (int) hours,
520 (int) minutes,
521 (int) seconds,
522 (int) micro_seconds );
523
524 return( datetime_object );
525 }
526
527 /* Creates a new datetime object from a HFS time
528 * Returns a Python object if successful or NULL on error
529 */
pybde_datetime_new_from_hfs_time(uint32_t hfs_time)530 PyObject *pybde_datetime_new_from_hfs_time(
531 uint32_t hfs_time )
532 {
533 PyObject *datetime_object = NULL;
534 uint16_t year = 0;
535 uint8_t hours = 0;
536 uint8_t minutes = 0;
537 uint8_t seconds = 0;
538
539 /* There are 60 seconds in a minute correct the value to minutes
540 */
541 seconds = (uint8_t) ( hfs_time % 60 );
542 hfs_time /= 60;
543
544 /* There are 60 minutes in an hour correct the value to hours
545 */
546 minutes = (uint8_t) ( hfs_time % 60 );
547 hfs_time /= 60;
548
549 /* There are 24 hours in a day correct the value to days
550 */
551 hours = (uint8_t) ( hfs_time % 24 );
552 hfs_time /= 24;
553
554 /* Add 1 day to compensate that Jan 1 1904 is represented as 0
555 */
556 hfs_time += 1;
557
558 /* Determine the number of years starting at '1 Jan 1904 00:00:00'
559 * correct the value to days within the year
560 */
561 year = 1904;
562
563 if( hfs_time >= 35064 )
564 {
565 year = 2000;
566
567 hfs_time -= 35064;
568 }
569 datetime_object = pybde_datetime_new_from_time_elements(
570 year,
571 (uint64_t) hfs_time,
572 hours,
573 minutes,
574 seconds,
575 0 );
576
577 return( datetime_object );
578 }
579
580 /* Creates a new datetime object from a POSIX time
581 * Returns a Python object if successful or NULL on error
582 */
pybde_datetime_new_from_posix_time(int64_t posix_time)583 PyObject *pybde_datetime_new_from_posix_time(
584 int64_t posix_time )
585 {
586 PyObject *datetime_object = NULL;
587 uint16_t year = 0;
588 uint8_t hours = 0;
589 uint8_t minutes = 0;
590 uint8_t seconds = 0;
591
592 /* There are 60 seconds in a minute correct the value to minutes
593 */
594 seconds = posix_time % 60;
595 posix_time /= 60;
596
597 /* There are 60 minutes in an hour correct the value to hours
598 */
599 minutes = posix_time % 60;
600 posix_time /= 60;
601
602 /* There are 24 hours in a day correct the value to days
603 */
604 hours = posix_time % 24;
605 posix_time /= 24;
606
607 /* Add 1 day to compensate that Jan 1 1601 is represented as 0
608 */
609 posix_time += 1;
610
611 /* Determine the number of years starting at '1 Jan 1970 00:00:00'
612 * correct the value to days within the year
613 */
614 year = 1970;
615
616 if( posix_time >= 10957 )
617 {
618 year = 2000;
619
620 posix_time -= 10957;
621 }
622 datetime_object = pybde_datetime_new_from_time_elements(
623 year,
624 (uint64_t) posix_time,
625 hours,
626 minutes,
627 seconds,
628 0 );
629
630 return( datetime_object );
631 }
632
633 /* Creates a new datetime object from a POSIX time in micro seconds
634 * Returns a Python object if successful or NULL on error
635 */
pybde_datetime_new_from_posix_time_in_micro_seconds(int64_t posix_time)636 PyObject *pybde_datetime_new_from_posix_time_in_micro_seconds(
637 int64_t posix_time )
638 {
639 PyObject *datetime_object = NULL;
640 uint32_t micro_seconds = 0;
641 uint16_t year = 0;
642 uint8_t hours = 0;
643 uint8_t minutes = 0;
644 uint8_t seconds = 0;
645
646 /* There are 1000000 micro seconds in a second correct the value to seconds
647 */
648 micro_seconds = (uint32_t) ( posix_time % 1000000 );
649 posix_time /= 1000000;
650
651 /* There are 60 seconds in a minute correct the value to minutes
652 */
653 seconds = posix_time % 60;
654 posix_time /= 60;
655
656 /* There are 60 minutes in an hour correct the value to hours
657 */
658 minutes = posix_time % 60;
659 posix_time /= 60;
660
661 /* There are 24 hours in a day correct the value to days
662 */
663 hours = posix_time % 24;
664 posix_time /= 24;
665
666 /* Add 1 day to compensate that Jan 1 1970 is represented as 0
667 */
668 posix_time += 1;
669
670 /* Determine the number of years starting at '1 Jan 1970 00:00:00'
671 * correct the value to days within the year
672 */
673 year = 1970;
674
675 if( posix_time >= 10957 )
676 {
677 year = 2000;
678
679 posix_time -= 10957;
680 }
681 datetime_object = pybde_datetime_new_from_time_elements(
682 year,
683 (uint64_t) posix_time,
684 hours,
685 minutes,
686 seconds,
687 micro_seconds );
688
689 return( datetime_object );
690 }
691
692