You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
965 lines
17 KiB
965 lines
17 KiB
/*****************************************************************************/ |
|
// Copyright 2006-2008 Adobe Systems Incorporated |
|
// All Rights Reserved. |
|
// |
|
// NOTICE: Adobe permits you to use, modify, and distribute this file in |
|
// accordance with the terms of the Adobe license agreement accompanying it. |
|
/*****************************************************************************/ |
|
|
|
/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_date_time.cpp#2 $ */ |
|
/* $DateTime: 2012/06/01 07:28:57 $ */ |
|
/* $Change: 832715 $ */ |
|
/* $Author: tknoll $ */ |
|
|
|
/*****************************************************************************/ |
|
|
|
#include "dng_date_time.h" |
|
|
|
#include "dng_exceptions.h" |
|
#include "dng_mutex.h" |
|
#include "dng_stream.h" |
|
#include "dng_string.h" |
|
#include "dng_utils.h" |
|
|
|
#include <time.h> |
|
|
|
#if qMacOS |
|
#include <TargetConditionals.h> |
|
#if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR |
|
#include <MobileCoreServices/MobileCoreServices.h> |
|
#else |
|
#include <CoreServices/CoreServices.h> |
|
#endif // TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR |
|
#endif // qMacOS |
|
|
|
#if qWinOS |
|
#include <windows.h> |
|
#endif |
|
|
|
/******************************************************************************/ |
|
|
|
// MWG says don't use fake time zones in XMP, but there is some |
|
// old software that requires them to work correctly. |
|
|
|
bool gDNGUseFakeTimeZonesInXMP = false; |
|
|
|
/******************************************************************************/ |
|
|
|
dng_date_time::dng_date_time () |
|
|
|
: fYear (0) |
|
, fMonth (0) |
|
, fDay (0) |
|
, fHour (0) |
|
, fMinute (0) |
|
, fSecond (0) |
|
|
|
{ |
|
|
|
} |
|
|
|
/******************************************************************************/ |
|
|
|
dng_date_time::dng_date_time (uint32 year, |
|
uint32 month, |
|
uint32 day, |
|
uint32 hour, |
|
uint32 minute, |
|
uint32 second) |
|
|
|
: fYear (year) |
|
, fMonth (month) |
|
, fDay (day) |
|
, fHour (hour) |
|
, fMinute (minute) |
|
, fSecond (second) |
|
|
|
{ |
|
|
|
} |
|
|
|
/******************************************************************************/ |
|
|
|
bool dng_date_time::IsValid () const |
|
{ |
|
|
|
return fYear >= 1 && fYear <= 9999 && |
|
fMonth >= 1 && fMonth <= 12 && |
|
fDay >= 1 && fDay <= 31 && |
|
fHour <= 23 && |
|
fMinute <= 59 && |
|
fSecond <= 59; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_date_time::Clear () |
|
{ |
|
|
|
*this = dng_date_time (); |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
static uint32 DateTimeParseU32 (const char *&s) |
|
{ |
|
|
|
uint32 x = 0; |
|
|
|
while (*s == ' ' || *s == ':') |
|
s++; |
|
|
|
while (*s >= '0' && *s <= '9') |
|
{ |
|
x = SafeUint32Mult(x, 10); |
|
x = SafeUint32Add(x, (uint32) (*(s++) - '0')); |
|
} |
|
|
|
return x; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
bool dng_date_time::Parse (const char *s) |
|
{ |
|
|
|
fYear = DateTimeParseU32 (s); |
|
fMonth = DateTimeParseU32 (s); |
|
fDay = DateTimeParseU32 (s); |
|
fHour = DateTimeParseU32 (s); |
|
fMinute = DateTimeParseU32 (s); |
|
fSecond = DateTimeParseU32 (s); |
|
|
|
return IsValid (); |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_string dng_time_zone::Encode_ISO_8601 () const |
|
{ |
|
|
|
dng_string result; |
|
|
|
if (IsValid ()) |
|
{ |
|
|
|
if (OffsetMinutes () == 0) |
|
{ |
|
|
|
result.Set ("Z"); |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
char s [64]; |
|
|
|
int offset = OffsetMinutes (); |
|
|
|
if (offset > 0) |
|
{ |
|
|
|
sprintf (s, "+%02d:%02d", offset / 60, offset % 60); |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
offset = -offset; |
|
|
|
sprintf (s, "-%02d:%02d", offset / 60, offset % 60); |
|
|
|
} |
|
|
|
result.Set (s); |
|
|
|
} |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_date_time_info::dng_date_time_info () |
|
|
|
: fDateOnly (true) |
|
, fDateTime () |
|
, fSubseconds () |
|
, fTimeZone () |
|
|
|
{ |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
bool dng_date_time_info::IsValid () const |
|
{ |
|
|
|
return fDateTime.IsValid (); |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_date_time_info::SetDate (uint32 year, |
|
uint32 month, |
|
uint32 day) |
|
{ |
|
|
|
fDateTime.fYear = year; |
|
fDateTime.fMonth = month; |
|
fDateTime.fDay = day; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_date_time_info::SetTime (uint32 hour, |
|
uint32 minute, |
|
uint32 second) |
|
{ |
|
|
|
fDateOnly = false; |
|
|
|
fDateTime.fHour = hour; |
|
fDateTime.fMinute = minute; |
|
fDateTime.fSecond = second; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_date_time_info::Decode_ISO_8601 (const char *s) |
|
{ |
|
|
|
Clear (); |
|
|
|
uint32 len = (uint32) strlen (s); |
|
|
|
if (!len) |
|
{ |
|
return; |
|
} |
|
|
|
unsigned year = 0; |
|
unsigned month = 0; |
|
unsigned day = 0; |
|
|
|
if (sscanf (s, |
|
"%u-%u-%u", |
|
&year, |
|
&month, |
|
&day) != 3) |
|
{ |
|
return; |
|
} |
|
|
|
SetDate ((uint32) year, |
|
(uint32) month, |
|
(uint32) day); |
|
|
|
if (fDateTime.NotValid ()) |
|
{ |
|
Clear (); |
|
return; |
|
} |
|
|
|
for (uint32 j = 0; j < len; j++) |
|
{ |
|
|
|
if (s [j] == 'T') |
|
{ |
|
|
|
unsigned hour = 0; |
|
unsigned minute = 0; |
|
unsigned second = 0; |
|
|
|
int items = sscanf (s + j + 1, |
|
"%u:%u:%u", |
|
&hour, |
|
&minute, |
|
&second); |
|
|
|
if (items >= 2 && items <= 3) |
|
{ |
|
|
|
SetTime ((uint32) hour, |
|
(uint32) minute, |
|
(uint32) second); |
|
|
|
if (fDateTime.NotValid ()) |
|
{ |
|
Clear (); |
|
return; |
|
} |
|
|
|
if (items == 3) |
|
{ |
|
|
|
for (uint32 k = j + 1; k < len; k++) |
|
{ |
|
|
|
if (s [k] == '.') |
|
{ |
|
|
|
while (++k < len && s [k] >= '0' && s [k] <= '9') |
|
{ |
|
|
|
char ss [2]; |
|
|
|
ss [0] = s [k]; |
|
ss [1] = 0; |
|
|
|
fSubseconds.Append (ss); |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
for (uint32 k = j + 1; k < len; k++) |
|
{ |
|
|
|
if (s [k] == 'Z') |
|
{ |
|
|
|
fTimeZone.SetOffsetMinutes (0); |
|
|
|
break; |
|
|
|
} |
|
|
|
if (s [k] == '+' || s [k] == '-') |
|
{ |
|
|
|
int32 sign = (s [k] == '-' ? -1 : 1); |
|
|
|
unsigned tzhour = 0; |
|
unsigned tzmin = 0; |
|
|
|
if (sscanf (s + k + 1, |
|
"%u:%u", |
|
&tzhour, |
|
&tzmin) > 0) |
|
{ |
|
|
|
fTimeZone.SetOffsetMinutes (sign * (tzhour * 60 + tzmin)); |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_string dng_date_time_info::Encode_ISO_8601 () const |
|
{ |
|
|
|
dng_string result; |
|
|
|
if (IsValid ()) |
|
{ |
|
|
|
char s [256]; |
|
|
|
sprintf (s, |
|
"%04u-%02u-%02u", |
|
(unsigned) fDateTime.fYear, |
|
(unsigned) fDateTime.fMonth, |
|
(unsigned) fDateTime.fDay); |
|
|
|
result.Set (s); |
|
|
|
if (!fDateOnly) |
|
{ |
|
|
|
sprintf (s, |
|
"T%02u:%02u:%02u", |
|
(unsigned) fDateTime.fHour, |
|
(unsigned) fDateTime.fMinute, |
|
(unsigned) fDateTime.fSecond); |
|
|
|
result.Append (s); |
|
|
|
if (fSubseconds.NotEmpty ()) |
|
{ |
|
|
|
bool subsecondsValid = true; |
|
|
|
uint32 len = fSubseconds.Length (); |
|
|
|
for (uint32 index = 0; index < len; index++) |
|
{ |
|
|
|
if (fSubseconds.Get () [index] < '0' || |
|
fSubseconds.Get () [index] > '9') |
|
{ |
|
subsecondsValid = false; |
|
break; |
|
} |
|
|
|
} |
|
|
|
if (subsecondsValid) |
|
{ |
|
result.Append ("."); |
|
result.Append (fSubseconds.Get ()); |
|
} |
|
|
|
} |
|
|
|
if (gDNGUseFakeTimeZonesInXMP) |
|
{ |
|
|
|
// Kludge: Early versions of the XMP toolkit assume Zulu time |
|
// if the time zone is missing. It is safer for fill in the |
|
// local time zone. |
|
|
|
dng_time_zone tempZone = fTimeZone; |
|
|
|
if (tempZone.NotValid ()) |
|
{ |
|
tempZone = LocalTimeZone (fDateTime); |
|
} |
|
|
|
result.Append (tempZone.Encode_ISO_8601 ().Get ()); |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
// MWG: Now we don't fill in the local time zone. So only |
|
// add the time zone if it is known and valid. |
|
|
|
if (fTimeZone.IsValid ()) |
|
{ |
|
result.Append (fTimeZone.Encode_ISO_8601 ().Get ()); |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_date_time_info::Decode_IPTC_Date (const char *s) |
|
{ |
|
|
|
if (strlen (s) == 8) |
|
{ |
|
|
|
unsigned year = 0; |
|
unsigned month = 0; |
|
unsigned day = 0; |
|
|
|
if (sscanf (s, |
|
"%4u%2u%2u", |
|
&year, |
|
&month, |
|
&day) == 3) |
|
{ |
|
|
|
SetDate ((uint32) year, |
|
(uint32) month, |
|
(uint32) day); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_string dng_date_time_info::Encode_IPTC_Date () const |
|
{ |
|
|
|
dng_string result; |
|
|
|
if (IsValid ()) |
|
{ |
|
|
|
char s [64]; |
|
|
|
sprintf (s, |
|
"%04u%02u%02u", |
|
(unsigned) fDateTime.fYear, |
|
(unsigned) fDateTime.fMonth, |
|
(unsigned) fDateTime.fDay); |
|
|
|
result.Set (s); |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void dng_date_time_info::Decode_IPTC_Time (const char *s) |
|
{ |
|
|
|
if (strlen (s) == 11) |
|
{ |
|
|
|
char time [12]; |
|
|
|
memcpy (time, s, sizeof (time)); |
|
|
|
if (time [6] == '+' || |
|
time [6] == '-') |
|
{ |
|
|
|
int tzsign = (time [6] == '-') ? -1 : 1; |
|
|
|
time [6] = 0; |
|
|
|
unsigned hour = 0; |
|
unsigned minute = 0; |
|
unsigned second = 0; |
|
unsigned tzhour = 0; |
|
unsigned tzmin = 0; |
|
|
|
if (sscanf (time, |
|
"%2u%2u%2u", |
|
&hour, |
|
&minute, |
|
&second) == 3 && |
|
sscanf (time + 7, |
|
"%2u%2u", |
|
&tzhour, |
|
&tzmin) == 2) |
|
{ |
|
|
|
dng_time_zone zone; |
|
|
|
zone.SetOffsetMinutes (tzsign * (tzhour * 60 + tzmin)); |
|
|
|
if (zone.IsValid ()) |
|
{ |
|
|
|
SetTime ((uint32) hour, |
|
(uint32) minute, |
|
(uint32) second); |
|
|
|
SetZone (zone); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
else if (strlen (s) == 6) |
|
{ |
|
|
|
unsigned hour = 0; |
|
unsigned minute = 0; |
|
unsigned second = 0; |
|
|
|
if (sscanf (s, |
|
"%2u%2u%2u", |
|
&hour, |
|
&minute, |
|
&second) == 3) |
|
{ |
|
|
|
SetTime ((uint32) hour, |
|
(uint32) minute, |
|
(uint32) second); |
|
|
|
} |
|
|
|
} |
|
|
|
else if (strlen (s) == 4) |
|
{ |
|
|
|
unsigned hour = 0; |
|
unsigned minute = 0; |
|
|
|
if (sscanf (s, |
|
"%2u%2u", |
|
&hour, |
|
&minute) == 2) |
|
{ |
|
|
|
SetTime ((uint32) hour, |
|
(uint32) minute, |
|
0); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_string dng_date_time_info::Encode_IPTC_Time () const |
|
{ |
|
|
|
dng_string result; |
|
|
|
if (IsValid () && !fDateOnly) |
|
{ |
|
|
|
char s [64]; |
|
|
|
if (fTimeZone.IsValid ()) |
|
{ |
|
|
|
sprintf (s, |
|
"%02u%02u%02u%c%02u%02u", |
|
(unsigned) fDateTime.fHour, |
|
(unsigned) fDateTime.fMinute, |
|
(unsigned) fDateTime.fSecond, |
|
(int) (fTimeZone.OffsetMinutes () >= 0 ? '+' : '-'), |
|
(unsigned) (Abs_int32 (fTimeZone.OffsetMinutes ()) / 60), |
|
(unsigned) (Abs_int32 (fTimeZone.OffsetMinutes ()) % 60)); |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
sprintf (s, |
|
"%02u%02u%02u", |
|
(unsigned) fDateTime.fHour, |
|
(unsigned) fDateTime.fMinute, |
|
(unsigned) fDateTime.fSecond); |
|
|
|
} |
|
|
|
result.Set (s); |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
static dng_mutex gDateTimeMutex ("gDateTimeMutex"); |
|
|
|
/*****************************************************************************/ |
|
|
|
void CurrentDateTimeAndZone (dng_date_time_info &info) |
|
{ |
|
|
|
time_t sec; |
|
|
|
time (&sec); |
|
|
|
tm t; |
|
tm zt; |
|
|
|
{ |
|
|
|
dng_lock_mutex lock (&gDateTimeMutex); |
|
|
|
t = *localtime (&sec); |
|
zt = *gmtime (&sec); |
|
|
|
} |
|
|
|
dng_date_time dt; |
|
|
|
dt.fYear = t.tm_year + 1900; |
|
dt.fMonth = t.tm_mon + 1; |
|
dt.fDay = t.tm_mday; |
|
dt.fHour = t.tm_hour; |
|
dt.fMinute = t.tm_min; |
|
dt.fSecond = t.tm_sec; |
|
|
|
info.SetDateTime (dt); |
|
|
|
int tzHour = t.tm_hour - zt.tm_hour; |
|
int tzMin = t.tm_min - zt.tm_min; |
|
|
|
bool zonePositive = (t.tm_year > zt.tm_year) || |
|
(t.tm_year == zt.tm_year && t.tm_yday > zt.tm_yday) || |
|
(t.tm_year == zt.tm_year && t.tm_yday == zt.tm_yday && tzHour > 0) || |
|
(t.tm_year == zt.tm_year && t.tm_yday == zt.tm_yday && tzHour == 0 && tzMin >= 0); |
|
|
|
tzMin += tzHour * 60; |
|
|
|
if (zonePositive) |
|
{ |
|
|
|
while (tzMin < 0) |
|
tzMin += 24 * 60; |
|
|
|
} |
|
|
|
else |
|
{ |
|
|
|
while (tzMin > 0) |
|
tzMin -= 24 * 60; |
|
|
|
} |
|
|
|
dng_time_zone zone; |
|
|
|
zone.SetOffsetMinutes (tzMin); |
|
|
|
info.SetZone (zone); |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
void DecodeUnixTime (uint32 unixTime, dng_date_time &dt) |
|
{ |
|
|
|
time_t sec = (time_t) unixTime; |
|
|
|
tm t; |
|
|
|
{ |
|
|
|
dng_lock_mutex lock (&gDateTimeMutex); |
|
|
|
#if qMacOS && !defined(__MACH__) |
|
|
|
// Macintosh CFM stores time in local time zone. |
|
|
|
tm *tp = localtime (&sec); |
|
|
|
#else |
|
|
|
// Macintosh Mach-O and Windows stores time in Zulu time. |
|
|
|
tm *tp = gmtime (&sec); |
|
|
|
#endif |
|
|
|
if (!tp) |
|
{ |
|
dt.Clear (); |
|
return; |
|
} |
|
|
|
t = *tp; |
|
|
|
} |
|
|
|
dt.fYear = t.tm_year + 1900; |
|
dt.fMonth = t.tm_mon + 1; |
|
dt.fDay = t.tm_mday; |
|
dt.fHour = t.tm_hour; |
|
dt.fMinute = t.tm_min; |
|
dt.fSecond = t.tm_sec; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_time_zone LocalTimeZone (const dng_date_time &dt) |
|
{ |
|
|
|
dng_time_zone result; |
|
|
|
if (dt.IsValid ()) |
|
{ |
|
|
|
#if qMacOS |
|
|
|
CFTimeZoneRef zoneRef = CFTimeZoneCopyDefault (); |
|
|
|
if (zoneRef) |
|
{ |
|
|
|
CFGregorianDate gregDate; |
|
|
|
gregDate.year = dt.fYear; |
|
gregDate.month = (SInt8) dt.fMonth; |
|
gregDate.day = (SInt8) dt.fDay; |
|
gregDate.hour = (SInt8) dt.fHour; |
|
gregDate.minute = (SInt8) dt.fMinute; |
|
gregDate.second = (SInt8) dt.fSecond; |
|
|
|
CFAbsoluteTime absTime = CFGregorianDateGetAbsoluteTime (gregDate, zoneRef); |
|
|
|
CFTimeInterval secondsDelta = CFTimeZoneGetSecondsFromGMT (zoneRef, absTime); |
|
|
|
CFRelease (zoneRef); |
|
|
|
result.SetOffsetSeconds (Round_int32 (secondsDelta)); |
|
|
|
if (result.IsValid ()) |
|
{ |
|
return result; |
|
} |
|
|
|
} |
|
|
|
#endif |
|
|
|
#if qWinOS |
|
|
|
if (GetTimeZoneInformation != NULL && |
|
SystemTimeToTzSpecificLocalTime != NULL && |
|
SystemTimeToFileTime != NULL) |
|
{ |
|
|
|
TIME_ZONE_INFORMATION tzInfo; |
|
|
|
DWORD x = GetTimeZoneInformation (&tzInfo); |
|
|
|
SYSTEMTIME localST; |
|
|
|
memset (&localST, 0, sizeof (localST)); |
|
|
|
localST.wYear = (WORD) dt.fYear; |
|
localST.wMonth = (WORD) dt.fMonth; |
|
localST.wDay = (WORD) dt.fDay; |
|
localST.wHour = (WORD) dt.fHour; |
|
localST.wMinute = (WORD) dt.fMinute; |
|
localST.wSecond = (WORD) dt.fSecond; |
|
|
|
SYSTEMTIME utcST; |
|
|
|
if (TzSpecificLocalTimeToSystemTime (&tzInfo, &localST, &utcST)) |
|
{ |
|
|
|
FILETIME localFT; |
|
FILETIME utcFT; |
|
|
|
(void) SystemTimeToFileTime (&localST, &localFT); |
|
(void) SystemTimeToFileTime (&utcST , &utcFT ); |
|
|
|
uint64 time1 = (((uint64) localFT.dwHighDateTime) << 32) + localFT.dwLowDateTime; |
|
uint64 time2 = (((uint64) utcFT .dwHighDateTime) << 32) + utcFT .dwLowDateTime; |
|
|
|
// FILETIMEs are in units to 100 ns. Convert to seconds. |
|
|
|
int64 time1Sec = time1 / 10000000; |
|
int64 time2Sec = time2 / 10000000; |
|
|
|
int32 delta = (int32) (time1Sec - time2Sec); |
|
|
|
result.SetOffsetSeconds (delta); |
|
|
|
if (result.IsValid ()) |
|
{ |
|
return result; |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
#endif |
|
|
|
} |
|
|
|
// Figure out local time zone. |
|
|
|
dng_date_time_info current_info; |
|
|
|
CurrentDateTimeAndZone (current_info); |
|
|
|
result = current_info.TimeZone (); |
|
|
|
return result; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_date_time_storage_info::dng_date_time_storage_info () |
|
|
|
: fOffset (kDNGStreamInvalidOffset ) |
|
, fFormat (dng_date_time_format_unknown) |
|
|
|
{ |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_date_time_storage_info::dng_date_time_storage_info (uint64 offset, |
|
dng_date_time_format format) |
|
|
|
: fOffset (offset) |
|
, fFormat (format) |
|
|
|
{ |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
bool dng_date_time_storage_info::IsValid () const |
|
{ |
|
|
|
return fOffset != kDNGStreamInvalidOffset; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
uint64 dng_date_time_storage_info::Offset () const |
|
{ |
|
|
|
if (!IsValid ()) |
|
ThrowProgramError (); |
|
|
|
return fOffset; |
|
|
|
} |
|
|
|
/*****************************************************************************/ |
|
|
|
dng_date_time_format dng_date_time_storage_info::Format () const |
|
{ |
|
|
|
if (!IsValid ()) |
|
ThrowProgramError (); |
|
|
|
return fFormat; |
|
|
|
} |
|
|
|
/*****************************************************************************/
|
|
|