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.
476 lines
12 KiB
476 lines
12 KiB
//=============================================================================
|
|
// File: datetime.cpp
|
|
// Contents: Definitions for DwDateTime
|
|
// Maintainer: Doug Sauder <dwsauder@fwb.gulf.net>
|
|
// 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 CONSETQUENTIAL 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 <mimelib/config.h>
|
|
#include <mimelib/debug.h>
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <mimelib/string.h>
|
|
#include <mimelib/datetime.h>
|
|
#include <mimelib/token.h>
|
|
#include <time.h>
|
|
|
|
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;
|
|
}
|
|
|
|
|