Mendes
Mendes

Reputation: 18471

localtime_s and strftime usage building ISO time string

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...

Upvotes: 2

Views: 2721

Answers (2)

Jerry Coffin
Jerry Coffin

Reputation: 490338

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.

Upvotes: 5

Howard Hinnant
Howard Hinnant

Reputation: 219185

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

Upvotes: 2

Related Questions