/*****************************************************************************/
// 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;
}
/*****************************************************************************/