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