1

I have a string which has the time in UTC as ddhhmm and I need to convert it to YYYY-MM-dd hh:mm:ss format.

Example Input: 220805
Output: 2020-06-22 08:05:00

As I know that <chrono> library can handle only time and not dates. I am not getting how to convert between local time and UTC when I do the conversion. Is there any C++ specific library which does this?

Thanks in advance.

NJMR
  • 1,886
  • 1
  • 27
  • 46
  • I assume you want to prefix the current UTC year and month? The "-dd hh:mm:00" part is just string manipulation, that's not a real date operation. – MSalters Jun 22 '20 at 08:14
  • Actually the time is in the future (30 mins max in to the future). The time which I am getting 220805 is in the future. I need to handle as per the time. – NJMR Jun 22 '20 at 08:16
  • [std::time_get::get_date](https://en.cppreference.com/w/cpp/locale/time_get/get_date) will allow you to generate and parse the date components. (See Example on page) – David C. Rankin Jun 22 '20 at 08:21

4 Answers4

2

std::gmtime gets you the current UTC time; std::put_time(time, "%Y-%m-") formats it.

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • Much nicer that the `std::time_get::get_date` route. – David C. Rankin Jun 22 '20 at 08:23
  • If the time is in the future, how to I handle that case. The time will be max 30 mins into the future. Example if current UTC is 220807 (ddhhmm) and input which I am getting will be 220830. In this case how do I handle month and year. – NJMR Jun 22 '20 at 10:33
  • @NJMR: `std::gmtime` takes a `std::time_t`. Adding 30 minutes to the current time, expressed as a `time_t` is [not entirely trivial](https://stackoverflow.com/a/18933370/15416) but it's doable. – MSalters Jun 22 '20 at 11:30
1

An example doing what you want:

#include <iostream>
#include <iomanip>
#include <boost/date_time/gregorian/gregorian.hpp>
int main()
{
    const boost::gregorian::date today = boost::gregorian::day_clock::local_day();
    const std::string input = "220805";
    std::ostringstream oss;
    oss << today.year() << '-'
        << std::setw(2) << std::setfill('0') << today.month().as_number() << '-'
        << input.substr(0,2) << ' '
        << input.substr(2,2) << ':'
        << input.substr(4,2) << ":00";
    const std::string output = oss.str();
    std::cout << "input = " << input << std::endl;
    std::cout << "output = " << output << std::endl;
    return 0;
}
Caduchon
  • 4,574
  • 4
  • 26
  • 67
  • If the time is in the future, how to I handle that case. The time will be max 30 mins into the future. Example if current UTC is 220807 (ddhhmm) and input which I am getting will be 220830. In this case how do I handle month and year. – NJMR Jun 22 '20 at 10:31
  • If hhmm is greater than 0030, the month and the year are the same than the current time. If, hhmm is lower than 0030, you can check the current time with a `date_time` object from boost to check if the current time is before or after 00:00. – Caduchon Jun 22 '20 at 12:35
1

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.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 1
    Wow, I just went through our videos... Thank you Sir... – NJMR Jun 25 '20 at 10:50
  • Hello Sir, I didnt understand your point "then adding 1 month to it should bring it to the desired time_point.". Can you please elaborate on this point or give some references.. Thanks. – NJMR Jun 26 '20 at 12:44
  • I've added a couple of footnotes to that bullet point that I hope are clarifying. – Howard Hinnant Jun 26 '20 at 13:18
0

You can still use library to read the clock if you want, but you still have to use the C-style date and time library to print (at least with )

This example also shows how to convert to local time:

auto timenow = std::chrono::system_clock::now();
std::time_t time_t_now = std::chrono::system_clock::to_time_t(timenow);

std::cout << "The time is "
          << std::put_time(std::localtime(&t_c), "%F %T") << '\n';
darune
  • 10,480
  • 2
  • 24
  • 62