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; ther开发者_如何学Pythone 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 ); }
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:
localtime_s
is an optional extension to C11.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';
}
The following answer pointed out a key detail of mktime()
(which is the only thread-safe function handling struct tm
even before C99):
- https://stackoverflow.com/a/54248414/103167
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);
startTm.tm_sec += startTimeT - refTimeT;
mktime(&startTm);
精彩评论