Start New Discussion within our Software Development Community

'Nuff said.

#include <stdexcept>
#include <ctime>

namespace jsw {
    class date_time {
    public:
        static date_time MIN_DATE;
        static date_time MAX_DATE;

        static bool is_leap_year(int year);
        static int days_in_month(int year, int month);
    public:
        date_time(
            int year = 1, int month = 1, int day = 1,
            int hour = 0, int minute = 0, int second = 0);
        date_time(std::time_t init, bool local = true);
        date_time(const std::tm& init);

        std::tm to_tm() const;

        int year() const;
        int month() const;
        int day() const;
        int weekday() const;
        int hour() const;
        int minute() const;
        int second() const;

        bool add_years(int years);
        bool add_months(int months);
        bool add_days(int days);
        bool add_hours(int hours);
        bool add_minutes(int minutes);
        bool add_seconds(int seconds);

        int compare(const date_time& rhs);
    private:
        enum {
            TICKS_PER_DAY = 86400000L,
            TICKS_PER_HOUR = 3600000L,
            TICKS_PER_MINUTE = 60000L,
            TICKS_PER_SECOND = 1000L,
            DAYS_IN_400 = 146097L,
            DAYS_IN_100 = 36524L,
            DAYS_IN_4 = 1461L,
        };

        static int month_days[];
        static int month_days_leap[];

        static long long date_to_ticks(
            int year, int month, int day, 
            int hour, int minute, int second);
        static int date_part(long long ticks, int part);
    private:
        long long _ticks;

        bool add_ticks(long long ticks);
    };
}

namespace jsw {
    /**
        Public static interface
    */
    date_time date_time::MIN_DATE(1, 1, 1);
    date_time date_time::MAX_DATE(9999, 12, 31);

    bool date_time::is_leap_year(int year)
    {
        if (year < 1 || year > 9999)
            throw std::out_of_range("Invalid year");

        return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
    }

    int date_time::days_in_month(int year, int month)
    {
        int *dim = !is_leap_year(year) ? month_days : month_days_leap;

        return dim[month] - dim[month - 1];
    }

    /**
        Public instance interface
    */
    date_time::date_time(
        int year, int month, int day,
        int hour, int minute, int second)
    {
        _ticks = date_to_ticks(year, month, day, hour, minute, second);

        if (this != &MIN_DATE && this != &MAX_DATE) {
            if (_ticks < MIN_DATE._ticks || _ticks > MAX_DATE._ticks)
                throw std::invalid_argument("Unable to create date_time");
        }
    }

    date_time::date_time(std::time_t init, bool local)
    {
        std::tm *cdate;

        if (local)
            cdate = std::localtime(&init);
        else
            cdate = std::gmtime(&init);

        if (cdate == 0)
            throw std::invalid_argument("Unable to convert time_t to date_time");

        _ticks = date_to_ticks(
            cdate->tm_year + 1900, cdate->tm_mon + 1, cdate->tm_mday, 
            cdate->tm_hour, cdate->tm_min, cdate->tm_sec);
    }

    date_time::date_time(const std::tm& init)
    {
        _ticks = date_to_ticks(
            init.tm_year + 1900, init.tm_mon + 1, init.tm_mday, 
            init.tm_hour, init.tm_min, init.tm_sec);

        if (this != &MIN_DATE && this != &MAX_DATE) {
            if (_ticks < MIN_DATE._ticks || _ticks > MAX_DATE._ticks)
                throw std::invalid_argument("Unable to create date_time");
        }
    }

    tm date_time::to_tm() const
    {
        int *dim = !is_leap_year(year()) ? month_days : month_days_leap;
        tm result;

        result.tm_year = (int)(year() - 1900);
        result.tm_mon = (int)(month() - 1);
        result.tm_yday = (int)(dim[month()] + day());
        result.tm_mday = (int)day();
        result.tm_wday = (int)(weekday() - 1);
        result.tm_hour = (int)hour();
        result.tm_min = (int)minute();
        result.tm_sec = (int)second();
        result.tm_isdst = -1;

        return result;
    }

    int date_time::year() const { return date_part(_ticks, 0); }
    int date_time::month() const { return date_part(_ticks, 1); }
    int date_time::day() const { return date_part(_ticks, 2); }
    int date_time::weekday() const { return (_ticks / TICKS_PER_DAY + 1) % 7; }
    int date_time::hour() const { return _ticks % TICKS_PER_DAY / TICKS_PER_HOUR; }
    int date_time::minute() const { return _ticks % TICKS_PER_HOUR / TICKS_PER_MINUTE; }
    int date_time::second() const { return _ticks % TICKS_PER_MINUTE / TICKS_PER_SECOND; }

    bool date_time::add_years(int years) { return add_months(years * 12); }

    bool date_time::add_months(int months)
    {
        int current_year = year();
        int current_month = month();
        int current_day = day();
        int adjusted_month = current_month + months - 1;

        if (adjusted_month >= 0) {
            // Months are being added to the current date
            current_month = adjusted_month % 12 + 1;
            current_year += adjusted_month / 12;
        }
        else {
            // Months are being subtracted from the current date
            current_month = (adjusted_month + 1) % 12 + 12;
            current_year += (adjusted_month - 11) / 12;
        }

        int year_day = days_in_month(current_year, current_month);

        if (current_day > year_day)
            current_day = year_day;

        try {
            _ticks = date_to_ticks(
                current_year, current_month, current_day, 
                hour(), minute(), second());

            return true;
        } catch (std::invalid_argument&) {
            return false;
        }
    }

    bool date_time::add_days(int days) 
    { 
        return add_ticks((long long)days * TICKS_PER_DAY); 
    }

    bool date_time::add_hours(int hours) 
    { 
        return add_ticks((long long)hours * TICKS_PER_HOUR); 
    }

    bool date_time::add_minutes(int minutes) 
    { 
        return add_ticks((long long)minutes * TICKS_PER_MINUTE); 
    }

    bool date_time::add_seconds(int seconds)
    {
        return add_ticks((long long)seconds * TICKS_PER_SECOND); 
    }

    int date_time::compare(const date_time& rhs)
    {
        if (_ticks == rhs._ticks)
            return 0;

        return _ticks < rhs._ticks ? -1 : +1;
    }

    /**
        Private static implementation
    */
    int date_time::month_days[] =      {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
    int date_time::month_days_leap[] = {0, 31, 60, 91, 121, 152, 182, 212, 244, 274, 305, 335, 366};

    long long date_time::date_to_ticks(
        int year, int month, int day, 
        int hour, int minute, int second)
    {
        if (year < 1 || year > 9999)
            throw std::invalid_argument("Invalid year");
        else if (month < 1 || month > 12)
            throw std::invalid_argument("Invalid month");

        int *dim = !is_leap_year(year--) ? month_days : month_days_leap;

        if (day > dim[month] - dim[month - 1])
            throw std::invalid_argument("Invalid day");

        long long leap_days = (year / 4) - (year / 100) + (year / 400);
        long long total_days = (year * 365) + leap_days + dim[month - 1] + day - 1;

        if (hour > 23 || minute > 59 || second > 59)
            throw std::invalid_argument("Invalid time");

        return (total_days * TICKS_PER_DAY) + ((long long)hour * TICKS_PER_HOUR) 
            + ((long long)minute * TICKS_PER_MINUTE) + ((long long)second * TICKS_PER_SECOND);
    }

    int date_time::date_part(long long ticks, int part)
    {
        if (part < 0 || part > 2)
            throw std::invalid_argument("Invalid date part");

        int result = 1; // Default to MIN_DATE parts

        if (ticks != 0) {
            int *dim = month_days;
            long long total_days = ticks / TICKS_PER_DAY;
            int span_400; // # of 400 year spans
            int span_100; // # of 100 year spans
            int span_4;   // # of 4 year spans
            int span;     // # of years in the current span

            span_400 = (int)(total_days / DAYS_IN_400);
            total_days -= span_400 * DAYS_IN_400; // Remove all 400 year spans
            span_100 = (int)(total_days / DAYS_IN_100);

            // This year is a century leap year, but we don't care yet
            if (span_100 == 4)
                span_100 = 3;

            total_days -= span_100 * DAYS_IN_100; // Remove all 100 year spans
            span_4 = (int)(total_days / DAYS_IN_4);
            total_days -= span_4 * DAYS_IN_4; // Remove all 4 year spans
            span = (int)(total_days / 365);

            // This year is a leap year, but we still don't care yet
            if (span == 4)
                span = 3;

            total_days -= span * 365; // Remove elapsed years in the current span

            // Sum up all of the span years including the current one to get the final year
            int year = (span_400 * 400) + (span_100 * 100) + (span_4 * 4) + span + 1;

            if (part == 0)
                result = year;
            else {
                // *Now* we care about the leap year for matching the month day
                if (is_leap_year(year))
                    dim = month_days_leap;

                // dim has 13 elements; dim[0] will never be a match in this loop
                for (int i = 1; i <= 12; i++) {
                    if (total_days < dim[i]) {
                        if (part == 1)
                            result = i;
                        else
                            result = (int)total_days - dim[i - 1] + 1;

                        break;
                    }
                }
            }
        }

        return result;
    }

    /**
        Private instance implementation
    */
    bool date_time::add_ticks(long long ticks)
    {
        long long temp = _ticks + ticks;

        if (temp < MIN_DATE._ticks || temp > MAX_DATE._ticks)
            return false;

        _ticks = temp;

        return true;
    }
}
The article starter has earned a lot of community kudos, and such articles offer a bounty for quality replies.