1 /*****************************************************************************/
2 // Copyright 2006-2019 Adobe Systems Incorporated
3 // All Rights Reserved.
4 //
5 // NOTICE: Adobe permits you to use, modify, and distribute this file in
6 // accordance with the terms of the Adobe license agreement accompanying it.
7 /*****************************************************************************/
8
9 #include "dng_date_time.h"
10
11 #include "dng_exceptions.h"
12 #include "dng_globals.h"
13 #include "dng_mutex.h"
14 #include "dng_stream.h"
15 #include "dng_string.h"
16 #include "dng_utils.h"
17
18 #include <time.h>
19
20 #if qMacOS
21 #include <CoreServices/CoreServices.h>
22 #endif
23
24 #if qWinOS
25 #include <windows.h>
26 #endif
27
28 /******************************************************************************/
29
dng_date_time()30 dng_date_time::dng_date_time ()
31
32 : fYear (0)
33 , fMonth (0)
34 , fDay (0)
35 , fHour (0)
36 , fMinute (0)
37 , fSecond (0)
38
39 {
40
41 }
42
43 /******************************************************************************/
44
dng_date_time(uint32 year,uint32 month,uint32 day,uint32 hour,uint32 minute,uint32 second)45 dng_date_time::dng_date_time (uint32 year,
46 uint32 month,
47 uint32 day,
48 uint32 hour,
49 uint32 minute,
50 uint32 second)
51
52 : fYear (year)
53 , fMonth (month)
54 , fDay (day)
55 , fHour (hour)
56 , fMinute (minute)
57 , fSecond (second)
58
59 {
60
61 }
62
63 /******************************************************************************/
64
IsValid() const65 bool dng_date_time::IsValid () const
66 {
67
68 return fYear >= 1 && fYear <= 9999 &&
69 fMonth >= 1 && fMonth <= 12 &&
70 fDay >= 1 && fDay <= 31 &&
71 fHour <= 23 &&
72 fMinute <= 59 &&
73 fSecond <= 59;
74
75 }
76
77 /*****************************************************************************/
78
Clear()79 void dng_date_time::Clear ()
80 {
81
82 *this = dng_date_time ();
83
84 }
85
86 /*****************************************************************************/
87
DateTimeParseU32(const char * & s)88 static uint32 DateTimeParseU32 (const char *&s)
89 {
90
91 uint32 x = 0;
92
93 while (*s == ' ' || *s == ':')
94 s++;
95
96 while (*s >= '0' && *s <= '9')
97 {
98 x = x * 10 + (uint32) (*(s++) - '0');
99 }
100
101 return x;
102
103 }
104
105 /*****************************************************************************/
106
Parse(const char * s)107 bool dng_date_time::Parse (const char *s)
108 {
109
110 fYear = DateTimeParseU32 (s);
111 fMonth = DateTimeParseU32 (s);
112 fDay = DateTimeParseU32 (s);
113 fHour = DateTimeParseU32 (s);
114 fMinute = DateTimeParseU32 (s);
115 fSecond = DateTimeParseU32 (s);
116
117 return IsValid ();
118
119 }
120
121 /*****************************************************************************/
122
Encode_ISO_8601() const123 dng_string dng_time_zone::Encode_ISO_8601 () const
124 {
125
126 dng_string result;
127
128 if (IsValid ())
129 {
130
131 if (OffsetMinutes () == 0)
132 {
133
134 result.Set ("Z");
135
136 }
137
138 else
139 {
140
141 char s [64];
142
143 int offset = OffsetMinutes ();
144
145 if (offset > 0)
146 {
147
148 sprintf (s, "+%02d:%02d", offset / 60, offset % 60);
149
150 }
151
152 else
153 {
154
155 offset = -offset;
156
157 sprintf (s, "-%02d:%02d", offset / 60, offset % 60);
158
159 }
160
161 result.Set (s);
162
163 }
164
165 }
166
167 return result;
168
169 }
170
171 /*****************************************************************************/
172
dng_date_time_info()173 dng_date_time_info::dng_date_time_info ()
174
175 : fDateOnly (true)
176 , fDateTime ()
177 , fSubseconds ()
178 , fTimeZone ()
179
180 {
181
182 }
183
184 /*****************************************************************************/
185
IsValid() const186 bool dng_date_time_info::IsValid () const
187 {
188
189 return fDateTime.IsValid ();
190
191 }
192
193 /*****************************************************************************/
194
SetDate(uint32 year,uint32 month,uint32 day)195 void dng_date_time_info::SetDate (uint32 year,
196 uint32 month,
197 uint32 day)
198 {
199
200 fDateTime.fYear = year;
201 fDateTime.fMonth = month;
202 fDateTime.fDay = day;
203
204 }
205
206 /*****************************************************************************/
207
SetTime(uint32 hour,uint32 minute,uint32 second)208 void dng_date_time_info::SetTime (uint32 hour,
209 uint32 minute,
210 uint32 second)
211 {
212
213 fDateOnly = false;
214
215 fDateTime.fHour = hour;
216 fDateTime.fMinute = minute;
217 fDateTime.fSecond = second;
218
219 }
220
221 /*****************************************************************************/
222
SetOffsetTime(const dng_string & s)223 void dng_date_time_info::SetOffsetTime (const dng_string &s)
224 {
225
226 // Initialize zone to invalid.
227
228 dng_time_zone zone;
229
230 SetZone (zone);
231
232 // Parse EXIF OffsetTime format.
233
234 if (s.Length () == 6 &&
235 (s.Get () [0] == '+' || s.Get () [0] == '-') &&
236 (s.Get () [1] >= '0' && s.Get () [1] <= '1') &&
237 (s.Get () [2] >= '0' && s.Get () [2] <= '9') &&
238 (s.Get () [3] == ':') &&
239 (s.Get () [4] >= '0' && s.Get () [4] <= '5') &&
240 (s.Get () [5] >= '0' && s.Get () [5] <= '9'))
241 {
242
243 int32 hours = (s.Get () [1] - '0') * 10 +
244 (s.Get () [2] - '0');
245
246 int32 minutes = (s.Get () [4] - '0') * 10 +
247 (s.Get () [5] - '0');
248
249 int32 offset = hours * 60 + minutes;
250
251 if (s.Get () [0] == '-')
252 {
253 offset = -offset;
254 }
255
256 zone.SetOffsetMinutes (offset);
257
258 if (zone.IsValid ())
259 {
260
261 SetZone (zone);
262
263 }
264
265 }
266
267 }
268
269 /*****************************************************************************/
270
OffsetTime() const271 dng_string dng_date_time_info::OffsetTime () const
272 {
273
274 dng_string result;
275
276 if (TimeZone ().IsValid ())
277 {
278
279 int32 offset = TimeZone ().OffsetMinutes ();
280
281 char s [7];
282
283 s [0] = (offset >= 0) ? '+' : '-';
284
285 offset = Abs_int32 (offset);
286
287 uint32 hours = offset / 60;
288 uint32 minutes = offset % 60;
289
290 s [1] = (hours / 10) + '0';
291 s [2] = (hours % 10) + '0';
292
293 s [3] = ':';
294
295 s [4] = (minutes / 10) + '0';
296 s [5] = (minutes % 10) + '0';
297
298 result.Set (s);
299
300 }
301
302 else
303 {
304
305 result.Set (" : ");
306
307 }
308
309 return result;
310
311 }
312
313 /*****************************************************************************/
314
Decode_ISO_8601(const char * s)315 void dng_date_time_info::Decode_ISO_8601 (const char *s)
316 {
317
318 Clear ();
319
320 uint32 len = (uint32) strlen (s);
321
322 if (!len)
323 {
324 return;
325 }
326
327 unsigned year = 0;
328 unsigned month = 0;
329 unsigned day = 0;
330
331 if (sscanf (s,
332 "%u-%u-%u",
333 &year,
334 &month,
335 &day) != 3)
336 {
337 return;
338 }
339
340 SetDate ((uint32) year,
341 (uint32) month,
342 (uint32) day);
343
344 if (fDateTime.NotValid ())
345 {
346 Clear ();
347 return;
348 }
349
350 for (uint32 j = 0; j < len; j++)
351 {
352
353 if (s [j] == 'T')
354 {
355
356 unsigned hour = 0;
357 unsigned minute = 0;
358 unsigned second = 0;
359
360 int items = sscanf (s + j + 1,
361 "%u:%u:%u",
362 &hour,
363 &minute,
364 &second);
365
366 if (items >= 2 && items <= 3)
367 {
368
369 SetTime ((uint32) hour,
370 (uint32) minute,
371 (uint32) second);
372
373 if (fDateTime.NotValid ())
374 {
375 Clear ();
376 return;
377 }
378
379 if (items == 3)
380 {
381
382 for (uint32 k = j + 1; k < len; k++)
383 {
384
385 if (s [k] == '.')
386 {
387
388 while (++k < len && s [k] >= '0' && s [k] <= '9')
389 {
390
391 char ss [2];
392
393 ss [0] = s [k];
394 ss [1] = 0;
395
396 fSubseconds.Append (ss);
397
398 }
399
400 break;
401
402 }
403
404 }
405
406 }
407
408 for (uint32 k = j + 1; k < len; k++)
409 {
410
411 if (s [k] == 'Z')
412 {
413
414 fTimeZone.SetOffsetMinutes (0);
415
416 break;
417
418 }
419
420 if (s [k] == '+' || s [k] == '-')
421 {
422
423 int32 sign = (s [k] == '-' ? -1 : 1);
424
425 unsigned tzhour = 0;
426 unsigned tzmin = 0;
427
428 if (sscanf (s + k + 1,
429 "%u:%u",
430 &tzhour,
431 &tzmin) > 0)
432 {
433
434 fTimeZone.SetOffsetMinutes (sign * (tzhour * 60 + tzmin));
435
436 }
437
438 break;
439
440 }
441
442 }
443
444 }
445
446 break;
447
448 }
449
450 }
451
452 }
453
454 /*****************************************************************************/
455
Encode_ISO_8601() const456 dng_string dng_date_time_info::Encode_ISO_8601 () const
457 {
458
459 dng_string result;
460
461 if (IsValid ())
462 {
463
464 char s [256];
465
466 sprintf (s,
467 "%04u-%02u-%02u",
468 (unsigned) fDateTime.fYear,
469 (unsigned) fDateTime.fMonth,
470 (unsigned) fDateTime.fDay);
471
472 result.Set (s);
473
474 if (!fDateOnly)
475 {
476
477 sprintf (s,
478 "T%02u:%02u:%02u",
479 (unsigned) fDateTime.fHour,
480 (unsigned) fDateTime.fMinute,
481 (unsigned) fDateTime.fSecond);
482
483 result.Append (s);
484
485 if (fSubseconds.NotEmpty ())
486 {
487
488 bool subsecondsValid = true;
489
490 uint32 len = fSubseconds.Length ();
491
492 for (uint32 index = 0; index < len; index++)
493 {
494
495 if (fSubseconds.Get () [index] < '0' ||
496 fSubseconds.Get () [index] > '9')
497 {
498 subsecondsValid = false;
499 break;
500 }
501
502 }
503
504 if (subsecondsValid)
505 {
506 result.Append (".");
507 result.Append (fSubseconds.Get ());
508 }
509
510 }
511
512 if (gDNGUseFakeTimeZonesInXMP)
513 {
514
515 // Kludge: Early versions of the XMP toolkit assume Zulu time
516 // if the time zone is missing. It is safer for fill in the
517 // local time zone.
518
519 dng_time_zone tempZone = fTimeZone;
520
521 if (tempZone.NotValid ())
522 {
523 tempZone = LocalTimeZone (fDateTime);
524 }
525
526 result.Append (tempZone.Encode_ISO_8601 ().Get ());
527
528 }
529
530 else
531 {
532
533 // MWG: Now we don't fill in the local time zone. So only
534 // add the time zone if it is known and valid.
535
536 if (fTimeZone.IsValid ())
537 {
538 result.Append (fTimeZone.Encode_ISO_8601 ().Get ());
539 }
540
541 }
542
543 }
544
545 }
546
547 return result;
548
549 }
550
551 /*****************************************************************************/
552
Decode_IPTC_Date(const char * s)553 void dng_date_time_info::Decode_IPTC_Date (const char *s)
554 {
555
556 if (strlen (s) == 8)
557 {
558
559 unsigned year = 0;
560 unsigned month = 0;
561 unsigned day = 0;
562
563 if (sscanf (s,
564 "%4u%2u%2u",
565 &year,
566 &month,
567 &day) == 3)
568 {
569
570 SetDate ((uint32) year,
571 (uint32) month,
572 (uint32) day);
573
574 }
575
576 }
577
578 }
579
580 /*****************************************************************************/
581
Encode_IPTC_Date() const582 dng_string dng_date_time_info::Encode_IPTC_Date () const
583 {
584
585 dng_string result;
586
587 if (IsValid ())
588 {
589
590 char s [64];
591
592 sprintf (s,
593 "%04u%02u%02u",
594 (unsigned) fDateTime.fYear,
595 (unsigned) fDateTime.fMonth,
596 (unsigned) fDateTime.fDay);
597
598 result.Set (s);
599
600 }
601
602 return result;
603
604 }
605
606 /*****************************************************************************/
607
Decode_IPTC_Time(const char * s)608 void dng_date_time_info::Decode_IPTC_Time (const char *s)
609 {
610
611 if (strlen (s) == 11)
612 {
613
614 char time [12];
615
616 memcpy (time, s, sizeof (time));
617
618 if (time [6] == '+' ||
619 time [6] == '-')
620 {
621
622 int tzsign = (time [6] == '-') ? -1 : 1;
623
624 time [6] = 0;
625
626 unsigned hour = 0;
627 unsigned minute = 0;
628 unsigned second = 0;
629 unsigned tzhour = 0;
630 unsigned tzmin = 0;
631
632 if (sscanf (time,
633 "%2u%2u%2u",
634 &hour,
635 &minute,
636 &second) == 3 &&
637 sscanf (time + 7,
638 "%2u%2u",
639 &tzhour,
640 &tzmin) == 2)
641 {
642
643 dng_time_zone zone;
644
645 zone.SetOffsetMinutes (tzsign * (tzhour * 60 + tzmin));
646
647 if (zone.IsValid ())
648 {
649
650 SetTime ((uint32) hour,
651 (uint32) minute,
652 (uint32) second);
653
654 SetZone (zone);
655
656 }
657
658 }
659
660 }
661
662 }
663
664 else if (strlen (s) == 6)
665 {
666
667 unsigned hour = 0;
668 unsigned minute = 0;
669 unsigned second = 0;
670
671 if (sscanf (s,
672 "%2u%2u%2u",
673 &hour,
674 &minute,
675 &second) == 3)
676 {
677
678 SetTime ((uint32) hour,
679 (uint32) minute,
680 (uint32) second);
681
682 }
683
684 }
685
686 else if (strlen (s) == 4)
687 {
688
689 unsigned hour = 0;
690 unsigned minute = 0;
691
692 if (sscanf (s,
693 "%2u%2u",
694 &hour,
695 &minute) == 2)
696 {
697
698 SetTime ((uint32) hour,
699 (uint32) minute,
700 0);
701
702 }
703
704 }
705
706 }
707
708 /*****************************************************************************/
709
Encode_IPTC_Time() const710 dng_string dng_date_time_info::Encode_IPTC_Time () const
711 {
712
713 dng_string result;
714
715 if (IsValid () && !fDateOnly)
716 {
717
718 char s [64];
719
720 if (fTimeZone.IsValid ())
721 {
722
723 sprintf (s,
724 "%02u%02u%02u%c%02u%02u",
725 (unsigned) fDateTime.fHour,
726 (unsigned) fDateTime.fMinute,
727 (unsigned) fDateTime.fSecond,
728 (int) (fTimeZone.OffsetMinutes () >= 0 ? '+' : '-'),
729 (unsigned) (Abs_int32 (fTimeZone.OffsetMinutes ()) / 60),
730 (unsigned) (Abs_int32 (fTimeZone.OffsetMinutes ()) % 60));
731
732 }
733
734 else
735 {
736
737 sprintf (s,
738 "%02u%02u%02u",
739 (unsigned) fDateTime.fHour,
740 (unsigned) fDateTime.fMinute,
741 (unsigned) fDateTime.fSecond);
742
743 }
744
745 result.Set (s);
746
747 }
748
749 return result;
750
751 }
752
753 /*****************************************************************************/
754
755 static dng_std_mutex gDateTimeMutex;
756
757 /*****************************************************************************/
758
CurrentDateTimeAndZone(dng_date_time_info & info)759 void CurrentDateTimeAndZone (dng_date_time_info &info)
760 {
761
762 time_t sec;
763
764 time (&sec);
765
766 tm t;
767 tm zt;
768
769 {
770
771 dng_lock_std_mutex lock (gDateTimeMutex);
772
773 t = *localtime (&sec);
774 zt = *gmtime (&sec);
775
776 }
777
778 dng_date_time dt;
779
780 dt.fYear = t.tm_year + 1900;
781 dt.fMonth = t.tm_mon + 1;
782 dt.fDay = t.tm_mday;
783 dt.fHour = t.tm_hour;
784 dt.fMinute = t.tm_min;
785 dt.fSecond = t.tm_sec;
786
787 info.SetDateTime (dt);
788
789 int tzHour = t.tm_hour - zt.tm_hour;
790 int tzMin = t.tm_min - zt.tm_min;
791
792 bool zonePositive = (t.tm_year > zt.tm_year) ||
793 (t.tm_year == zt.tm_year && t.tm_yday > zt.tm_yday) ||
794 (t.tm_year == zt.tm_year && t.tm_yday == zt.tm_yday && tzHour > 0) ||
795 (t.tm_year == zt.tm_year && t.tm_yday == zt.tm_yday && tzHour == 0 && tzMin >= 0);
796
797 tzMin += tzHour * 60;
798
799 if (zonePositive)
800 {
801
802 while (tzMin < 0)
803 tzMin += 24 * 60;
804
805 }
806
807 else
808 {
809
810 while (tzMin > 0)
811 tzMin -= 24 * 60;
812
813 }
814
815 dng_time_zone zone;
816
817 zone.SetOffsetMinutes (tzMin);
818
819 info.SetZone (zone);
820
821 }
822
823 /*****************************************************************************/
824
DecodeUnixTime(uint32 unixTime,dng_date_time & dt)825 void DecodeUnixTime (uint32 unixTime, dng_date_time &dt)
826 {
827
828 time_t sec = (time_t) unixTime;
829
830 tm t;
831
832 {
833
834 dng_lock_std_mutex lock (gDateTimeMutex);
835
836 #if qMacOS && !defined(__MACH__)
837
838 // Macintosh CFM stores time in local time zone.
839
840 tm *tp = localtime (&sec);
841
842 #else
843
844 // Macintosh Mach-O and Windows stores time in Zulu time.
845
846 tm *tp = gmtime (&sec);
847
848 #endif
849
850 if (!tp)
851 {
852 dt.Clear ();
853 return;
854 }
855
856 t = *tp;
857
858 }
859
860 dt.fYear = t.tm_year + 1900;
861 dt.fMonth = t.tm_mon + 1;
862 dt.fDay = t.tm_mday;
863 dt.fHour = t.tm_hour;
864 dt.fMinute = t.tm_min;
865 dt.fSecond = t.tm_sec;
866
867 }
868
869 /*****************************************************************************/
870
LocalTimeZone(const dng_date_time & dt)871 dng_time_zone LocalTimeZone (const dng_date_time &dt)
872 {
873
874 dng_time_zone result;
875
876 if (dt.IsValid ())
877 {
878
879 #if qMacOS
880
881 CFTimeZoneRef zoneRef = CFTimeZoneCopyDefault ();
882
883 CFReleaseHelper<CFTimeZoneRef> zoneRefDeleter (zoneRef);
884
885 if (zoneRef)
886 {
887
888 // New path that doesn't use deprecated CFGregorian-based APIs.
889
890 CFCalendarRef calendar =
891 CFCalendarCreateWithIdentifier (kCFAllocatorDefault,
892 kCFGregorianCalendar);
893
894 CFReleaseHelper<CFCalendarRef> calendarDeleter (calendar);
895
896 CFAbsoluteTime absTime;
897
898 if (CFCalendarComposeAbsoluteTime (calendar,
899 &absTime,
900 "yMdHms",
901 dt.fYear,
902 dt.fMonth,
903 dt.fDay,
904 dt.fHour,
905 dt.fMinute,
906 dt.fSecond))
907 {
908
909 CFTimeInterval secondsDelta = CFTimeZoneGetSecondsFromGMT (zoneRef, absTime);
910
911 result.SetOffsetSeconds (Round_int32 (secondsDelta));
912
913 if (result.IsValid ())
914 {
915 return result;
916 }
917
918 }
919
920 }
921
922 #endif
923
924 #if qWinOS
925
926 if (GetTimeZoneInformation != NULL &&
927 SystemTimeToTzSpecificLocalTime != NULL &&
928 SystemTimeToFileTime != NULL)
929 {
930
931 TIME_ZONE_INFORMATION tzInfo;
932
933 DWORD x = GetTimeZoneInformation (&tzInfo);
934
935 SYSTEMTIME localST;
936
937 memset (&localST, 0, sizeof (localST));
938
939 localST.wYear = (WORD) dt.fYear;
940 localST.wMonth = (WORD) dt.fMonth;
941 localST.wDay = (WORD) dt.fDay;
942 localST.wHour = (WORD) dt.fHour;
943 localST.wMinute = (WORD) dt.fMinute;
944 localST.wSecond = (WORD) dt.fSecond;
945
946 SYSTEMTIME utcST;
947
948 if (TzSpecificLocalTimeToSystemTime (&tzInfo, &localST, &utcST))
949 {
950
951 FILETIME localFT;
952 FILETIME utcFT;
953
954 (void) SystemTimeToFileTime (&localST, &localFT);
955 (void) SystemTimeToFileTime (&utcST , &utcFT );
956
957 uint64 time1 = (((uint64) localFT.dwHighDateTime) << 32) + localFT.dwLowDateTime;
958 uint64 time2 = (((uint64) utcFT .dwHighDateTime) << 32) + utcFT .dwLowDateTime;
959
960 // FILETIMEs are in units to 100 ns. Convert to seconds.
961
962 int64 time1Sec = time1 / 10000000;
963 int64 time2Sec = time2 / 10000000;
964
965 int32 delta = (int32) (time1Sec - time2Sec);
966
967 result.SetOffsetSeconds (delta);
968
969 if (result.IsValid ())
970 {
971 return result;
972 }
973
974 }
975
976 }
977
978 #endif
979
980 }
981
982 // Figure out local time zone.
983
984 dng_date_time_info current_info;
985
986 CurrentDateTimeAndZone (current_info);
987
988 result = current_info.TimeZone ();
989
990 return result;
991
992 }
993
994 /*****************************************************************************/
995
dng_date_time_storage_info()996 dng_date_time_storage_info::dng_date_time_storage_info ()
997
998 : fOffset (kDNGStreamInvalidOffset )
999 , fFormat (dng_date_time_format_unknown)
1000
1001 {
1002
1003 }
1004
1005 /*****************************************************************************/
1006
dng_date_time_storage_info(uint64 offset,dng_date_time_format format)1007 dng_date_time_storage_info::dng_date_time_storage_info (uint64 offset,
1008 dng_date_time_format format)
1009
1010 : fOffset (offset)
1011 , fFormat (format)
1012
1013 {
1014
1015 }
1016
1017 /*****************************************************************************/
1018
IsValid() const1019 bool dng_date_time_storage_info::IsValid () const
1020 {
1021
1022 return fOffset != kDNGStreamInvalidOffset;
1023
1024 }
1025
1026 /*****************************************************************************/
1027
Offset() const1028 uint64 dng_date_time_storage_info::Offset () const
1029 {
1030
1031 if (!IsValid ())
1032 ThrowProgramError ();
1033
1034 return fOffset;
1035
1036 }
1037
1038 /*****************************************************************************/
1039
Format() const1040 dng_date_time_format dng_date_time_storage_info::Format () const
1041 {
1042
1043 if (!IsValid ())
1044 ThrowProgramError ();
1045
1046 return fFormat;
1047
1048 }
1049
1050 /*****************************************************************************/
1051