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;
}
1 Answer 1
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:
std::chrono::system_clock::to_time_t()
(system clock is just example).
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
andtime->tm_yday
are ignored. The values intime
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.
-
\$\begingroup\$ The date is calculated using this site. \$\endgroup\$Incomputable– Incomputable2017年06月04日 08:39:25 +00:00Commented Jun 4, 2017 at 8:39
-
\$\begingroup\$ Thank very much for your explanation! @Incomputable Really appreciated!! \$\endgroup\$helloworld– helloworld2017年06月15日 05:05:09 +00:00Commented Jun 15, 2017 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– Incomputable2017年06月15日 14:22:25 +00:00Commented Jun 15, 2017 at 14:22
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\$