3
\$\begingroup\$

The question is basically like this: Given year, month, day and duration, calculate the future date based on given data and duration. The given date guarantees to be after 1900-01-01, and the duration k is between [0, 10000].

For example: Input: 2016 10 1 100(meaning given 2016-10-1, what is the future date after 100 days) Output: 2017-01-09

Another example: Input: 2017 1 1 10 Output: 2017-01-11

I have done my solution, but it is trivial and ugly. The most frustrating part is line 44 - 47, where I have to handle special cases on month and year. I feel like this special and trivial handling is due to my design of calculating month, but I cannot figure out a more universal/clear solution except for current solution.

Love to hear your advice! Thank you!

Code with line indication and better format fyi : https://paste.ubuntu.com/24765263/

#include <iostream>
#include <string>
#include <unordered_map>
using namespace std; // I know global namespace is bad practice. Just to save time.


int getMonthDays(int y, int m){
    unordered_map <int, int >daysMap = {{1,31}, {2,28}, {3,31}, {4,30}, {5,31}, {6,30}, {7,31}, {8,31}, {9,30}, {10,31}, {11,30}, {0,31}};
    if (m == 2){
        if (y % 400 ==0 || (y % 100 != 0&& y % 4 == 0)){
            return 29;
        }
        else{
            return 28;
        }
    }
    else{
        return daysMap[m];
    }
}

int main (){

    int year, month, day, duration;
    cin >> year >> month >> day >> duration;

    int res_year;
    string res_month, res_day;

    int days = getMonthDays(year, month) - day;
    while ( days < duration ){
        month++;
        if(days + getMonthDays(year + month/12, (month - 12 * (month/13))% 12) > duration){
            break;
        }
        else{
            days += getMonthDays(year + month/12, (month - 12 * (month/13))% 12);
        }

    }

    res_year = year + month / 12 ;
    res_month = (month - 12 * (month/13))% 12 >= 10 ? to_string((month - 12 * (month/13))% 12) : "0" + to_string((month - 12 * (month/13))% 12);
    if (res_month == "00"){
        res_month = "12"; // dont have set month "12", so if get month as "00", convert it to "12"
        res_year -= 1; // if the result month is "12", year minus 1 coz Month December is still in this year.
    }

    if (duration - days > 0 ){
        if (duration - days >= 10){
            res_day = to_string(duration - days);
        }
        else{
            res_day = "0" + to_string(duration - days);
        }
    }
    else{
        res_day = to_string(day + duration);
    }
    cout << res_year << "-" << res_month << "-" << res_day;

}
\$\endgroup\$
  • \$\begingroup\$ This is common interview type question. I think you choose the wrong technique to solve this. When doing date/time and manipulating: Convert to seconds (an epoch time). Do your manipulation then convert back into Gregorian from there. \$\endgroup\$ – Martin York Jun 4 '17 at 0:47
  • 1
    \$\begingroup\$ @LokiAstari, I believe I found more or less C++ way of outputting result of std::chrono::xxx_clock::now(). It might be worth updating the answer you've given long ago if you think the approach is good. \$\endgroup\$ – Incomputable Jun 4 '17 at 8:25
  • 1
    \$\begingroup\$ For convenience you can also take a look at Hinnant's Date library to simplify the work for you. \$\endgroup\$ – coincoin Jun 4 '17 at 9:04
  • \$\begingroup\$ @coincoin, I already mentioned it in the review :) I didn't want to deal with the license so I implemented some stuff by hand. \$\endgroup\$ – Incomputable Jun 4 '17 at 9:32
1
\$\begingroup\$

This is quite tricky problem to get right. As Loki said, we need to convert it to seconds after epoch. But doing so is not trivial...

Standard approach:

Well, not really standard, but in my opinion one should use std::chrono in this case. Some stuff is done in a roundabout way, but I don't think it is a huge problem. Howard Hinnant has written a library for it, but in case the license isn't acceptable, I'll reimplement some functionality by hand.

output std::time_point:

The first issue is outputting a std::time_point. The problem with it is that it doesn't have operator<< overload for streams. The only way possible to deal with this is to implement our own:

Necessary links:

The only ways to print time are either std::ctime() and std::put_time() (not counting the "implement by hand" approach). So let's follow the thread and find its end and test it:

#include <iostream>
#include <chrono>
#include <iomanip>

template <typename Clock, typename Duration>
std::ostream& operator<<(std::ostream& os, const std::chrono::time_point<Clock, Duration>& timep)
{
    auto converted_timep = Clock::to_time_t(timep);
    os << std::put_time(std::localtime(&converted_timep), "%Y %b %d %H:%M:%S");
    return os;
}

int main()
{
    auto now = std::chrono::system_clock::now();
    std::cout << now;

    return 0;
}

Output: 2017 Jun 04 12:07:48.

The only problem with implementation is that it can't be used from multiple threads, since std::localtime() returns pointer to static variable. The format could be mutable by some function, but I wouldn't care about it at this point.

Since we can manipulate std::chrono::time_point, and std::chrono::duration, let's use everything we have:

convert given date to std::chrono::time_point<system_clock>:

A good news, at last! From std::mktime():

Converts local calendar time to a time since epoch as a time_t object. time->tm_wday and time->tm_yday are ignored. The values in time are permitted to be outside their normal ranges.

Hooray! So we can just fill in the std::tm structure with year, month, and day since January 1 and we are done!

#include <iostream>
#include <chrono>
#include <iomanip>

template <typename Clock, typename Duration>
std::ostream& operator<<(std::ostream& os, const std::chrono::time_point<Clock, Duration>& timep)
{
    auto converted_timep = Clock::to_time_t(timep);
    os << std::put_time(std::localtime(&converted_timep), "%Y %b %d %H:%M:%S");
    return os;
}

auto convert_to_timepoint(int years, int months, int days)
{
    //perform checks, do division, modulus and stuff...
    years -= 1900; //epoch
    std::tm date = {};
    date.tm_year = years;
    date.tm_mon = months;
    date.tm_mday = days;

    return std::chrono::system_clock::from_time_t(std::mktime(&date));
}

int main()
{
    auto now = std::chrono::system_clock::now();
    std::cout << now << '\n';

    std::cout << "calling convert_to_timepoint:\n";
    auto date = convert_to_timepoint(1991, 0, 23);
    std::cout << date;

    return 0;
}

Output:

2017 Jun 04 13:08:49
calling convert_to_timepoint:
1991 Jan 23 00:00:00

Now the easy stuff, add the given number of days:

#include <iostream>
#include <chrono>
#include <iomanip>

template <typename Clock, typename Duration>
std::ostream& operator<<(std::ostream& os, const std::chrono::time_point<Clock, Duration>& timep)
{
    auto converted_timep = Clock::to_time_t(timep);
    os << std::put_time(std::localtime(&converted_timep), "%Y %b %d %H:%M:%S");
    return os;
}

auto convert_to_timepoint(int years, int months, int days)
{
    years -= 1900; //epoch
    std::tm date = {};
    date.tm_year = years;
    date.tm_mon = months;
    date.tm_mday = days;

    return std::chrono::system_clock::from_time_t(std::mktime(&date));
}

template <typename Clock, typename Duration>
auto add_days(const std::chrono::time_point<Clock, Duration>& timepoint, int days_to_add)
{
    constexpr std::time_t seconds_in_day = 60 * 60 * 24;
    //                                     mm   hh   dd

    std::time_t days = seconds_in_day * days_to_add;
    auto date = Clock::to_time_t(timepoint);
    return Clock::from_time_t(date + days);
}

int main()
{
    auto now = std::chrono::system_clock::now();
    std::cout << now << '\n';

    std::cout << "calling convert_to_timepoint with 1991 0 23 (1991 Jan 23):\n";
    auto date = convert_to_timepoint(1991, 0, 23);
    std::cout << date << '\n';

    std::cout << "10 day later (1991 Feb 02):\n" << add_days(date, 10);

    return 0;
}

Output:

2017 Jun 04 13:21:29
calling convert_to_timepoint with 1991 0 23 (1991 Jan 23):
1991 Jan 23 00:00:00
10 day later (1991 Feb 02):
1991 Feb 02 00:00:00

I left the convert_to_timepoint() so that it would be reusable on its own, but if needed it can be fused with add_days().

Some thoughts:

  • using namespace std; rarely buys anything, but opens a chance for errors.
  • standard library has a solution most of the time
  • modularity is important. Breaking functions into logical pieces makes it very easy to quickly prototype, adjust, mix'n'match.

Conclusion:

In this kind of problem, it is very dangerous try to implement by hand, since there are lots of edge cases. As a result, the problem given was rather a test of knowledge of standard library.

\$\endgroup\$
  • \$\begingroup\$ The date is calculated using this site. \$\endgroup\$ – Incomputable Jun 4 '17 at 8:39
  • \$\begingroup\$ Thank very much for your explanation! @Incomputable Really appreciated!! \$\endgroup\$ – Jacqueline P. Jun 15 '17 at 5:05
  • \$\begingroup\$ @JenifferPan, you're welcome! It's still worth checking out Howard's library, since he was leading <chrono>s development. \$\endgroup\$ – Incomputable Jun 15 '17 at 14:22

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.