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";
}