//============================================================================= // File: datetime.cpp // Contents: Definitions for DwDateTime // Maintainer: Doug Sauder // WWW: http://www.fwb.gulf.net/~dwsauder/mimepp.html // // Copyright (c) 1996, 1997 Douglas W. Sauder // All rights reserved. // // IN NO EVENT SHALL DOUGLAS W. SAUDER BE LIABLE TO ANY PARTY FOR DIRECT, // INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF // THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF DOUGLAS W. SAUDER // HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // DOUGLAS W. SAUDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT // NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" // BASIS, AND DOUGLAS W. SAUDER HAS NO OBLIGATION TO PROVIDE MAINTENANCE, // SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. // //============================================================================= #define DW_IMPLEMENTATION #ifdef HAVE_CONFIG_H #include "../config.h" #endif #include #include #include #include #include #include #include #include #include static char lWeekDay[7][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static char lMonth[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; extern "C" int ParseRfc822Date(const char *str, struct tm *tms, int *z); extern "C" int ParseDate(const char *str, struct tm *tms, int *z); static DwInt32 ymd_to_jdnl(int year, int mon, int day, int julian); static void jdnl_to_ymd(DwInt32 jdn, int *year, int *mon, int *day, int julian); static DwUint32 my_inv_gmtime(struct tm* ptms); const char* const DwDateTime::sClassName = "DwDateTime"; int DwDateTime::sDefaultZone = 0; int DwDateTime::sIsDefaultZoneSet = 0; DwDateTime* (*DwDateTime::sNewDateTime)(const DwString&, DwMessageComponent*) = 0; DwDateTime* DwDateTime::NewDateTime(const DwString& aStr, DwMessageComponent* aParent) { if (sNewDateTime) { return sNewDateTime(aStr, aParent); } else { return new DwDateTime(aStr, aParent); } } void DwDateTime::SetDefaultZone(int aZone) { sDefaultZone = aZone; sIsDefaultZoneSet = 1; } DwDateTime::DwDateTime() { Init(); mIsModified = 1; } DwDateTime::DwDateTime(const DwDateTime& aDateTime) : DwFieldBody(aDateTime) { mYear = aDateTime.mYear; mMonth = aDateTime.mMonth; mDay = aDateTime.mDay; mHour = aDateTime.mHour; mMinute = aDateTime.mMinute; mSecond = aDateTime.mSecond; mZone = aDateTime.mZone; } DwDateTime::DwDateTime(const DwString& aStr, DwMessageComponent* aParent) : DwFieldBody(aStr, aParent) { Init(); mIsModified = 0; } void DwDateTime::Init() { mClassId = kCidDateTime; mClassName = DwDateTime::sClassName; // Check if default time zone is set if (sIsDefaultZoneSet == 0) { // Use calls to gmtime() and localtime() to get the time difference // between local time and UTC (GMT) time. time_t t_now = time((time_t*) 0); #if defined(HAVE_GMTIME_R) struct tm utc; gmtime_r(&t_now, &utc); struct tm local; localtime_r(&t_now, &local); #else struct tm utc = *gmtime(&t_now); struct tm local = *localtime(&t_now); #endif DwUint32 t_local = my_inv_gmtime(&local); DwUint32 t_utc = my_inv_gmtime(&utc); sDefaultZone = (int) (t_local - t_utc)/60; // do not flag this as it would never check the zone again, which // would lead to wrong timediff on the DST/non-DST day change //sIsDefaultZoneSet = 1; } // Set the time zone from the default time zone mZone = sDefaultZone; // Get the current calendar time time_t t_now = time((time_t*) 0); // Set year, month, day, hour, minute, and second from calendar time _FromCalendarTime(t_now); } DwDateTime::~DwDateTime() { } const DwDateTime& DwDateTime::operator = (const DwDateTime& aDateTime) { if (this == &aDateTime) return *this; DwFieldBody::operator = (aDateTime); mYear = aDateTime.mYear; mMonth = aDateTime.mMonth; mDay = aDateTime.mDay; mHour = aDateTime.mHour; mMinute = aDateTime.mMinute; mSecond = aDateTime.mSecond; mZone = aDateTime.mZone; return *this; } DwUint32 DwDateTime::AsUnixTime() const { struct tm tt; tt.tm_year = mYear - 1900; tt.tm_mon = mMonth - 1; tt.tm_mday = mDay; tt.tm_hour = mHour; tt.tm_min = mMinute; tt.tm_sec = mSecond; DwUint32 t = my_inv_gmtime(&tt); t = (t == (DwUint32) -1) ? 0 : t; t -= mZone*60; return t; } void DwDateTime::FromUnixTime(DwUint32 aTime) { _FromUnixTime(aTime); SetModified(); } void DwDateTime::_FromUnixTime(DwUint32 aTime) { time_t t = aTime + mZone*60; #if defined(HAVE_GMTIME_R) struct tm tt; gmtime_r(&t, &tt); #else struct tm tt = *gmtime(&t); #endif mYear = tt.tm_year + 1900; mMonth = tt.tm_mon + 1; mDay = tt.tm_mday; mHour = tt.tm_hour; mMinute = tt.tm_min; mSecond = tt.tm_sec; } void DwDateTime::FromCalendarTime(time_t aTime) { _FromCalendarTime(aTime); SetModified(); } void DwDateTime::_FromCalendarTime(time_t aTime) { // Note: the broken-down time is the only portable representation. // ANSI does not even require that time_t be an integer type; it could // be a double. And, it doesn't even have to be in seconds. // Get the broken-down time. #if defined(HAVE_GMTIME_R) struct tm tms_utc; gmtime_r(&aTime, &tms_utc); #else struct tm tms_utc = *gmtime(&aTime); #endif // Convert to UNIX time, using portable routine DwUint32 t_unix = my_inv_gmtime(&tms_utc); // Set from the UNIX time _FromUnixTime(t_unix); } DwInt32 DwDateTime::DateAsJulianDayNum() const { DwInt32 jdn = ymd_to_jdnl(mYear, mMonth, mDay, -1); return jdn; } void DwDateTime::DateFromJulianDayNum(DwInt32 aJdn) { jdnl_to_ymd(aJdn, &mYear, &mMonth, &mDay, -1); SetModified(); } DwInt32 DwDateTime::TimeAsSecsPastMidnight() const { DwInt32 n = mHour; n *= 60; n += mMinute; n *= 60; n += mSecond; return n; } void DwDateTime::TimeFromSecsPastMidnight(DwInt32 aSecs) { mSecond = (int) (aSecs % 60); aSecs /= 60; mMinute = (int) (aSecs % 60); aSecs /= 60; mHour = (int) (aSecs % 24); SetModified(); } void DwDateTime::Parse() { mIsModified = 0; char buffer[80]; char *str; int mustDelete; // Allocate memory from heap only in rare instances where the buffer // is too small. if (mString.length() >= 80) { mustDelete = 1; str = new char [mString.length()+1]; } else { mustDelete = 0; str = buffer; } strncpy(str, mString.data(), mString.length()); str[mString.length()] = 0; str[79] = 0; struct tm tms; int zone; int err = ParseRfc822Date(str, &tms, &zone); if ( err == -1 ) // try another format err = ParseDate(str, &tms, &zone); if (!err) { mYear = tms.tm_year + 1900; mMonth = tms.tm_mon+1; mDay = tms.tm_mday; mHour = tms.tm_hour; mMinute = tms.tm_min; mSecond = tms.tm_sec; mZone = zone; } else /* if (err) */ { mYear = 1970; mMonth = 1; mDay = 1; mHour = 0; mMinute = 0; mSecond = 0; mZone = 0; } if (mustDelete) { delete[] str; } } void DwDateTime::Assemble() { if (!mIsModified) return; // Find the day of the week DwInt32 jdn = DateAsJulianDayNum(); int dow = (int) ((jdn+1)%7); char sgn = (mZone < 0) ? '-' : '+'; int z = (mZone < 0) ? -mZone : mZone; char buffer[80]; snprintf(buffer, sizeof(buffer), "%s, %d %s %4d %02d:%02d:%02d %c%02d%02d", lWeekDay[dow], mDay, lMonth[(mMonth-1)%12], mYear, mHour, mMinute, mSecond, sgn, z/60%24, z%60); mString = buffer; mIsModified = 0; } DwMessageComponent* DwDateTime::Clone() const { return new DwDateTime(*this); } #if defined (DW_DEBUG_VERSION) void DwDateTime::PrintDebugInfo(std::ostream& aStrm, int /*aDepth*/) const { aStrm << "---------------- Debug info for DwDateTime class ---------------\n"; _PrintDebugInfo(aStrm); } #else void DwDateTime::PrintDebugInfo(std::ostream& , int) const {} #endif // defined (DW_DEBUG_VERSION) #if defined (DW_DEBUG_VERSION) void DwDateTime::_PrintDebugInfo(std::ostream& aStrm) const { DwFieldBody::_PrintDebugInfo(aStrm); aStrm << "Date: " << mYear << '-' << mMonth << '-' << mDay << ' ' << mHour << ':' << mMinute << ':' << mSecond << ' ' << mZone << '\n'; } #else void DwDateTime::_PrintDebugInfo(std::ostream& ) const {} #endif // defined (DW_DEBUG_VERSION) void DwDateTime::CheckInvariants() const { #if defined (DW_DEBUG_VERSION) DwFieldBody::CheckInvariants(); assert(mYear >= 0); assert(1 <= mMonth && mMonth <= 12); assert(1 <= mDay && mDay <= 31); assert(0 <= mHour && mHour < 24); assert(0 <= mMinute && mMinute < 60); assert(0 <= mSecond && mSecond < 60); assert(-12*60 <= mZone && mZone <= 12*60); #endif // defined (DW_DEBUG_VERSION) } #ifdef PAPAL /* Pope Gregory XIII's decree */ #define LASTJULDATE 15821004L /* last day to use Julian calendar */ #define LASTJULJDN 2299160L /* jdn of same */ #else /* British-American usage */ #define LASTJULDATE 17520902L /* last day to use Julian calendar */ #define LASTJULJDN 2361221L /* jdn of same */ #endif static DwInt32 ymd_to_jdnl(int year, int mon, int day, int julian) { DwInt32 jdn; if (julian < 0) /* set Julian flag if auto set */ julian = (((year * 100L) + mon) * 100 + day <= LASTJULDATE); if (year < 0) /* adjust BC year */ year++; if (julian) jdn = 367L * year - 7 * (year + 5001L + (mon - 9) / 7) / 4 + 275 * mon / 9 + day + 1729777L; else jdn = (DwInt32)(day - 32075) + 1461L * (year + 4800L + (mon - 14) / 12) / 4 + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12 - 3 * ((year + 4900L + (mon - 14) / 12) / 100) / 4; return jdn; } static void jdnl_to_ymd(DwInt32 jdn, int *year, int *mon, int *day, int julian) { DwInt32 x, z, m, d, y; DwInt32 daysPer400Years = 146097L; DwInt32 fudgedDaysPer4000Years = 1460970L + 31; if (julian < 0) /* set Julian flag if auto set */ julian = (jdn <= LASTJULJDN); x = jdn + 68569L; if (julian) { x += 38; daysPer400Years = 146100L; fudgedDaysPer4000Years = 1461000L + 1; } z = 4 * x / daysPer400Years; x = x - (daysPer400Years * z + 3) / 4; y = 4000 * (x + 1) / fudgedDaysPer4000Years; x = x - 1461 * y / 4 + 31; m = 80 * x / 2447; d = x - 2447 * m / 80; x = m / 11; m = m + 2 - 12 * x; y = 100 * (z - 49) + y + x; *year = (int)y; *mon = (int)m; *day = (int)d; if (*year <= 0) /* adjust BC years */ (*year)--; } #define JDN_JAN_1_1970 2440588L /* * Converts broken-down time to time in seconds since 1 Jan 1970 00:00. * Pays no attention to time zone or daylight savings time. Another way * to think about this function is that it is the inverse of gmtime(). * One word of caution: the values in the broken down time must be * correct. * * This function is different from mktime() in three ways: * 1. mktime() accepts a broken-down local time and converts it to a scalar * UTC time. Thus, mktime() takes time zone and daylight savings time * information into account when computing the scalar time. (This makes * mktime() highly non-portable). * 2. mktime() will adjust for non-standard values, such as a tm_mday member * that is out of range. This function does no such conversion. * 3. mktime() sets the struct fields tm_yday, tm_wday, and tm_isdst to * their correct values on output. This function does not. */ static DwUint32 my_inv_gmtime(struct tm* ptms) { DwInt32 jdn; DwUint32 t; jdn = ymd_to_jdnl(ptms->tm_year+1900, ptms->tm_mon+1, ptms->tm_mday, -1); t = jdn - JDN_JAN_1_1970; /* days */ t = 24*t + ptms->tm_hour; /* hours */ t = 60*t + ptms->tm_min; /* minutes */ t = 60*t + ptms->tm_sec; /* seconds */ return t; }