is adding m_y, m_m, m_d, considered as cheating?
if not, how to get the value for getYMD()'s param?
mike_2000_17 2,669 21st Century Viking Team Colleague Featured Poster
is adding m_y, m_m, m_d, considered as cheating?
Nothing is considered "cheating", but there are trade-offs. Keeping the year / month / day values within the class will add to the memory used by the objects (instead of just one number for the days since some fixed date in the past (called "epoch")), but you can, technically, respond faster to requests (calls) for the year / month / day (or some derivation of that). However, you also have to understand that this will not allow you to get away with not having to write code for converting between the "day-since-epoch" value and the "year-month-day" values. Whichever way you do it, you still need to provide conversions back and forth. If you store only YMD, then you need to convert when getting or setting the date as day-since-epoch. And vice versa if you only store the day-since-epoch. And if you store both, then you have to guarantee that they are always referring to the same date, meaning that you will have to do conversions all the time, e.g., if you set the day since epoch, then you have to update YMD data to match it, and the same goes for every other function.
So, you will have to write the conversion code. As far as how to do it, well, you have to handle the leap-years and the various number of days in each month, but it's a fairly straight-forward thing to do. If you have problem writing that code, just show us what you have done and where exactly you are having trouble, and then we can help you, we don't provide ready-made code.
rubberman 1,355 Nearly a Posting Virtuoso Featured Poster
Using Julian date+time values for such is preferred. That way, you can use simple value comparisons to see if one date+time is before/after another, and the conversions to standard date+time using local timezone settings is well documented. There are good documents in Wikipedia for that, and may be a number of open source C++ classes you can use. I wrote my own for a major C++ framework back in the 1990's (before there was such in the FOSS community) and it is working fine today, helping run most semiconductor FABs in the world.
Here is a class (.h and .cpp files) that I wrote for my own use. I am releasing it under a GPLv3 License. IE, you can use it freely, but if you modify it you must provide the modified (and original) source to users who request it.
#ifndef AFS_DATE_H
#define AFS_DATE_H
///////////////////////////////////////////////////////////
// Date.h
// Implementation of the Class Date
// Created on: 16-Nov-2006 4:23:49 PM
// Original author: William Boyle
///////////////////////////////////////////////////////////
#include <time.h>
#if defined(LINUX) || defined(CYGWIN)
#include <sys/time.h>
#endif
#include "AFS/BASE/Error.h"
namespace AFS
{
class InvalidDateError : public AfsError
{
public:
InvalidDateError() : AfsError(UtilitySection, InvalidDate) {}
InvalidDateError( const InvalidDateError& cpy ) : AfsError(cpy) {}
virtual ~InvalidDateError() throw (LiveObjectError) {}
InvalidDateError& operator=( const InvalidDateError& rhs )
{ if (this != &rhs) { AfsError::operator=(rhs); } return *this; }
};
/**
* getDateFromString() - if 'ptm' is nil, a local static structure will be returned.
* This can only be used in re-entrant code if a pointer to a local or guarded
* struct tm is passed for 'ptm' by the caller.
*/
struct tm* getDateFromString( const char* str, const char* fmt = "%B %e, %Y", struct tm* ptm = 0 )
throw (InvalidDateError);
/**
* The date class simply stores the date as integer year, month, and day fields.
* It does not yet allow date arithmetic, though it will validate a February 29
* date as being in a leap-year with use of the isValid() method.
*/
class Date : public ReferenceCounter
{
private:
uint32_t m_JulianDate;
public:
/**
* Date constructed from a system time value. Since these are trivially
* created from struct tm objects using the system ::mktime() function,
* we don't need a constructor for that structure type. Use of a
* default argument makes this the default constructor.
*/
Date( time_t ctm = ::time(0) );
/**
* Create date object with specified year, month, and day where
* the date is in the Gregorian calendar from 15 October 1582 on.
* The 32bit unsigned integer value will store dates until sometime
* after the year 5,878,800.
*/
Date(int year, int month, int day);
/**
* Date constructed from string. The 'fmt' argument specifies how the string is to
* be decoded, using the same tokens as is used for output by the asString()
* method and the system strftime() function.
*/
Date(const char* dateStr, const char* fmt = "%Y%m%d");
/**
* Date constructed from a julian date value.
*/
Date( uint32_t jdate );
/**
* Copy constructor.
*/
Date(const Date& cpy);
virtual ~Date() throw (LiveObjectError);
Date& operator=(const Date& rhs);
/**
* Date arithmetic operators. Note there is no int operator+( const Date& )
* method as adding 2 dates is nonsensical. Subtraction (diff) is another
* matter, however.
*/
Date operator+( int rhs ) const;
Date operator-( int rhs ) const;
int operator-( const Date& rhs ) const;
Date& operator+=( int rhs );
Date& operator-=( int rhs );
/**
* Date comparison operators.
*/
bool operator==( const Date& rhs ) const { return m_JulianDate == rhs.m_JulianDate; }
bool operator!=( const Date& rhs ) const { return m_JulianDate != rhs.m_JulianDate; }
bool operator<( const Date& rhs ) const { return m_JulianDate < rhs.m_JulianDate; }
bool operator<=( const Date& rhs ) const { return m_JulianDate <= rhs.m_JulianDate; }
bool operator>( const Date& rhs ) const { return m_JulianDate > rhs.m_JulianDate; }
bool operator>=( const Date& rhs ) const { return m_JulianDate >= rhs.m_JulianDate; }
void setDate( time_t newVal, bool usegmt = false );
void setDate( const char* newVal, const char* fmt );
void setDate( int year, int month, int day );
int year() const;
int month() const;
int day() const;
int dayOfWeek() const;
bool isLeapYear() const;
/**
* getDate() - sets year, month, day, and returns dayOfYear.
*/
int getDate( int& y, int& m, int& d ) const;
uint32_t julian() const { return m_JulianDate; }
/**
* asString() - returns string formatted as using format characters
* specified by strftime. The default returns string formatted as YYYYMMDD.
*/
std::string asString(const char* fmt = "%Y%m%d") const;
/**
* Validate the date. Returns false if not valid.
*/
bool isValid() const;
static const char* nameOfDay( int d, bool abbrev = false );
static const char* nameOfMonth( int m, bool abbrev = false );
static uint32_t daysBetween( const Date& lhs, const Date& rhs );
protected:
};
class Time : public ReferenceCounter
{
private:
/**
* Seconds after midnight - in GMT. To get local time, the output functions will
* require a timezone in hours+fractions before/after GMT.
*/
uint32_t m_Seconds;
uint32_t m_USeconds;
public:
/**
* Time() - default constructor sets time to now.
*/
Time( time_t t = ::time(0), uint32_t usecs = 0, bool usegmt = false);
Time( int hours, int minutes, int seconds, uint32_t usecs = 0 );
Time( const char* timeStr, const char* fmt = "%H:%M:%S" );
Time(const Time& cpy);
virtual ~Time() throw (LiveObjectError);
Time& operator=( const Time& rhs );
Time operator+( int rhs ) const;
Time operator-( int rhs ) const;
int operator-( const Time& rhs ) const;
Time& operator+=( int rhs );
Time& operator-=( int rhs );
/**
* Time comparison operators.
*/
bool operator==( const Time& rhs ) const { return (m_Seconds == rhs.m_Seconds && m_USeconds == rhs.m_USeconds); }
bool operator!=( const Time& rhs ) const { return (m_Seconds != rhs.m_Seconds || m_USeconds != rhs.m_USeconds); }
bool operator<( const Time& rhs ) const { return (m_Seconds < rhs.m_Seconds || (m_Seconds == rhs.m_Seconds && m_USeconds < rhs.m_USeconds)); }
bool operator<=( const Time& rhs ) const { return (operator<(rhs) || operator==(rhs)); }
bool operator>( const Time& rhs ) const { return (m_Seconds > rhs.m_Seconds || (m_Seconds == rhs.m_Seconds && m_USeconds > rhs.m_USeconds)); }
bool operator>=( const Time& rhs ) const { return (operator>(rhs) || operator==(rhs)); }
bool isValid() const;
int hour() const;
int minute() const;
int second() const;
int useconds() const { return m_USeconds; }
/**
* Set time from time_t value with optional gmt.
*/
void setTime( time_t newVal = ::time(0), bool usegmt = false );
void setTime( uint32_t seconds, uint32_t usecs = 0 );
void setTime( int hours, int minutes, int seconds, uint32_t usecs = 0 );
/**
* Set time from provided string and format.
*/
void setTime(const char* newVal, const char* fmt = "%H:%M:%S", uint32_t usecs = 0 );
/**
* setTimeNow() - sets the time value to the current time.
*/
void setTimeNow( bool usegmt = false );
/**
* setUSeconds() - sets microsecond value.
*/
void setUSeconds( uint32_t usecs ) { m_USeconds = usecs; }
/**
* Seconds after midnight - in GMT. To get local time, the output functions will
* require a timezone in hours+fractions before/after GMT.
*/
uint32_t getSeconds() const { return m_Seconds; }
uint32_t getUSeconds() const { return m_USeconds; }
std::string asString(const char* fmt = "%H:%M:%S") const;
/**
* adjust() - adjusts time by specified number of seconds.
* Returns 0 if time has not crossed 24-hour boundary, +days if time
* is in future, -days if in past.
*/
int adjust( int secs );
protected:
/**
* Seconds after midnight - in GMT. To get local time, the output functions will
* require a timezone in hours+fractions before/after GMT.
*/
void setSeconds(uint32_t newVal);
};
class DateTime : public ReferenceCounter
{
private:
Date m_Date;
Time m_Time;
public:
/**
* DateTime() - the default constructor sets the date+time to now.
*/
DateTime();
DateTime(const DateTime& cpy);
virtual ~DateTime() throw (LiveObjectError);
DateTime& operator=( const DateTime& rhs );
/**
* DateTime comparison operators.
*/
bool operator==( const DateTime& rhs ) const { return (m_Date == rhs.m_Date && m_Time == rhs.m_Time); }
bool operator!=( const DateTime& rhs ) const { return (m_Date != rhs.m_Date || m_Time != rhs.m_Time); }
bool operator<( const DateTime& rhs ) const { return (m_Date < rhs.m_Date || (m_Date == rhs.m_Date && m_Time < rhs.m_Time)); }
bool operator<=( const DateTime& rhs ) const { return (operator<(rhs) || operator==(rhs)); }
bool operator>( const DateTime& rhs ) const { return (m_Date > rhs.m_Date || (m_Date == rhs.m_Date && m_Time > rhs.m_Time)); }
bool operator>=( const DateTime& rhs ) const { return (operator>(rhs) || operator==(rhs)); }
Date& getDate() { return m_Date; }
const Date& getDate() const { return m_Date; }
Time& getTime() { return m_Time; }
const Time& getTime() const { return m_Time; }
void setDate(const Date& newVal) { m_Date = newVal; }
void setTime(const Time& newVal) { m_Time = newVal; }
void setDateTime( const Date& newDate, const Time& newTime )
{ m_Date = newDate; m_Time = newTime; }
void setDateTime( const char* str, const char* fmt = "%F %T");
bool isValid() const { return (m_Date.isValid() && m_Time.isValid()); }
bool isLeapYear() const { return m_Date.isLeapYear(); }
std::string asString( const char* fmt = "%F %T" ) const;
};
}
#endif // AFS_DATE_H
///////////////////////////////////////////////////////////
// Date.cpp
// Implementation of the Class Date
// Created on: 16-Nov-2006 4:23:49 PM
// Original author: William Boyle
///////////////////////////////////////////////////////////
#include "AFS/BASE/Date.h"
using namespace AFS;
static const uint32_t s_secondsInDay = (24 * 3600);
static const char* s_unknown = "UNKNOWN";
static const char* s_invalid = "INVALID";
static const char* mabbrev[] = { "Jan", "Feb", "Mar",
"Apr", "May", "Jun",
"Jul", "Aug", "Sep",
"Oct", "Nov", "Dec" };
static const char* mnames[] = { "January", "February", "March",
"April", "May", "June",
"July", "August", "September",
"October", "November", "December" };
static const char* dabbrev[] = { "Sun", "Mon", "Tue", "Wed",
"Thu", "Fri", "Sat" };
static const char* dnames[] = { "Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday" };
/*
* SDateToJulian() - computes the julian decimal date from
* the calendar date.
*
* Output: the julian date as a 32bit unsigned integer value.
*
* This algorithm was derived from the Wikipedia entry "Julian day" at
* http://en.wikipedia.org/wiki/Julian_date and verified
* against results of open source julian date computation package
* "jday" version 2.4 from SourceForge.net. The jday package
* utilizes extensive floating point calculations, whereas the
* algorithm described by the Wikipedia, and as I have implemented
* here, utilize much simpler and more efficient integer computations.
*
* Important Note: this package does not properly deal with dates before
* the start of the Gregorian calendar which began October 15, 1582.
*
* bboyle - 11/21/2006.
*
* Returns julian date on success, UINT_MAX on error.
*/
static uint32_t SDateToJulian( int year, int month, int day )
{
uint32_t retval = UINT_MAX;
if (month > 0 && month <= 12 && year > 1582 && day > 0 && day <= 31)
{
int a = (14 - month)/12;
int y = (year + 4800 - a);
int m = (month + (12*a) - 3);
retval = (uint32_t) (day + (((153*m)+2)/5) + (365*y) + (y/4) - (y/100) + (y/400) - 32045);
}
return retval;
}
/*
* SJulianToDate() - derives the year, month, day, day-of-week,
* and day-of-year from a julian date and stores them in the
* struct tm object provided by the caller.
*
* Note: it will not convert the julian date if its value is UINT_MAX.
*/
static void SJulianToDate( uint32_t jd, int& year, int& month, int& day )
{
year = month = day = INT_MIN;
if (jd != UINT_MAX)
{
uint32_t j = jd + 32044;
uint32_t g = j / 146097;
uint32_t dg = j % 146097;
uint32_t c = (((dg / 36524) + 1) * 3) / 4;
uint32_t dc = dg - (c * 36524);
uint32_t b = dc / 1461;
uint32_t db = dc % 1461;
uint32_t a = (((db / 365) + 1) * 3) / 4;
uint32_t da = db - (a * 365);
uint32_t y = (g * 400) + (c * 100) + (b * 4) + a;
uint32_t m = (((da * 5) + 308) / 153 ) - 2;
uint32_t d = da - (((m + 4) * 153) / 5) + 122;
uint32_t Y = y - 4800 + ((m + 2) / 12);
uint32_t M = ((m + 2) % 12) + 1;
uint32_t D = d + 1;
year = (int)Y;
month = (int)M;
day = (int)D;
}
}
static void SJulianToDate( uint32_t jd, struct tm* ptm )
{
if (ptm != 0)
{
int year, month, day;
SJulianToDate(jd, year, month, day);
if (year != INT_MIN && month != INT_MIN && day != INT_MIN)
{
int dw = (jd % 7) + 1;
uint32_t ny = SDateToJulian(year, 1, 1);
ptm->tm_year = year - 1900;
ptm->tm_mon = month - 1;
ptm->tm_mday = day;
ptm->tm_wday = (dw < 7) ? dw : 0;
// Compute day-of-year.
ptm->tm_yday = (jd - ny);
}
}
}
// ---------------------------------------------------------------------------
// SGetNum() - decodes number from string for up to 'len' characters. It will
// stop if a non-digit number is found before 'len' characters are decoded.
// All numbers are decoded in base 10.
// If there are any errors, the value of INT_MIN will be returned.
// ---------------------------------------------------------------------------
static int SGetNum(const char* str, size_t len, int minval, int maxval, int& adjust)
{
int retval = INT_MIN;
int newadj = adjust;
char buffer[len + 1];
size_t decoded = 0;
size_t copied = 0;
bool havesign = false;
::memset((void*)buffer, 0, len + 1);
// 1. Skip leading white-space.
while (decoded < len && str[newadj] != 0 && ::isblank(str[newadj]) != 0)
{
++newadj;
++decoded;
}
// 2. Skip leading zeros.
while (decoded < len && str[newadj] == '0')
{
++newadj;
++decoded;
}
// 3. Check for sign.
if (decoded < len && (str[newadj] == '-' || str[newadj] == '+'))
{
buffer[0] = str[newadj];
copied = 1;
++newadj;
++decoded;
havesign = true;
}
// 4. Copy remaining digits into buffer.
while (decoded < len && ::isdigit(str[newadj]) != 0)
{
buffer[copied++] = str[newadj];
++newadj;
++decoded;
}
if (copied > 1 || (copied == 1 && havesign == false))
{
long val = ::strtol(buffer, 0, 10);
if (val >= (long)minval && val <= (long)maxval)
{
retval = (int)val;
adjust = newadj;
}
}
return retval;
}
// ---------------------------------------------------------------------------
// SGetMonth - Months start with January (0). Returns -1 and no adjustments
// if month is not found.
// ---------------------------------------------------------------------------
static int SGetMonth(const char* str, bool abbrev, int& adjust )
{
if (abbrev)
{
for (int i = 0; i < 12; i++)
{
int diff = ::strncasecmp(str + adjust, mabbrev[i], 3);
if (!diff)
{
adjust += 3;
return i + 1;
}
}
}
else
{
static size_t mlen[] = { 7, 8, 5, 5, 3, 4, 4, 6, 9, 7, 8, 8 };
for (int i = 0; i < 12; i++)
{
int diff = ::strncasecmp(str + adjust, mnames[i], mlen[i]);
if (!diff)
{
adjust += mlen[i];
return i + 1;
}
}
}
return INT_MIN;
}
// ---------------------------------------------------------------------------
// SGetDayOfWeek() - Week starts on Sunday (0). Returns -1 if not found.
// ---------------------------------------------------------------------------
static int SGetDayOfWeek(const char* str, bool abbrev, int& adjust)
{
if (abbrev)
{
for (int i = 0; i < 7; i++)
{
int diff = ::strncasecmp(str + adjust, dabbrev[i], 3);
if (!diff)
{
adjust += 3;
return i;
}
}
}
else
{
static size_t dlen[] = { 6, 6, 7, 9, 8, 6, 8 };
for (int i = 0; i < 7; i++)
{
int diff = ::strncasecmp(str + adjust, dnames[i], dlen[i]);
if (!diff)
{
adjust += dlen[i];
return i;
}
}
}
return INT_MIN;
}
// Skip past over current token and white-space delimiter to next token.
static void SSkipToNext(const char* str, int& adjust)
{
while (str[adjust] != '\0' || ::isspace(str[adjust]) != 0)
{
++adjust;
}
while (str[adjust] != '\0' || ::isspace(str[adjust]) == 0)
{
++adjust;
}
}
struct SDateStruct {
int hours;
int minutes;
int seconds;
int msecs;
int day;
int month;
int year;
int century;
int dayofweek;
bool is12hr;
bool is2yr;
bool ampm ;
bool pm ;
int utc[3];
SDateStruct()
{
hours = -1;
minutes = -1;
seconds = -1;
msecs = -1;
day = -1;
month = -1;
year = -1;
century = -1;
dayofweek = -1;
is12hr = false;
is2yr = false;
ampm = false;
pm = false;
utc[0] = 1;
utc[1] = utc[2] = 0;
}
};
// ---------------------------------------------------------------------------
// SGetDateFromString() - static function that will set the date/time pointer
// to the values found in the string. It will not alter what is already in
// the passed struct SDateStruct object that it does not find in the format
// specifier.
// ---------------------------------------------------------------------------
static void SGetDateFromString(const char* str, const char* fmt, SDateStruct& dtm, int& strOff, int& fmtOff )
throw (InvalidDateError)
{
// -----------------------------------------------------------
// Set from date string using format specifier to decode it.
// The algorithm is:
// 1. Skip over equal number of chars in date/fmt until
// % is found. If found, check next character for
// year/month/day specifier.
// 2. Get part of date found and set flag so we won't
// process that part again. Skip to end of date
// component in the date string and the format
// specifier in the format string.
// 3. Continue at 1 to get next part of the date.
//
// If there are duplicate items, the last one is the value saved.
// ------------------------------------------------------------
bool done = false;
// Walk thru string and format specifier. Each character in the format string
// must either be equal to the corresponding character in the source string,
// or it must be a valid format specifier. If not, an InvalidDateError exception
// will be thrown. The only exceptions to this will be blank spaces, which will be
// skipped over for both format and source strings.
while (fmt[fmtOff] != '\0' && str[strOff] != '\0')
{
int num = 0;
bool abbrev = false;
// Skip over spaces.
while (str[strOff] == ' ')
{
++strOff;
}
while (fmt[fmtOff] == ' ')
{
++fmtOff;
}
if (fmt[fmtOff] != '\0' && str[strOff] != '\0')
{
if (fmt[fmtOff] != '%')
{
// Check if end of fmt string.
if (fmt[fmtOff] != str[strOff])
{
// Error.
InvalidDateError err;
err.setMessage("Invalid date string - format and source strings disagree: format (%s) @ position %u, source (%s) @ position %u",
fmt, fmtOff,
str, strOff);
throw err;
}
++strOff;
}
else
{
static int maxC = INT_MAX/100;
int bogus = 0;
const char* bogusStr = 0;
++fmtOff;
switch( fmt[fmtOff] )
{
case 'a': // 3-letter day of week.
abbrev = true;
case 'A': // Full day of week.
dtm.dayofweek = SGetDayOfWeek(str, abbrev, strOff);
if (dtm.dayofweek == INT_MIN)
{
// Invalid day-of-week.
InvalidDateError err;
err.setMessage("Invalid day-of-week in string @ %d: %s", strOff, str);
throw err;
}
break;
case 'b':
case 'h':
abbrev = true;
case 'B':
dtm.month = SGetMonth(str, abbrev, strOff);
if (dtm.month == INT_MIN)
{
// Invalid month.
InvalidDateError err;
err.setMessage("Invalid month in string @ %d: %s", strOff, str);
throw err;
}
break;
case 'c': // Get full date & time.
{
int foff = 0;
SGetDateFromString(str, "%a %b %e %H:%M:%S %Y", dtm, strOff, foff);
}
break;
case 'C': // Century
dtm.century = SGetNum(str, 2, -43, maxC, strOff);
if (dtm.century == INT_MIN)
{
// Invalid century
InvalidDateError err;
err.setMessage("Invalid century in string @ %d: %s", strOff, str);
throw err;
}
break;
case 'D': // Get date in format of "%m/%d/%y"
case 'x':
{
int foff = 0;
SGetDateFromString(str, "%m/%d/%y", dtm, strOff, foff);
}
break;
case 'd': // Day of month from 01 to 31 (optional leading 0 for D < 10)
case 'e': // Day of month from 1 to 31 (optional leading space for D < 10)
dtm.day = SGetNum(str, 2, 1, 31, strOff);
if (dtm.day == INT_MIN)
{
// Invalid day of month.
InvalidDateError err;
err.setMessage("Invalid day of month in string @ %d: %s", strOff, str);
throw err;
}
break;
case 'F': // ISO 8601:2000 format "%Y-%m-%d"
{
int foff = 0;
SGetDateFromString(str, "%Y-%m-%d", dtm, strOff, foff);
}
break;
case 'I': // Hour on a 12-hour clock, formatted 01 to 12.
case 'l': // Hour on 12-hour clock, formatted 1 to 12.
dtm.is12hr = true;
case 'H': // Hour on a 24-hour clock, formatted 00 to 23.
case 'k': // Hour on 24-hour clock, formatted 0 to 23.
dtm.hours = SGetNum(str, 2, dtm.is12hr ? 1 : 0, dtm.is12hr ? 12 : 23, strOff);
if (dtm.hours == INT_MIN)
{
// Invalid hour.
InvalidDateError err;
err.setMessage("Invalid hour of day in string @ %d: %s", strOff, str);
throw err;
}
break;
case 'm': // Month number, formatted 01 to 12.
dtm.month = SGetNum(str, 2, 1, 12, strOff);
if (dtm.month == INT_MIN)
{
// Invalid month.
InvalidDateError err;
err.setMessage("Invalid month in string @ %d: %s", strOff, str);
throw err;
}
break;
case 'M': // The minuted, formatted 00 to 59.
dtm.minutes = SGetNum(str, 2, 0, 59, strOff);
if (dtm.minutes == INT_MIN)
{
// Invalid minute.
InvalidDateError err;
err.setMessage("Invalid minute in string @ %d: %s", strOff, str);
throw err;
}
break;
case 'p': // AM or PM.
dtm.ampm = true;
if (::strncasecmp(str, "pm", 2) == 0)
{
dtm.pm = true;
}
else if (::strncasecmp(str, "am", 2) == 0)
{
dtm.pm = false;
}
else
{
// Invalid AM/PM in string.
InvalidDateError err;
err.setMessage("Invalid AM/PM in string @ %d: %s", strOff, str);
throw err;
}
strOff += 2;
break;
case 'r': // 12-hour time to the second as in "%I:%M:%S %p".
{
int foff = 0;
SGetDateFromString(str, "%I:%M:%S %p", dtm, strOff, foff);
}
break;
case 'R': // 12-hour time as in "%H:%M".
{
int foff = 0;
SGetDateFromString(str, "%H:%M", dtm, strOff, foff);
}
break;
case 'S': // The second formatted from 00 to 60 (60 == leap-second).
dtm.seconds = SGetNum(str, 2, 0, 60, strOff);
if (dtm.seconds == INT_MIN)
{
// Invalid seconds.
InvalidDateError err;
err.setMessage("Invalid seconds in string @ %d: %s", strOff, str);
throw err;
}
break;
case 's': // The number of 1/100's second formatted from 00 to 99
dtm.msecs = SGetNum(str, 2, 0, 99, strOff);
if (dtm.msecs == INT_MIN)
{
// Invalid seconds.
InvalidDateError err;
err.setMessage("Invalid 1/100 seconds in string @ %d: %s", strOff, str);
throw err;
}
dtm.msecs *= 10; // Adjust from 100's second to millisecond
break;
case 'T': // 24-hour time to the second, as in "%H:%M:%S".
case 'X': // Full time of day 24-hour formatted as "%H:%M:%S".
{
int foff = 0;
SGetDateFromString(str, "%H:%M:%S", dtm, strOff, foff);
}
break;
case 'y': // 2-digit year formatted 00 to 99
dtm.year = SGetNum(str, 2, 0, 99, strOff);
if (dtm.year == INT_MIN)
{
// Invalid 2-digit year.
InvalidDateError err;
err.setMessage("Invalid 2-digit year in string @ %d: %s", strOff, str);
throw err;
}
dtm.is2yr = true;
break;
case 'Y': // 4-digit year formatted YYYY.
dtm.year = SGetNum(str, 4, 0, 9999, strOff);
if (dtm.year == INT_MIN)
{
// Invalid 4-digit year.
InvalidDateError err;
err.setMessage("Invalid 4-digit year in string @ %d: %s", strOff, str);
throw err;
}
break;
case 'z': // Offset from UTC as +-HHMM
if (str[strOff] == '-')
{
dtm.utc[0] = -1;
}
else if (str[strOff] == '+')
{
dtm.utc[0] = 1;
}
else
{
// Invalid UTC format.
InvalidDateError err;
err.setMessage("Invalid timezone format - missing leading '+' or '-' @ %d: %s", strOff, str);
throw err;
}
++strOff;
dtm.utc[1] = SGetNum(str, 2, 0, 24, strOff);
if (dtm.utc[1] == INT_MIN)
{
// Invalid UTC format.
InvalidDateError err;
err.setMessage("Invalid timezone format (hours) in string @ %d: %s", strOff, str);
throw err;
}
dtm.utc[2] = SGetNum(str, 2, 0, 59, strOff);
if (dtm.utc[2] == INT_MIN)
{
// Invalid UTC format.
InvalidDateError err;
err.setMessage("Invalid timezone format (minutes) in string @ %d: %s", strOff, str);
throw err;
}
break;
case 't': // TAB character - skip.
if (str[strOff] != '\t')
{
// No tab found.
InvalidDateError err;
err.setMessage("No tab found as specified in string @ %d: %s", strOff, str);
throw err;
}
++strOff;
break;
case 'u': // Weekday as a number (monday = 1, sunday = 7).
bogusStr = "weekday (1-7)";
bogus = SGetNum(str, 1, 1, 7, strOff);
if (bogus != INT_MIN)
{
dtm.dayofweek = (bogus == 7) ? 0 : bogus;
}
break;
case 'w': // Weekday as a number (sunday = 0, saturday = 6).
bogusStr = "weekday (0-6)";
bogus = SGetNum(str, 1, 0, 6, strOff);
if (bogus != INT_MIN)
{
dtm.dayofweek = bogus;
}
break;
case '%':
if (str[strOff] != '%')
{
// No % found.
InvalidDateError err;
err.setMessage("No '%' found as specified in string @ %d: %s", strOff, str);
throw err;
}
++strOff;
break;
case 'n': // New-line character - skip.
// Note, %n will adjust 2 for MSWin systems (CR/LF) instead of 1.
// So, we should really just look for an CR/LF pair. Anything
// else will be treated as a single '\n' character.
if (str[strOff] != '\n')
{
// No new-line found.
InvalidDateError err;
err.setMessage("No new-line found as specified in string @ %d: %s", strOff, str);
throw err;
}
++strOff;
break;
// The following are other format specifiers, with the equivalent
// position adjustments to the provided string specified. These
// are not supported, except to ignore.
case 'g': // 2-digit week-based year - skip.
bogusStr = "week-based year (2d)";
bogus = SGetNum(str, 2, 0, 99, strOff);
break;
case 'G': // 4-digit week-based year - skip.
bogusStr = "week-based year (4d)";
bogus = SGetNum(str, 4, 0, 9999, strOff);
break;
case 'j': // Count of days in the year - skip.
bogusStr = "days (3d) in year";
bogus = SGetNum(str, 3, 0, 366, strOff);
break;
case 'U': // Week number - formatted 00 to 53 - skip.
bogusStr = "week number (00-53)";
bogus = SGetNum(str, 2, 0, 53, strOff);
break;
case 'V': // Week number - formatted 01 to 53 - skip.
bogusStr = "week number (01-53)";
bogus = SGetNum(str, 2, 1, 53, strOff);
break;
case 'Z': // Timezone name.
SSkipToNext(str,strOff);
break;
default: // Anything else - throw invalid date error.
{
// Error.
InvalidDateError err;
err.setMessage("Invalid date format specifier '%%%c' @ %d: %s", fmt[fmtOff], fmtOff, fmt);
throw err;
}
break;
}
if (bogus == INT_MIN)
{
// Invalid unsupported numeric value.
InvalidDateError err;
err.setMessage("Invalid %s in string @ %d: %s", bogusStr, strOff, str);
throw err;
}
}
fmtOff++;
}
}
}
/**
* getDateFromString() - sets struct tm object to date and/or time from string and
* format specifier.
*/
struct tm* AFS::getDateFromString( const char* str, const char* fmt, struct tm* ptm ) throw (InvalidDateError)
{
static struct tm thisTime;
if (!ptm)
{
ptm = &thisTime;
::memset((void*)ptm, 0, sizeof(struct tm));
}
if (str == 0 || *str == 0)
{
// Nothing passed - use current local time.
time_t now = ::time(0);
return localtime_r(&now, ptm);
}
// Check for missing format specifier. Set to default if missing.
if (fmt == 0 || *fmt == 0)
{
// Set from default format to represent "Month DD, YYYY"
// which would be "%B %e, %Y"
fmt = "%B %e, %Y";
}
int strOff = 0, fmtOff = 0;
SDateStruct dtm;
SGetDateFromString(str, fmt, dtm, strOff, fmtOff);
if (dtm.hours != -1)
{
if (dtm.is12hr)
{
if (!dtm.ampm)
{
// Missing AM/PM for 12hour clock.
InvalidDateError err;
err.setMessage("Missing AM/PM specifier for 12 hour clock in format string: %s", fmt);
throw err;
}
if (dtm.pm)
{
ptm->tm_hour = (dtm.hours < 12) ? dtm.hours + 12 : 12;
}
else
{
ptm->tm_hour = (dtm.hours < 12) ? dtm.hours : 0;
}
}
else
{
ptm->tm_hour = dtm.hours;
}
}
if (dtm.minutes != -1)
{
ptm->tm_min = dtm.minutes;
}
if (dtm.seconds != -1)
{
ptm->tm_sec = dtm.seconds;
}
if (dtm.day != -1)
{
ptm->tm_mday = dtm.day;
}
if (dtm.month != -1)
{
ptm->tm_mon = dtm.month - 1;
}
if (dtm.century != -1)
{
if (dtm.century < 14)
{
// Invalid century.
InvalidDateError err;
err.setMessage("Invalid century (%d) in string; %s", dtm.century, str);
throw err;
}
ptm->tm_year = (dtm.century - 19) * 100;
}
if (dtm.year != -1)
{
if (!dtm.is2yr)
{
if (dtm.century != -1)
{
// Have 4-digit year and century.
InvalidDateError err;
err.setMessage("Date format has both century and 4-digit year specifiers: %s", fmt);
throw err;
}
ptm->tm_year = dtm.year - 1900;
}
else if (dtm.century == -1)
{
// Use current century 21.
ptm->tm_year = 100 + dtm.year;
}
else
{
// Already have century. Just add 2-digit year value.
ptm->tm_year += dtm.year;
}
}
if (dtm.dayofweek != -1)
{
ptm->tm_wday = dtm.dayofweek;
}
return ptm;
}
/**
* Default constructor creates the object with today's date unless
* specified otherwise.
*/
Date::Date( time_t ctm )
: m_JulianDate(UINT_MAX)
{
// Set date from system time here.
setDate( ctm );
}
/**
* Create date object with specified year, month, and day.
*/
Date::Date(int year, int month, int day)
: m_JulianDate(SDateToJulian(year, month, day))
{
}
/**
* Date constructed from string. The 'fmt' argument specifies how the string is to
* be decoded, using the same tokens as is used for output by the asString()
* method and the system strftime() function.
*/
Date::Date(const char* dateStr, const char* fmt)
: m_JulianDate(UINT_MAX)
{
setDate(dateStr, fmt);
}
Date::Date( uint32_t jdate )
: m_JulianDate(jdate)
{
}
/**
* Default constructor creates the object with today's date.
*/
Date::Date(const Date& cpy)
: m_JulianDate(cpy.m_JulianDate)
{
}
Date::~Date() throw (LiveObjectError)
{
}
Date& Date::operator=(const Date& rhs)
{
if (this != &rhs)
{
m_JulianDate = rhs.m_JulianDate;
}
return *this;
}
void Date::setDate( time_t newVal, bool usegmt )
{
struct tm theTime;
struct tm* ptm = 0;
::memset((void*)&theTime, 0, sizeof(theTime));
if (usegmt)
{
ptm = gmtime_r(&newVal, &theTime);
}
else
{
ptm = localtime_r(&newVal, &theTime);
}
if (ptm)
{
m_JulianDate = SDateToJulian(ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday);
}
}
void Date::setDate(const char* newVal, const char* fmt)
{
struct tm theTime;
struct tm* ptm = 0;
::memset((void*)&theTime, 0, sizeof(theTime));
try
{
ptm = getDateFromString(newVal, fmt, &theTime);
if (ptm)
{
m_JulianDate = SDateToJulian(ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday);
}
}
catch (InvalidDateError err)
{
m_JulianDate = UINT_MAX;
TRACE_ERROR(err, Date, setDate);
}
}
void Date::setDate( int year, int month, int day )
{
m_JulianDate = SDateToJulian(year, month, day);
}
/**
* Returns string formatted as using format characters specified by strftime. The
* default returns string formatted as YYYYMMDD.
*/
std::string Date::asString(const char* fmt) const
{
std::string retval( "INVALID_DATE" );
if (isValid())
{
size_t maxlen = PATH_MAX + ((fmt) ? ::strlen(fmt) : 0);
char buffer[maxlen + 1];
struct tm theDate;
::memset((void*)&theDate, 0, sizeof(theDate));
SJulianToDate(m_JulianDate, &theDate);
if ( ::strftime(buffer, maxlen, fmt, &theDate) > 0 )
{
retval = (const char*)buffer;
}
}
return retval;
}
/**
* Validate the date. Returns false if not valid.
*/
bool Date::isValid() const
{
return (m_JulianDate != UINT_MAX);
}
int Date::year() const
{
int year, month, day;
SJulianToDate(m_JulianDate, year, month, day);
return year;
}
int Date::month() const
{
int year, month, day;
SJulianToDate(m_JulianDate, year, month, day);
return month;
}
int Date::day() const
{
int year, month, day;
SJulianToDate(m_JulianDate, year, month, day);
return day;
}
int Date::dayOfWeek() const
{
int retval = INT_MIN;
if (isValid())
{
int dw = (m_JulianDate % 7) + 1;
retval = (dw == 7) ? 0 : dw;
}
return retval;
}
int Date::getDate( int& y, int& m, int& d ) const
{
SJulianToDate(m_JulianDate, y, m, d);
return (int)(m_JulianDate - SDateToJulian(y, 1, 1));
}
bool Date::isLeapYear() const
{
bool retval = false;
if (isValid())
{
int yr = year();
retval = ((yr % 4) == 0 && ((yr % 100) != 0 || (yr % 400) == 0));
}
return retval;
}
Date Date::operator+( int rhs ) const
{
Date retval = *this;
retval.m_JulianDate += rhs;
return retval;
}
Date Date::operator-( int rhs ) const
{
Date retval = *this;
retval.m_JulianDate -= rhs;
return retval;
}
int Date::operator-( const Date& rhs ) const
{
return m_JulianDate - rhs.m_JulianDate;
}
Date& Date::operator+=( int rhs )
{
m_JulianDate += rhs;
return *this;
}
Date& Date::operator-=( int rhs )
{
m_JulianDate -= rhs;
return *this;
}
const char* Date::nameOfDay( int d, bool abbrev )
{
const char* retval = s_unknown;
if (d >= 0 && d <= 7)
{
if (abbrev)
{
retval = dabbrev[d];
}
else
{
retval = dnames[d];
}
}
return retval;
}
const char* Date::nameOfMonth( int m, bool abbrev )
{
const char* retval = s_unknown;
if (m > 0 && m <= 12)
{
if (abbrev)
{
retval = mabbrev[m-1];
}
else
{
retval = mnames[m-1];
}
}
return retval;
}
uint32_t Date::daysBetween( const Date& lhs, const Date& rhs )
{
return (uint32_t) ::abs( lhs - rhs );
}
// -------------------------------- Time Class --------------------------
Time::Time( time_t t, uint32_t usecs, bool usegmt )
: m_Seconds(UINT_MAX), m_USeconds(UINT_MAX)
{
// Get system time and set.
setTime(t, usegmt);
if (m_Seconds != UINT_MAX)
{
m_USeconds = usecs;
}
}
Time::Time( int hours, int minutes, int seconds, uint32_t usecs )
: m_Seconds((hours * 3600) + (minutes * 60) + seconds),
m_USeconds(usecs)
{
}
Time::Time( const char* timeStr, const char* fmt )
: m_Seconds(UINT_MAX), m_USeconds(UINT_MAX)
{
setTime(timeStr, fmt);
}
Time::Time(const Time& cpy)
: m_Seconds(cpy.m_Seconds), m_USeconds(cpy.m_USeconds)
{
}
Time::~Time() throw (LiveObjectError)
{
}
Time& Time::operator=(const Time& rhs)
{
if (this != &rhs)
{
m_Seconds = rhs.m_Seconds;
m_USeconds = rhs.m_USeconds;
}
return *this;
}
int Time::adjust( int secs )
{
int retval = 0;
if (secs < 0)
{
uint32_t asecs = (uint32_t) ::abs(secs);
if (asecs > m_Seconds)
{
// We are in previous calendar days. Compute negative days.
retval = asecs / s_secondsInDay;
asecs %= s_secondsInDay;
// See if we have another day to adjust.
if (asecs > m_Seconds)
{
++retval;
m_Seconds += (s_secondsInDay - asecs);
}
}
if (!retval)
{
// No change in days.
m_Seconds -= asecs;
}
}
else
{
m_Seconds += secs;
retval = m_Seconds / s_secondsInDay;
if (retval)
{
m_Seconds %= s_secondsInDay;
}
}
return retval;
}
Time Time::operator+( int rhs ) const
{
Time retval = *this;
retval.adjust(rhs);
return retval;
}
Time Time::operator-( int rhs ) const
{
Time retval = *this;
retval.adjust(-1 * rhs);
return retval;
}
int Time::operator-( const Time& rhs ) const
{
return m_Seconds - rhs.m_Seconds;
}
Time& Time::operator+=( int rhs )
{
adjust(rhs);
return *this;
}
Time& Time::operator-=( int rhs )
{
adjust( -1 * rhs );
return *this;
}
void Time::setTime( time_t newVal, bool usegmt )
{
struct tm theTime;
struct tm* ptm = 0;
::memset((void*)&theTime, 0, sizeof(theTime));
if (usegmt)
{
ptm = gmtime_r(&newVal, &theTime);
}
else
{
ptm = localtime_r(&newVal, &theTime);
}
if (ptm)
{
m_Seconds = (ptm->tm_hour * 3600) + (ptm->tm_min * 60) + ptm->tm_sec;
m_USeconds = 0;
}
}
void Time::setTime( uint32_t seconds, uint32_t usecs )
{
m_Seconds = seconds;
m_USeconds = usecs;
}
void Time::setTime( int hours, int minutes, int seconds, uint32_t usecs )
{
if (hours >= 0 && hours < 24 && minutes >= 0 && minutes < 60 && seconds >= 0 && seconds <= 60)
{
m_Seconds = (hours * 3600) + (minutes * 16) + seconds;
}
m_USeconds = (usecs < 1000000) ? usecs : 0;
}
void Time::setTime(const char* newVal, const char* fmt, uint32_t usecs)
{
struct tm theTime;
struct tm* ptm = 0;
::memset((void*)&theTime, 0, sizeof(theTime));
try
{
ptm = getDateFromString(newVal, fmt, &theTime);
if (ptm)
{
m_Seconds = (ptm->tm_hour * 3600) + (ptm->tm_min * 60) + ptm->tm_sec;
m_USeconds = usecs;
}
}
catch (InvalidDateError err)
{
TRACE_ERROR(err, Time, setTime);
m_Seconds = m_USeconds = UINT_MAX;
}
}
void Time::setTimeNow( bool usegmt )
{
struct timeval now;
now.tv_sec = now.tv_usec = 0;
if (gettimeofday(&now, 0) == 0)
{
setTime(now.tv_sec, usegmt);
m_USeconds = now.tv_usec;
}
else
{
// Error
int errnum = errno;
InvalidDateError err;
err.setMessage("unable to get current time - errno %d: %s", errnum, ::strerror(errnum));
TRACE_ERROR(err, Time, setTimeNow);
m_Seconds = m_USeconds = UINT_MAX;
}
}
bool Time::isValid() const
{
return ( m_Seconds <= s_secondsInDay && m_USeconds < 1000000 );
}
std::string Time::asString( const char* fmt ) const
{
std::string retval( "INVALID_TIME" );
if (isValid())
{
uint32_t csecs = m_Seconds;
size_t maxlen = PATH_MAX + ((fmt) ? ::strlen(fmt) : 0);
char buffer[maxlen + 1];
struct tm theTime;
::memset((void*)&theTime, 0, sizeof(theTime));
theTime.tm_hour = (csecs / 3600);
theTime.tm_min = ((csecs % 3600) / 60);
theTime.tm_sec = csecs % 60;
if ( ::strftime(buffer, maxlen, fmt, &theTime) > 0 )
{
retval = (const char*)buffer;
}
}
return retval;
}
int Time::hour() const
{
return isValid() ? (m_Seconds / 3600) : INT_MIN;
}
int Time::minute() const
{
return isValid() ? ((m_Seconds % 3600) / 60) : INT_MIN;
}
int Time::second() const
{
return isValid() ? (m_Seconds % 60) : INT_MIN;
}
// ------------------------------- DateTime Class ------------------------------
DateTime::DateTime()
{
}
DateTime::DateTime(const DateTime& cpy)
: m_Date(cpy.m_Date), m_Time(cpy.m_Time)
{
}
DateTime::~DateTime() throw (LiveObjectError)
{
}
DateTime& DateTime::operator=( const DateTime& rhs )
{
if (this != &rhs)
{
m_Date = rhs.m_Date;
m_Time = rhs.m_Time;
}
return *this;
}
void DateTime::setDateTime( const char* str, const char* fmt )
{
struct tm theTime;
struct tm* ptm = 0;
::memset((void*)&theTime, 0, sizeof(theTime));
try
{
ptm = getDateFromString(str, fmt, &theTime);
if (ptm)
{
m_Date.setDate(ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday);
m_Time.setTime(ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
}
}
catch (InvalidDateError err)
{
TRACE_ERROR(err, DateTime, setDateTime);
}
}
std::string DateTime::asString( const char* fmt ) const
{
std::string retval( "INVALID_DATE INVALID_TIME" );
if (isValid())
{
size_t maxlen = PATH_MAX + ((fmt) ? ::strlen(fmt) : 0);
char buffer[maxlen + 1];
struct tm theTime;
struct tm* ptm = 0;
::memset((void*)&theTime, 0, sizeof(theTime));
theTime.tm_year = m_Date.year() - 1900;
theTime.tm_mon = m_Date.month() - 1;
theTime.tm_mday = m_Date.day();
theTime.tm_hour = m_Time.hour();
theTime.tm_min = m_Time.minute();
theTime.tm_sec = m_Time.second();
time_t nt = ::mktime(&theTime);
::memset((void*)&theTime, 0, sizeof(theTime));
ptm = localtime_r(&nt, &theTime);
if (ptm)
{
if ( ::strftime(buffer, maxlen, fmt, ptm) > 0 )
{
retval = (const char*)buffer;
}
}
}
return retval;
}
Do note that this is Linux code, and it may not be suitable for some Windows environments. Also note that I am not dealing with time, just dates. Julian values as float/double values can handle dates + time as well, but I didn't need to do that for my uses. It did simplify some stuff.
Edited by rubberman
rubberman 1,355 Nearly a Posting Virtuoso Featured Poster
Also, there are some sub-classes such as the reference counter classes that stuff is derived from, which you will need to remove since that is used by the reference-counting garbage collector which I am not releasing at this time - it is a field I am still doing active research on. It allows programs to use appropriate "smart pointers" to run for years at a time with no deletes. We had 10 million lines of code written with this tech that I invented that had zero deletes, and no memory leaks, that our customers (Samsung, Philips, Intel, Seagate, et al) used for their factories which they would only shut down 1 day a year for preventative maintenance. If the software failed for ANY reason, the costs to them was in excess of $10 million in profits per hour of downtime. Let's just say they would not be happy if that happened!
Vasthor commented: well, that's cool! +2
Vasthor 0 Junior Poster in Training
nah, don't worry about the code & algorithm, I just want to know if the design is valid. and if it is, I need a starting point as rubberman suggested.
Be a part of the DaniWeb community
We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.