/*****************************************************************************/ // 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 #if qMacOS #include #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR #include #else #include #endif // TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR #endif // qMacOS #if qWinOS #include #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 (); CFReleaseHelper zoneRefDeleter (zoneRef); if (zoneRef) { // New path that doesn't use deprecated CFGregorian-based APIs. CFCalendarRef calendar = CFCalendarCreateWithIdentifier (kCFAllocatorDefault, kCFGregorianCalendar); CFReleaseHelper calendarDeleter (calendar); CFAbsoluteTime absTime; if (CFCalendarComposeAbsoluteTime (calendar, &absTime, "yMdHms", dt.fYear, dt.fMonth, dt.fDay, dt.fHour, dt.fMinute, dt.fSecond)) { CFTimeInterval secondsDelta = CFTimeZoneGetSecondsFromGMT (zoneRef, absTime); 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; } /*****************************************************************************/