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