1
\$\begingroup\$

This is a Date class I made for a hobby project. It does not deal with time at all; that is, the smallest unit of time it considers is one day, and no concern with time zones.

Where I do most of my coding, I'm not allowed C++11, so if there are any new languages features that would apply here, I am definitely interested in hearing about it. I'd also like to hear about the overall design of the class, or any edge cases I may have missed.

Date.h

#pragma once
#include <memory>
#include <string>
#include <vector>

#include <time.h>

/**
 * Represents a date. Does not account for time. Does not account for time zones
 */
class Date
{
public:
    /**
     * @brief Construct current, local Date
     */
    Date();

    /**
     * @brief Construct local Date from timestamp
     * @param timestamp seconds since January 1 1970 UTC
     */
    Date(time_t timestamp);

    /**
     * Return the year
     */
    size_t getYear() const;

    /**
     * @brief Return a number representing the month.
     *
     * @return represented month
     */
    size_t getMonth() const;

    /**
     * @brief get name of month, in English
     */
    std::string getMonthString() const;

    /**
     * @brief get day of month
     */
    size_t getDate() const;

    /**
     * @brief Increment current date by given number of years.
     */
    void addYears(int numberOfYears);

    /**
     * @brief Increment current date by given number of months.
     */
    void addMonths(int numberOfMonths);

    /**
     * @brief Increment current date by given number of days.
     */
    void addDays(int numberOfDays);

    /**
     * @brief return String representation of date
     * example: 1 February 1983
     */
    std::string toString() const;

    bool operator==(const Date& rhs) const;
    bool operator!=(const Date& rhs) const;

private:
    bool isLeapYear(size_t year) const;
    bool isLeapYear() const;
    void init(time_t timestamp);
    const static std::vector<std::string> monthNames;
    size_t daysPerMonth() const;

    int date;
    int month;
    int year;
};

std::ostream& operator<<(std::ostream& stream, const Date& rhs);

Date.cpp

#include "stdafx.h"
#include "Date.h"
#include <cassert>
#include <vector>

Date::Date()
{
    init(time(NULL));
}

Date::Date(time_t timestamp)
{
    init(timestamp);
}

void Date::init(time_t timestamp)
{
    struct tm datetime;
    localtime_s(&datetime, &timestamp);
    year = datetime.tm_year + 1900;
    month = datetime.tm_mon;
    date = datetime.tm_mday;
}

size_t Date::getYear() const
{
    return year;
}

size_t Date::getMonth() const
{
    //1-index for human use
    return month + 1;
}

size_t Date::getDate() const
{
    return date;
}

std::string Date::toString() const
{
    return std::to_string(getDate()) + " " + getMonthString() + " " + std::to_string(getYear());
}

const std::vector<std::string> Date::monthNames = {
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December"
};

std::string Date::getMonthString() const
{
    return monthNames[getMonth()];
}

bool Date::operator==(const Date& rhs) const
{
    return getYear() == rhs.getYear() &&
        getMonth() == rhs.getMonth() &&
        getDate() == rhs.getDate();
}

bool Date::operator!=(const Date& rhs) const
{
    return !(*this == rhs);
}

std::ostream& operator<<(std::ostream& stream, const Date& rhs)
{
    return stream << rhs.toString();
}

void Date::addYears(int numberOfYears)
{
    if (isLeapYear() &&
        !isLeapYear(getYear() + numberOfYears) && 
        date == 29)
    {
        addDays(1);
    }
    year += numberOfYears;
}

void Date::addDays(int numberOfDays)
{
    numberOfDays += (date - 1);
    date = 1;

    while (numberOfDays < 0)
    {
        addMonths(-1);
        numberOfDays += daysPerMonth();
    }
    while (numberOfDays >= daysPerMonth())
    {
        numberOfDays -= daysPerMonth();
        addMonths(1);
    }
    date += numberOfDays;

}

void Date::addMonths(int numberOfMonths)
{
    month += numberOfMonths;
    while (month < 0)
    {
        addYears(-1);
        month += 12;
    }
    while (month > 11)
    {
        addYears(1);
        month -= 12;
    }
    if (date > daysPerMonth())
    {
        // No need to be precise. Just loop to the 1st.
        month += 1;
        date = 1;
    }
}

size_t Date::daysPerMonth() const
{
    assert(month >= 0);
    assert(month <= 11);
    switch (month)
    {
    case 0:
    case 2:
    case 4:
    case 6:
    case 7:
    case 9:
    case 11:
        return 31;
    case 3:
    case 5:
    case 8:
        return 30;
    default: //February
        if (isLeapYear())
        {
            return 29;
        }
        return 28;
    }
}

bool Date::isLeapYear(size_t year) const
{
    return year % 4 == 0;
}

bool Date::isLeapYear() const
{
    return isLeapYear(getYear());
}
\$\endgroup\$
  • 2
    \$\begingroup\$ The leap year calculation is more complicated than that. Years that are multiples of 100 aren't leap years except multiples of 400 are. \$\endgroup\$ – RichN Jan 25 '17 at 15:18
  • \$\begingroup\$ Ugh, of course it is. Good catch! \$\endgroup\$ – User319 Jan 25 '17 at 21:19
1
\$\begingroup\$

Design

Member (private) vs. Non Member

Allways try to keep your Class small. Functions like isLeapYear(size_t year) or getDaysPerMonth() do not really operate on the State of your class. Thus you should consider to make them non-member functions. I would than suggest to only declare and define them in the *.cpp (within a anonymous namespace perhaps) that would lessen the compile dependencies.

Same for the variable monthNames.

Function names

When I read the function name getDate() in a Class Date I would think that it returns something like a copy or different representation of the whole Class. Consider to rename the Function.

Coding style

Named loops

Loops like

while (month > 11)
{
    addYears(1);
    month -= 12;
}

take a little time and effort to understand. Consider to move them in seperate named functions like "getNumberOfYearsFromAmountOfDays(...)". In this way it is much easier to understand the code on first sight. I would make these functions non-member.

Bug

IsLeapYear

From the comments:

"The leap year calculation is more complicated than that. Years that are multiples of 100 aren't leap years except multiples of 400 are." – RichN

C++11

One thing I see you could change with C++11 is the init Function. Because it is now allowed to call one Constructor in the initializer list from another. Example

Date::Date() :
   Date(time(NULL))
{
   /* empty */
}

Thats all. Its not much but perhaps it helps a little.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.