C++11 alternative to localtime_r
Asked Answered
D

2

40

C++ defines time formatting functions in terms of strftime, which requires a struct tm "broken-down time" record. However, the C and C++03 languages provide no thread-safe way to obtain such a record; there is just one master struct tm for the whole program.

In C++03, this was more or less OK, because the language didn't support multithreading; it merely supported platforms supporting multithreading, which then provided facilities like POSIX localtime_r.

C++11 also defines new time utilities, which interface with the non-broken-down time_t type, which is what would be used to reinitialize the global struct tm. But obtaining a time_t isn't the problem.

Am I missing something or does this task still require reliance on POSIX?

EDIT: Here is some workaround code. It maintains compatibility with multithreaded environments that provide ::localtime_r and single-threaded environments that provide only std::localtime. It can easily be adapted to check for other functions as well, such as posix::localtime_r or ::localtime_s or what-have-you.

namespace query {
    char localtime_r( ... );

    struct has_localtime_r
        { enum { value = sizeof localtime_r( std::declval< std::time_t * >(), std::declval< std::tm * >() )
                        == sizeof( std::tm * ) }; };


    template< bool available > struct safest_localtime {
        static std::tm *call( std::time_t const *t, std::tm *r )
            { return localtime_r( t, r ); }
    };

    template<> struct safest_localtime< false > {
        static std::tm *call( std::time_t const *t, std::tm *r )
            { return std::localtime( t ); }
    };
}
std::tm *localtime( std::time_t const *t, std::tm *r )
    { return query::safest_localtime< query::has_localtime_r::value >().call( t, r ); }
Danzig answered 6/9, 2011 at 0:37 Comment(6)
Can't you just wrap localtime in a function that uses a mutex? Or some other lighter-weight form of lock? I don't see how POSIX is required here.Illness
@Nicol: Yes, but that's only safe as long as nobody else tries to do the same thing. In other words, it works in a program, not in a library.Danzig
But then they'd be calling a non-thread-safe function from threaded code, so they'd get what they deserve. You could just expose a function for them to use, and if they use the standard C one instead, then they get undefined behavior.Illness
@Nicol: What? I'm writing a library which cannot provide such restrictions to its users.Danzig
You can't tell users not to call non-thread-safe functions in multithreaded code? Or is it that threading is an implementation detail you don't want to expose to your users?Illness
@Nicol: My library is not multithreaded, but I would like it to be compatible with multithreaded code. Your suggestion to use a mutex guard and call localtime anyway is exactly what you're suggesting I tell users not to do. I can't insist that my mutex is the mutex for localtime — the library's functionality is unrelated. Anyway, anything to do with a mutex will be the wrong solution, since my code is in no way multithreaded, it's only thread-safe.Danzig
H
34

You're not missing anything.

The next C standard (due out probably this year) does have defined in Annex K:

struct tm *localtime_s(const time_t * restrict timer,
                       struct tm * restrict result);

And this new function is thread safe! But don't get too happy. There's two major problems:

  1. localtime_s is an optional extension to C11.

  2. C++11 references C99, not C11. local_time_s is not to be found in C++11, optional or not.

Update

In the 4 years since I answered this question, I have also been frustrated by the poor design of C++ tools in this area. I was motivated to create modern C++ tools to deal with this:

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

#include "tz.h"
#include <iostream>

int
main()
{
    using namespace date;
    auto local_time = make_zoned(current_zone(), std::chrono::system_clock::now());
    std::cout << local_time << '\n';
}

This just output for me:

2015-10-28 14:17:31.980135 EDT

local_time is a pairing of std::chrono::system_clock::time_point and time_zone indicating the local time.

There exists utilities for breaking the std::chrono::system_clock::time_point into human-readable field types, such as year, month, day, hour, minute, second, and subseconds. Here is a presentation focusing on those (non-timezone) pieces:

https://www.youtube.com/watch?v=tzyGjOm8AKo

All of this is of course thread safe (it is modern C++).

Update 2

The above is now part of C++20 with this slightly altered syntax:

#include <chrono>
#include <iostream>

int
main()
{
    namespace chr = std::chrono;

    chr::zoned_time local_time{chr::current_zone(), chr::system_clock::now()};
    std::cout << local_time << '\n';
}
Highstepper answered 6/9, 2011 at 2:44 Comment(1)
… And they changed the name! Thanks for the info. It is possible, anyway, to query whether localtime_r is available and fall back on localtime otherwise. So it looks like I can have my cake and eat it too, after all.Danzig
R
1

The following answer pointed out a key detail of mktime() (which is the only thread-safe function handling struct tm even before C99):

Here then is a portable alternative to localtime_r or localtime_s:

//  localtime_s(&startTm, &startTimeT);
tm startTm{};
startTm.tm_isdst = -1;
startTm.tm_year = 122;
auto refTimeT = mktime(&startTm);

// goes horribly out of range but ok because mktime will canonicalize
startTm.tm_sec += startTimeT - refTimeT;

mktime(&startTm);
Rae answered 17/6, 2022 at 19:10 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.