From the comments:
Actually the time is in the future (30 mins max in to the future).
It is unspecified what to do if the input is not in the range [now, now+30min]
. However, given a precondition that the input will be in this range, all ambiguities can be eliminated.
The C time API is somewhat clumsy to work with since it interjects the local time zone in conversions between serial (time_t
) and field (tm
) representations (not always but often enough to be error prone). And the problem statement either says or implies that both the input and output are UTC (stated for input, implied for output).
With all this in mind, C++20 <chrono>
can neatly solve this problem. And though C++20 <chrono>
isn't shipping yet (to the best of my knowledge), this C++ <chrono>
preview library can be used with C++11/14/17 to solve the problem.
#include "date/date.h"
#include <iostream>
#include <stdexcept>
#include <sstream>
#include <string>
// Convert ddhhmm UTC to YYYY-MM-dd hh:mm:ss UTC
// Precondition: ddhhmm is in the range [now, now+30min]
// Throws std::out_of_range if the precondition is not met
std::string
convert(std::string in)
{
using namespace date;
using namespace std;
using namespace std::chrono;
auto now = floor<minutes>(system_clock::now());
auto today = year_month_day{floor<days>(now)};
auto ym = today.year()/today.month();
day d;
hours h;
minutes m;
istringstream io{in};
io.exceptions(ios::failbit);
io >> parse("%d", d) >> parse("%H", h) >> parse("%M", m);
auto tp = sys_days{ym/d} + h + m;
if (tp < now)
{
ym += months{1};
tp = sys_days{ym/d} + h + m;
}
if (tp > now + 30min)
{
auto min_str = format("%d%H%M", now);
auto max_str = format("%d%H%M", now + 30min);
throw std::out_of_range(in + " is out of range: ["
+ min_str + ", " + max_str + "].");
}
return format("%F %T", tp);
}
The first step is to establish the current UTC date/time with system_clock::now()
. As we only want to traffic in minutes-precision, the current time is immediately truncated to minutes.
Next the year, month and day fields (UTC) are extracted from the current time and stored in today
.
Now the question is: What month
and year
need to be applied to the input day
in order to get the desired time_point
? In order to answer this, a "trial" year
and month
is taken from the current date and stored in ym
.
Next the input day
, hours
and minutes
fields are extracted from the input stream. If the input stream does not have the expected input, an exception is thrown.
A trial time_point
is computed from ym
, input day, input hours and input minutes. This trial time is either less than the current time, or it is the desired time_point
. If the trial time_point
is in the past, then adding 1 month
to it should bring it to the desired time_point
.1,2
If after adjusting, the time_point
is not within the desired range, an exception is thrown with an informative string which details what the problem is.
Otherwise the time_point
is formatted in the desired way.
This can be exercised like so:
std::cout << convert("220805") << '\n';
This just output for me:
terminating with uncaught exception of type std::out_of_range: 220805 is out of range: [221443, 221513].
And this:
std::cout << convert("221510") << '\n';
Just output for me:
2020-06-22 15:10:00
To port this program to C++20, remove #include "date/date.h"
and using namespace date;
.
"date/date.h"
is a header-only, open-source library.
1 The ddhhmm
input can be viewed as a "time point" with an epoch of midnight UTC of the first day of the current month. And it has a maximum value of 30min past the last instant of the current month (due to the precondition). Assuming a random uniformly distributed ddhhmm
in the range [yy-mm-01 00:00:00, yy-mm-last 24:30:00], most of the time yy-mm is the current year and month. But occasionally (when ddhhmm
is past yy-mm-last 24:00:00) yy-mm will be the following month.
2 The variable ym
has type year_month
. Adding months{1}
to it will increment the year
field when the starting month
is December
.