Skip to main content
replaced http://stackoverflow.com/ with https://stackoverflow.com/
Source Link

Putting using namespace std at the top of every program is a bad habita bad habit that you'd do well to avoid.

Putting using namespace std at the top of every program is a bad habit that you'd do well to avoid.

Putting using namespace std at the top of every program is a bad habit that you'd do well to avoid.

added note about partial leap year support
Source Link
Edward
  • 66.6k
  • 4
  • 118
  • 282

An alternative approach

As others have correctly noted, some dates are inherently ambiguous and can't always be correctly interpreted. A date of '4 5 1980' might be intended to be 4 May or 5 April. However, any date with day > 12 and year > 31 (which is most dates in the past 2000 years) can be unambiguously rearranged. Further, dates that are invalid, such as 31 April (there are only 30 days in April) can be rejected. Here's an alternative approach that includes a bool ambiguous data member. Leap years are partially handled. The idea is that if the given date is unambiguous, no further action is needed, but an ambiguous date might require, for example, that the user is asked further questions about intent. Here's the refactored code:

#include <iostream>
#include <algorithm>
#include <cassert>

class Date
{
public:
    Date(unsigned y=1970, unsigned m=1, unsigned d=1) :
        year{y}, 
        month{m},
        day{d},
        ambiguous{true}
    { rearrange(); }
    bool operator==(const Date& other) const {
        return year == other.year && month == other.month && day == other.day;
    }
    bool isAmbiguous() const { return ambiguous; }
    friend std::istream& operator>>(std::istream &in, Date& d) {
        in >> d.year >> d.month >> d.day; 
        d.rearrange();
        return in;
    }
    friend std::ostream &operator<<(std::ostream &out, const Date& d) { 
        return out << d.year << " " << d.month << " " << d.day; 
    }

private:
    bool rearrange();
    static bool check(unsigned y, unsigned m, unsigned d);
    static bool invalid(unsigned y, unsigned m, unsigned d);

    unsigned year;
    unsigned month;
    unsigned day;
    bool ambiguous;
};

bool Date::invalid(unsigned y, unsigned m, unsigned d) {
    return y == 0 || m == 0 || d == 0;
}

bool Date::check(unsigned y, unsigned m, unsigned d)
{
    switch (m) {
        case 1:  // January
        case 3:  // March
        case 5:  // May
        case 7:  // July
        case 8:  // August
        case 10: // October
        case 12: // December
            return d <= 31;

        case 4:  // April 
        case 6:  // June 
        case 9:  // September
        case 11: // November
            return d <= 30;

        case 2:  // February
            if (y % 4 != 0) 
                return d <= 28;
            return d <= 29;

        default:
            return false;
    }
}

// returns true if the resulting, possibly rearranged Date is valid
bool Date::rearrange()
{
    // if it's invalid, no rearranging can fix it
    if (invalid(year, month, day)) {
        return false;
    }
    if (day > 31) {
        std::swap(day, year);
    }
    if (month > 31) {
        std::swap(month, year);
    }
    if (month > 12) {
        std::swap(month, day);
    }
    // is it already good?
    if (check(year, month, day)) {
        ambiguous = !((year > 31) && ((month == day) || (day > 12)));
        return true;
    } 
    ambiguous = true;
    return false;
}
    
void test()
{
    Date anchor{1969, 7, 20};
    std::cout << "anchor : " << anchor << "\n";
    Date ymd{1969, 7, 20};
    assert(ymd == anchor);
    Date ydm{1969, 20, 7};
    assert(ydm == anchor);
    Date myd{7, 1969, 20};
    assert(myd == anchor);
    Date mdy{7, 20, 1969};
    assert(mdy == anchor);
    Date dmy{20, 7, 1969};
    assert(dmy == anchor);
    Date dym{20, 1969, 7};
    assert(dym == anchor);
}

int main()
{
    std::cout << "Enter year, month, Date and I'll try and organize it" << std::endl;
    Date dawg;
    test();
    std::cin >> dawg;
    std::cout << dawg << " is" << (dawg.isAmbiguous() ? "" : " not") << " ambiguous.\n";
}

An alternative approach

As others have correctly noted, some dates are inherently ambiguous and can't always be correctly interpreted. A date of '4 5 1980' might be intended to be 4 May or 5 April. However, any date with day > 12 and year > 31 (which is most dates in the past 2000 years) can be unambiguously rearranged. Further, dates that are invalid, such as 31 April (there are only 30 days in April) can be rejected. Here's an alternative approach that includes a bool ambiguous data member. Leap years are partially handled. The idea is that if the given date is unambiguous, no further action is needed, but an ambiguous date might require, for example, that the user is asked further questions about intent. Here's the refactored code:

#include <iostream>
#include <algorithm>
#include <cassert>

class Date
{
public:
    Date(unsigned y=1970, unsigned m=1, unsigned d=1) :
        year{y}, 
        month{m},
        day{d},
        ambiguous{true}
    { rearrange(); }
    bool operator==(const Date& other) const {
        return year == other.year && month == other.month && day == other.day;
    }
    bool isAmbiguous() const { return ambiguous; }
    friend std::istream& operator>>(std::istream &in, Date& d) {
        in >> d.year >> d.month >> d.day; 
        d.rearrange();
        return in;
    }
    friend std::ostream &operator<<(std::ostream &out, const Date& d) { 
        return out << d.year << " " << d.month << " " << d.day; 
    }

private:
    bool rearrange();
    static bool check(unsigned y, unsigned m, unsigned d);
    static bool invalid(unsigned y, unsigned m, unsigned d);

    unsigned year;
    unsigned month;
    unsigned day;
    bool ambiguous;
};

bool Date::invalid(unsigned y, unsigned m, unsigned d) {
    return y == 0 || m == 0 || d == 0;
}

bool Date::check(unsigned y, unsigned m, unsigned d)
{
    switch (m) {
        case 1:  // January
        case 3:  // March
        case 5:  // May
        case 7:  // July
        case 8:  // August
        case 10: // October
        case 12: // December
            return d <= 31;

        case 4:  // April 
        case 6:  // June 
        case 9:  // September
        case 11: // November
            return d <= 30;

        case 2:  // February
            if (y % 4 != 0) 
                return d <= 28;
            return d <= 29;

        default:
            return false;
    }
}

// returns true if the resulting, possibly rearranged Date is valid
bool Date::rearrange()
{
    // if it's invalid, no rearranging can fix it
    if (invalid(year, month, day)) {
        return false;
    }
    if (day > 31) {
        std::swap(day, year);
    }
    if (month > 31) {
        std::swap(month, year);
    }
    if (month > 12) {
        std::swap(month, day);
    }
    // is it already good?
    if (check(year, month, day)) {
        ambiguous = !((year > 31) && ((month == day) || (day > 12)));
        return true;
    } 
    ambiguous = true;
    return false;
}
    
void test()
{
    Date anchor{1969, 7, 20};
    std::cout << "anchor : " << anchor << "\n";
    Date ymd{1969, 7, 20};
    assert(ymd == anchor);
    Date ydm{1969, 20, 7};
    assert(ydm == anchor);
    Date myd{7, 1969, 20};
    assert(myd == anchor);
    Date mdy{7, 20, 1969};
    assert(mdy == anchor);
    Date dmy{20, 7, 1969};
    assert(dmy == anchor);
    Date dym{20, 1969, 7};
    assert(dym == anchor);
}

int main()
{
    std::cout << "Enter year, month, Date and I'll try and organize it" << std::endl;
    Date dawg;
    test();
    std::cin >> dawg;
    std::cout << dawg << " is" << (dawg.isAmbiguous() ? "" : " not") << " ambiguous.\n";
}
added some newer points
Source Link
Edward
  • 66.6k
  • 4
  • 118
  • 282

Right now, if I enter the 10 March 2010 date as 3 10 2010 which is the ordering commonly used in the United States, the program reinterprets that as 3 October 2010. (This was intended to make the same point as @πάντα ῥεῖ in the section "Don't promise things to users of your API, you cannot ever guarantee" but that answer states the point more accurately and eloquently.)

Since you're using C++, you should use the keywords true and false instead of 1 and 0 for boolean return values.

Only #include things that are needed

The code currently has this line:

#include <string>

However, there is no code in this program that needs anything from that header, so it should be omitted.

Right now, if I enter the 10 March 2010 date as 3 10 2010 which is the ordering commonly used in the United States, the program reinterprets that as 3 October 2010.

Since you're using C++, you should use the keywords true and false instead of 1 and 0 for boolean return values.

Right now, if I enter the 10 March 2010 date as 3 10 2010 which is the ordering commonly used in the United States, the program reinterprets that as 3 October 2010. (This was intended to make the same point as @πάντα ῥεῖ in the section "Don't promise things to users of your API, you cannot ever guarantee" but that answer states the point more accurately and eloquently.)

Since you're using C++, you should use the keywords true and false instead of 1 and 0 for boolean return values.

Only #include things that are needed

The code currently has this line:

#include <string>

However, there is no code in this program that needs anything from that header, so it should be omitted.

added some newer points
Source Link
Edward
  • 66.6k
  • 4
  • 118
  • 282
Loading
Rollback to Revision 2
Source Link
Edward
  • 66.6k
  • 4
  • 118
  • 282
Loading
edited body
Source Link
TheCoffeeCup
  • 9.4k
  • 4
  • 36
  • 96
Loading
added clarification
Source Link
Edward
  • 66.6k
  • 4
  • 118
  • 282
Loading
Source Link
Edward
  • 66.6k
  • 4
  • 118
  • 282
Loading