2

I´ve wrote the following function that receives a time point and return a ISO string with milliseconds:

std::string TimePointToISOString(const std::chrono::time_point<std::chrono::system_clock>& time)
    {
        std::time_t rawTime = std::chrono::system_clock::to_time_t(time);
        struct tm timeinfo;
        localtime_s(&timeinfo, &rawTime);

        std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(time.time_since_epoch());
        std::size_t frac_seconds = ms.count() % 1000;

        char buffer[32];
        std::strftime(buffer, 32, "%FT%TZ", &timeinfo);

        std::string bufferStr(buffer);
        std::stringstream ss;
        ss << bufferStr.substr(0, 19) << "." << std::setw(3) << std::setfill ('0') << frac_seconds << bufferStr.substr(20);

        return ss.str();
    }

I´ve used to run that on Linux with the localtime() function with no problems at all. Now I am migrating to Windows/VS2012 and the compiler suggests to change localtime() for a safer localtime_s(), done immediately.

After the conversion the strftime call does crash at runtime with the following error:

Expression: ("Invalid format directive", 0)

Compilation runs fine. Help appreciated to find out what´s going on here. I guess something simple that I can´t notice...

Mendes
  • 17,489
  • 35
  • 150
  • 263

2 Answers2

5

The %F and %T conversions were added in C99, and VS 2012 only supports C89.

Although it still doesn't try to support all of C99, I believe VS 2015 adds enough more (especially the C99 library functions, which are also part of C++11) that this should work fine with it.

If you need to continue using the older compiler/library, %F and %T are both basically "shortcuts" for some formats that were already supported in C89. If I'm reading the requirements correctly, the following should be equivalent:

std::strftime(buffer, 32, "%Y-%m-%dT%H:%M:%SZ", &timeinfo);

As an aside, if your compiler supports C++11, it's generally easier and cleaner to use std::put_time instead of strftime.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Jerry, I´ve tried `ss << std::put_time(&timeinfo, "%FT%TZ");` with no success also... Same error... Seens that `%FT` and `%TZ` are not supported at all in VS2012. So I´ve changed to `"%Y-%m-%dT%H:%M:%SZ" and is working with `strftime` AND std::put_time. Thanks for helping. – Mendes Mar 24 '16 at 11:53
  • @Mendes: Yes, I'd guess that `put_time` is little more than a wrapper around `strftime` (or both use the same engine), so for any given compiler, the two are likely to support the same conversions--switching between them is purely a matter of convenience, not likely to affect your original question. – Jerry Coffin Mar 24 '16 at 12:45
2

This is a good question (upvoted). And Jerry Coffin's answer is a good answer (also upvoted). This answer is about adding further information about an alternative open-source solution which is known to work on VS-2013 and later, gcc, and clang. This alternative will produce identical output to TimePointToISOString. In order to differentiate it, I've given it another name: to_local_string:

std::string
to_local_string(const std::chrono::system_clock::time_point& time)
{
    using namespace date;

    auto zone = current_zone();
    auto local = floor<std::chrono::milliseconds>(zone->to_local(time).first);
    auto dp = floor<days>(local);
    year_month_day ymd = dp;
    std::stringstream ss;
    ss << ymd << 'T' << make_time(local-dp);
    return ss.str();
}

This uses the time zone database parser found here:

https://github.com/HowardHinnant/date

and documented here:

http://howardhinnant.github.io/tz.html

This is a parser of the IANA Timezone database, and is used here to simply find the UTC offset for the local timezone. Once the local time_point is found (stored as a std::chrono::system_clock::time_point), it is broken up into field types year/month/day/hour/minute/second/millisecond using this library whose implementation is found at the same github site.

I just ran both TimePointToISOString and to_local_string on the same time_point and the output was:

2016-03-23T22:54:52.626
2016-03-23T22:54:52.626
Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577