I am trying to sort photos in chronological order. Therefore I extract the time as a string from the EXIF data, which I then convert to std::time_t
. However I sometimes get an incorrect result. I have reduced the problem to this minimal example. It features three time-strings, one second apart:
#include <vector>
#include <string>
#include <iostream>
#include <ctime>
#include <iomanip>
#include <sstream>
int main()
{
std::vector<std::string> vec;
vec.push_back("2016:07:30 09:27:06");
vec.push_back("2016:07:30 09:27:07");
vec.push_back("2016:07:30 09:27:08");
for ( auto & i : vec )
{
struct std::tm tm;
std::istringstream iss;
iss.str(i);
iss >> std::get_time(&tm,"%Y:%m:%d %H:%M:%S");
std::time_t time = mktime(&tm);
std::cout << i << " time = " << time << std::endl;
}
}
Compiled with clang++ -std=c++14 test.cpp
.
The output:
2016:07:30 09:27:06 time = 1469867226
2016:07:30 09:27:07 time = 1469863627
2016:07:30 09:27:08 time = 1469863628
Which is obviously wrong, they should be 1
apart, which is only true for the last two?
Edit
Since there seems to be a bug in clang's
std::get_time
(andstd::put_time
), here is my version information:$ clang --version Apple LLVM version 8.1.0 (clang-802.0.42) Target: x86_64-apple-darwin16.7.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
Here is another library you could use to work around the bug. And this library is portable. If you have any problems with it, I'll get it fixed within hours. It is just a single header, no sources, and so very easy to "install". Just put the header somewhere in your include path:
#include "date.h"
#include <vector>
#include <string>
#include <iostream>
#include <sstream>
int main()
{
std::vector<std::string> vec;
vec.push_back("2016:07:30 09:27:06");
vec.push_back("2016:07:30 09:27:07");
vec.push_back("2016:07:30 09:27:08");
for ( auto & i : vec )
{
date::sys_seconds tm;
std::istringstream iss{i};
iss >> date::parse("%Y:%m:%d %H:%M:%S", tm);
std::cout << i << " time = " << tm.time_since_epoch().count() << std::endl;
std::cout << date::format("%Y:%m:%d %H:%M:%S\n", tm);
}
}
Output:
2016:07:30 09:27:06 time = 1469870826
2016:07:30 09:27:06
2016:07:30 09:27:07 time = 1469870827
2016:07:30 09:27:07
2016:07:30 09:27:08 time = 1469870828
2016:07:30 09:27:08
As I said in my comment, all your output is wrong. It seems that it is a bug in clang. I searched on the Bugzilla page of the LLVM project but found nothing. Maybe you want to submit the bug report with your example code.
A workaround is to parse the string manually with std::sscanf()
and fill the std::tm
struct. The following code does that for your input string i
. See the whole example with the output of std::get_time()
and the std::sscanf()
method at ideone. If the string is different then adapt that solution to your needs.
This solution is using %d:%d:%d %d:%d:%d
as format string. You could also use %4d:%2d:%2d %2d:%2d:%2d
which would accept only numbers which have length smaller or equal than the number between %
and d
.
Output:
2016:07:30 09:27:06 | sscanf() time = 1469870826
2016:07:30 09:27:06 | std::get_time() time = 1469870826
2016:07:30 09:27:07 | sscanf() time = 1469870827
2016:07:30 09:27:07 | std::get_time() time = 1469870827
2016:07:30 09:27:08 | sscanf() time = 1469870828
2016:07:30 09:27:08 | std::get_time() time = 1469870828
Code:
#include <vector>
#include <string>
#include <iostream>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <cstring>
int main()
{
std::vector<std::string> vec;
vec.push_back("2016:07:30 09:27:06");
vec.push_back("2016:07:30 09:27:07");
vec.push_back("2016:07:30 09:27:08");
for (auto & i : vec)
{
struct std::tm tm;
/* std::sscanf() method: */
std::memset(&tm, 0, sizeof(tm));
if (6 != std::sscanf(i.c_str(), "%d:%d:%d %d:%d:%d",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec))
{
return -1;
}
/* correct the numbers according to:
* see: http://en.cppreference.com/w/cpp/chrono/c/tm */
--tm.tm_mon;
tm.tm_year -= 1900;
/* mktime determines if Daylight Saving Time was in effect
* see: http://en.cppreference.com/w/cpp/chrono/c/mktime */
tm.tm_isdst = -1;
std::time_t time = std::mktime(&tm);
std::cout << i << " | sscanf() time = " << time << std::endl;
/************************************************************/
/* std::get_time() method: */
std::istringstream iss;
iss.str(i);
iss >> std::get_time(&tm, "%Y:%m:%d %H:%M:%S");
time = std::mktime(&tm);
std::cout << i << " | std::get_time() time = " << time << std::endl;
}
}
It is necessary to subtract one of the tm_mon
because it begins at 0. Further the tm_year
member are years since 1900. See the description of std::tm
here.
Further it is needed to set tm_isdst
to -1 to let std::mktime()
determine if the Daylight Saving Time was in effect.
To convert the std::time_t
Linux timestamp back to a std::string
of your given format: %Y:%m:%d %H:%M:%S
you can use std::strftime()
with std::localtime(). See live example on ideone.
Output:
time = 1469870826 | 2016:07:30 09:27:06
Code:
#include <vector>
#include <string>
#include <iostream>
#include <ctime>
int main()
{
char buff[20];
time_t timestamp = 1469870826;
std::strftime(buff, sizeof(buff), "%Y:%m:%d %H:%M:%S", std::localtime(×tamp));
std::string timeStr(buff);
std::cout << "time = " << timestamp << " | " << timeStr;
}
there is a bug in the posted code - not in the library. it's a subtle one. replace:
struct std::tm tm;
with:
struct std::tm tm{};
this makes sure tm
gets zero-initialized. after applying that change the output is as expected:
2016:07:30 09:27:06 time = 1469867226
2016:07:30 09:27:07 time = 1469867227
2016:07:30 09:27:08 time = 1469867228
the reason that 2nd and 3rd runs were "correct" was because mktime
updates its parameter via pointer. in particular it sets DST and GMT offset, so after the loop runs for the 1st time, the memory region that gets assigned to tm
already has these set. next loop iteration grabs the same memory region, puts (uninitialized) value and updates only some fields. however with DST and GMT offset already "set", behavior of mktime
changes.
because of this, 1st run is not consistent with 2nd and 3rd.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With