0
\$\begingroup\$

My program calculates the user's age in years and months. It takes the user's birth year and month and then the current year and month as input.

#include <iostream>
    using namespace std;

int main ()
{
    int y1 , m1 , y2 , m2 ;
    
    
cout<<"Enter Your year of birth"<<endl;
cin>> y1;
cout<<"Enter Your month of birth"<<endl;
cin>>m1;
cout<<"Enter the current year"<<endl;
cin>>y2;
cout<<"Enter the current month"<<endl;
cin>>m2;
if (m2 < m1)
    {cout<<"You have "<<y2-y1 <<"years"<<" and only "<<(m2-m1)* -1 <<" months "<<endl;}
else {
cout<<"you have "<<y2-y1<<" years "<<"and "<<m2-m1<<" months"<<endl;}           
                    

}
\$\endgroup\$
1

2 Answers 2

3
\$\begingroup\$

Avoid using namespace std - that dumps the whole of the standard library into the global namespace, eliminating all the advantages of it having a namespace.

When taking input, think about how it can fail. If something that's not a number is entered, then we'll be working with uninitialized values, and all bets are off. Check with something like

if (!std::cin) {
    std::cerr << "Invalid input\n";
    return EXIT_FAILURE;
}

Obviously this can be improved to be more forgiving (e.g. ask again for input). There's a good question here you might want to read: Reading a number from the standard input.

The calculation is clearly wrong, as we output y2-y1 for years, even when a partial year has elapsed (e.g. try this code with 2000 12 2001 1 as input - we should expect "0 years and 1 month" as output, but get "1years and only 11 months".

Don't use std::endl when there's no need to flush output buffers (reading standard input will flush standard output, so it's not required there).


Improved code

#include <iostream>
#include <limits>

static int read_integer(const char* prompt)
{
    for (;;) {
        std::cout << prompt;
        int value;
        if (std::cin >> value) {
            return value;
        }
        if (std::cin.eof()) {
            throw std::ios_base::failure("Input stream closed");
        }
        std::cerr << "Please enter a number!\n";
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
}

int main()
{
    int y1 = read_integer("Enter Your year of birth: ");
    int m1 = read_integer("Enter Your month of birth: ");
    int y2 = read_integer("Enter the current year: ");
    int m2 = read_integer("Enter the current month: ");

    if (m2 < m1) {
        ++y1;
        m2 += 12;
    }

    std::cout << "You are " << y2-y1 << " years and "
              << m2-m1 << " months old.\n";
}

Enhancements

It seems pointless to ask for today's date when our runtime should already know that. We can use std::time and std::localtime to find the current year and month, instead of requiring them from the user:

auto const t = std::time(nullptr);
auto const *now = std::localtime(&t);

int y2 = now->tm_year + 1900;
int m2 = now->tm_mon + 1;

Consider how to deal with singular and plural forms. In English, we write "1 year" rather than "1 years". We can write a function to combine the number with an appropriate singular or plural noun. (In other languages, it becomes more complex, with three or more different forms, which apply to different kinds of number).

Consider what to do with dates in the future, rather than printing a negative number of years.

Here's a version with the enhancements included:

#include <ctime>
#include <iostream>
#include <limits>
#include <string>
    
static int read_integer(const char* prompt)
{
    for (;;) {
        std::cout << prompt;
        int value;
        if (std::cin >> value) {
            return value;
        }
        if (std::cin.eof()) {
            throw std::ios_base::failure("Input stream closed");
        }
        if (std::cin.bad()) {
            throw std::ios_base::failure("Input stream failed");
        }
        std::cerr << "Please enter a number!\n";
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    }
}

static std::string quantity(int n, const char* single, const char* plural)
{
    return std::to_string(n) + " " + (n == 1 ? single : plural);
}

int main()
{
    int y1 = read_integer("Enter Your year of birth: ");
    int m1 = read_integer("Enter Your month of birth: ");

    auto const t = std::time(nullptr);
    auto const *now = std::localtime(&t);

    int y2 = now->tm_year + 1900;
    int m2 = now->tm_mon + 1;

    if (m2 < m1) {
        --y2;
        m2 += 12;
    }

    if (y1 * 12 + m1 > y2 * 12 + m2) {
        std::cout << "You are not born yet.\n";
    } else {
        std::cout << "You are " << quantity(y2-y1, "year", "years")  << " and "
                  << quantity(m2-m1, "month", "months") << " old.\n";
    }
}
\$\endgroup\$
1
2
\$\begingroup\$

As pointed out your time calculations are incorrect.

Avoid mixing units

Your function is basically just calculating the months between two dates but you are working in both years and months.

Rather than calculate the years then the months convert both dates to the same unit. In this case that would be months.

Converting to months and back.

  • Given int year and int month (assuming months 1 - 12) the months date is year * 12 + month - 1

  • Given int months you can get year = (int)months / 12 and month = months % 12 + 1

With both dates in months you can just use simple math on the months.

Example What year will I be when twice my age.

    int birth = 1967 * 12 + 8 - 1;
    int now = 2021 * 12 + 10 - 1;
    int yearTwiceAge = (birth + (now - birth) * 2) / 12;

Rather than check for negatives use abs (see example A).

The little things

Format the output

It is also always nice to ensure the output is clean and that the wording matches the values. EG its "1 year" not "1 years" and if there are 0 months or years we never write "0 years" or "0 months"(see example A)

Help the user

People write dates in many ways so rather than force them to use a particular format, get a string and try various date formats to parse the string into a date. If you can not parse the date then ask again and show an example of a correct format. (see example B)

You can also use the C style time libraries to help.

Example A

Using Months as the units and formatting the result

#include <iostream>

void MonthsBetween(int yearA, int monthA, int yearB, int monthB) { // months from 1 to 12
    int dif = abs((yearA * 12 + monthA - 1) - (yearB * 12 + monthB - 1));
    int years = dif / 12;
    int months = dif % 12;

    std::cout << "Between " << monthA << "/" <<  yearA << " and ";
    std::cout << monthB << "/" << yearB;
    if (years) { std::cout << " there " << (years == 1 ? "is " : "are "); }
    else if (months) { std::cout << " there " << (months == 1 ? "is " : "are "); }
    else {
        std::cout << " there is no difference.\n";
        return;
    }
       
    if (years) { std::cout << years << " year" << (years > 1 ? "s" : ""); }
    if (years && months) { std::cout << " and "; }
    if (months) { std::cout  << months << " month" << (months > 1 ? "s" : ""); }
    std::cout << ".\n";
}

Usage examples...

int main() {
    MonthsBetween(1921, 10, 2021, 10);
    MonthsBetween(1921, 1, 2021, 10);
    MonthsBetween(2023, 4, 2020, 3);
    MonthsBetween(2020, 4, 2022, 3); 
    MonthsBetween(2020, 8, 2021, 1);
    return 0;
}
/* Output
Between 10/1921 and 10/2021 there are 100 years.
Between 1/1921 and 10/2021 there are 100 years and 9 months.
Between 4/2023 and 3/2020 there are 3 years and 1 month.
Between 4/2020 and 3/2022 there is 1 year and 11 months. 
Between 8/2020 and 1/2021 there are 5 months.
*/

Example B

The code below will try and parse the dateStr using several formats returning true and setting the time object when successful. It will also understand "now" to mean "now"

#include <sstream>
#include <iomanip>

const std::string formats[] {"%Y-%m", "%m-%Y", "%m/%Y",  "%Y/%m"};
bool ParseDate(std::string dateStr, std::tm *date) {
    if (dateStr.compare("now") == 0) {
        std::time_t t = std::time(nullptr);
        *date = *std::localtime(&t);   
        return true;
    }
    for (auto f : formats) {
        std::istringstream ss(dateStr);
        ss >> std::get_time(date, (char *)&f[0]);
        if (!ss.fail()) { return true; }
    }
    std::cout << "Invalid date. Use format eg Jan 1960 as YYYY/MM is 1960/01\n";
    return false;
}

Usage example...

// using the MonthsBetween function from prev example
int main() {
    std::tm birth, now;  
    ParseDate("10/2002", &birth);
    ParseDate("now", &now);
    MonthsBetween(birth.tm_year + 1900, birth.tm_mon + 1, now.tm_year + 1900, now.tm_mon + 1);

}
/* Outputs 
Between 3/2002 and 10/2021 there are 19 years and 7 months.
*/
\$\endgroup\$
2
  • \$\begingroup\$ The minus ones are unnecessary, they cancel out. The year zero is an arbitrary point in time anyway, so it doesn't matter what constant you add or subtract when converting from year + month to months. \$\endgroup\$
    – G. Sliepen
    Commented Oct 9, 2021 at 8:46
  • \$\begingroup\$ @G.Sliepen Yes indeed, the formula can simply be abs((yearB - yearA) * 12 + monthB - monthA); but I wanted the working to be unambiguous for the OP. It would be problematic in some situation eg given only a year and then a month year 2000 - 01:2001 the month does need to be normalized (0<>11) \$\endgroup\$
    – Blindman67
    Commented Oct 9, 2021 at 15:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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