Incorrect time calculation with mktime to get UTC+8 - c++

I want to get the current time in Hong Kong (UTC+8), and my local time is UTC-5.
Using and running the following in VS2012:
#pragma warning(disable : 4996)
char buffer[10];
time_t rawtime;
time(&rawtime);
strftime(buffer, 10, "%H:%M:%S", localtime(&rawtime));
cout << "LocalTime=" << buffer << endl;
strftime(buffer, 10, "%H:%M:%S", gmtime(&rawtime));
cout << "GMTime=" << buffer << endl;
tm* r = gmtime(&rawtime);
r->tm_hour += 8; // Hong Kong time
mktime(r); // Normalize the struct
strftime(buffer, 10, "%H:%M:%S", r);
cout << "HongKongTime=" << buffer << endl;
Produces the following output:
LocalTime=22:51:47
GMTime=02:51:47
HongKongTime=11:51:47
So it's computing UTC correctly, but then adding 8 hours to that is actually producing a time that is UTC +9. What's going wrong?
And is there a more elegant/reliable way of getting UTC+8 than this kludge?

You could use localtime after changing the TZ environment variable to your desired timezone:
#include <iostream>
#include <stdlib.h>
#include <time.h>
int main(){
_putenv_s( "TZ", "GMT-08:00" );
time_t mytime = time( NULL );
struct tm* mytm = localtime( &mytime );
std::cout << "Current local time and date: " << asctime(mytm);
return 0;
}
The object mytime will receive as a result of the function time() the amount of seconds since 00:00 hours, Jan 1, 1970 UTC, which is the current Unix timestamp. localtime() will use the value pointed by mytime to fill a tm structure with the values that represent the corresponding time, expressed for the local timezone.
By default, the timezone used by localtime() is generally the one used in your computer. However, you can change it with the function _putenv_s(), in which I manipulated the TZ variable and added a new definition to it GMT-08:00 which is the timezone for Hong Kong.
In POSIX systems, a user can specify the time zone by means of the TZ
environment variable.
Note that however a more standard way of manipulating TZ variable is by using the function int setenv (const char *name, const char *value, int replace) but it wasn't defined in this sample, so I used an alternative.
You can read more about TZ environment variable here

Related

Have mktime() ignore DST and local time zone in C++

Our system receives data from a vendor in ASCII format "20210715083015". This time is US Eastern time, and is already adjusted for Daylight Savings.
Our backend needs this time in nanoseconds since Epoch. I'm trying to use mktime() to do the conversion, but mktime() insists on adding an hour for DST, even when I force tm_isdst to 0.
Here's the code snippet:
std::tm tmstr{};
<breakdown ASCII time into tm structure>
tmstr.tm_isdst = 0;
cout << "isdst before: " tmstr.tm_isdst;
time_t seconds = std::mktime(&tmstr);
cout << ", isdst after: " tmstr.tm_isdst << endl;
Here's the output:
isdst before: 0, isdst after: 1
It's ignoring the set value of 0, and applying its own conversion.
How do I use mktime(), or something equivalent, without it trying to adjust the time to my timezone? I'd rather not have to internally set timezones, I just want it to do a straight conversion from a tm structure to seconds.
This is g++ version 7.3.1, under Redhat version 6.10.
It's ignoring the set value of 0
No, it is not ignoring that member.
mktime() takes .tm_isdst into account and then adjusts all the struct tm members to their usual values for that date, thus a change to .tm_isdst and .tm_hour is expected in OP's case as the tmstr is in daylight time for "20210715" July 15th.
How do I use mktime(), or something equivalent, without it trying to adjust the time to my timezone?
mktime() is a local time conversion. The usual approach is let mktime() deduce the daylight flag.
tmstr.tm_isdst = -1;
This time is US Eastern time, and is already adjusted for Daylight Savings.
No, you are asserting it is US standard Eastern time (EST). US Eastern time (ET) has daylight adjustments.
Change your time zone from one with daylight and standard periods to a zone with only standard. Research setenv(). Perhaps as simple as:
// Implementation dependent code.
setenv("TZ", "EST5", 1);
tzset();
time_t seconds = mktime(&tmstr);
Key issue missing: was the time_t result correct?
Ignore tmstr for a moment. What time_t value was reported? What time_t value was expected?
I suspect OP did get the right time_t value, just not the expected result in struct tm.
Hopefully, you could use the timegm() function (not to confound with the gmtime() function... see below for more info).
The mktime() function has a state. If the last time it was used with isdst set to 0, then using isdst = -1 will use 0. If the last time you used it with isdst set to 1, then using isdst = -1 will use 1. This is true only if the function is called with a time when the time change happens (within that 1h window). Other functions are not unlikely to change that variable too (i.e. the localtime() may also affect that state, I haven't tested that).
// a date when the time change happened (Sun Oct 26 01:08:48 PST 1980)
time_t et = 341399328;
struct tm t = {};
localtime_r(&et, &t);
struct tm ot = {};
ot.tm_year = t.tm_year;
ot.tm_mon = t.tm_mon;
ot.tm_mday = t.tm_mday;
ot.tm_hour = t.tm_hour;
ot.tm_min = t.tm_min;
ot.tm_sec = t.tm_sec;
ot.tm_isdst = 1;
std::cerr << " +--> " << mktime(&ot) << " (1)\n";
ot.tm_isdst = 0;
std::cerr << " +--> " << mktime(&ot) << " (0)\n";
ot.tm_isdst = -1;
std::cerr << " +--> " << mktime(&ot) << " (-1)\n"; // same as 0
ot.tm_isdst = 0;
std::cerr << " +--> " << mktime(&ot) << " (0)\n";
ot.tm_isdst = 1;
std::cerr << " +--> " << mktime(&ot) << " (1)\n";
ot.tm_isdst = -1;
std::cerr << " +--> " << mktime(&ot) << " (-1)\n"; // same as 1
The output should always be the same as the input (i.e. convert from time_t to struct tm and vice versa). Instead I get:
341399328 -> 1980/10/26 1:8:48
+--> 341395728 (1)
+--> 341399328 (0)
+--> 341399328 (-1)
+--> 341399328 (0)
+--> 341395728 (1)
+--> 341395728 (-1)
So, more or less, you can't trust the mktime() conversions.
There are now two new functions: timegm() and timelocal(). The timelocal() function is the same as the mktime(). The timegm() totally ignores the locale timezone, so you can convert UTC time back and forth properly. The converse being gmtime().
In other words:
time_t to struct tm with gmtime()
struct tm to time_t with timegm()
and the input time_t in (1) will always equal the output time_t in (2).
Easier to post here...
From the mktime() man page, you're not reading what is written...
The mktime() function converts a broken-down time structure, expressed as local time, to calendar time representation. The function ignores the values supplied by the caller in the tm_wday and tm_yday fields. The value specified in the tm_isdst field informs mktime() whether or not daylight saving time (DST) is in effect for the time supplied in the tm structure: a positive value means DST is in effect; zero means that DST is not in effect; and a negative value means that mktime() should (use timezone information and system databases to) attempt to determine whether DST is in effect at the specified time.
The mktime() function modifies the fields of the tm structure as follows: tm_wday and tm_yday are set to values determined from the contents of the other fields; if structure members are outside their valid interval, they will be normalized (so that, for example, 40 October is changed into 9 November); tm_isdst is set (regardless of its initial value) to a positive value or to 0, respectively, to indicate whether DST is or is not in effect at the specified time. Calling mktime() also sets the external variable tzname with information about the current timezone.

localtime_s fails where gmtime_s succeeds with dates before 1-1-1970

I'm trying to get the current year stored in a date from before 1970 using an std::chrono::time_point<std::chrono::system_clock>, however I've run into an issue regarding the reading from its contents into a std::tm struct.
I convert the time_point to a time_t first, after which I read its values to get the tm_year value. However, when trying to do so, the code fails when using localtime_s, however it succeeds when I'm using gmtime_s. This is only for dates before 1-1-1970, dates after that work fine using both functions.
The code below reproduces the error. If terstGmTimeVsLocalTime is called with utc=true it works, if it is called with utc=false it doesn't produce the correct output.
#include <iomanip>
#include <time.h>
#include <iostream>
void testGmTimeVsLocaltime(const bool& utc) {
// Create time
std::tm timeInfoWrite = std::tm();
timeInfoWrite.tm_year = 1969 - 1900; // Year to parse, here it is 1969
timeInfoWrite.tm_mon = 0;
timeInfoWrite.tm_mday = 1;
timeInfoWrite.tm_hour = 1;
timeInfoWrite.tm_min = 0;
timeInfoWrite.tm_sec = 0;
timeInfoWrite.tm_isdst = -1;
std::chrono::time_point<std::chrono::system_clock> timePoint = std::chrono::system_clock::from_time_t(utc ? _mkgmtime(&timeInfoWrite) : std::mktime(&timeInfoWrite));
// Convert to time_t
std::time_t timeT = std::chrono::system_clock::to_time_t(timePoint);
// Read values
std::tm timeInfoRead;
if (utc) {
gmtime_s(&timeInfoRead, &timeT);
} else {
localtime_s(&timeInfoRead, &timeT);
}
// Output result
std::cout << (timeInfoRead.tm_year + 1900) << '\n';
// Wait for input
std::getchar();
}
int main() {
testGmTimeVsLocaltime(true); // Set to false to show bug
return 0;
}
utc=true outputs 1969, as would be expected. However, utc=false outputs 1899 (presumably since an error occurs and tm_year gets set to -1).
Is there anything I'm missing? The documentation doesn't specifically specify that localtime_s should fail for dates before 1-1-1970.
I'm on Windows 10 x64 if it makes a difference.
Using Howard Hinnant's free, open-source date lib, you can completely side-step the clumsy, error and bug-prone C api, and work directly with a modern <chrono>-based system:
#include "chrono_io.h"
#include "date.h"
#include <iostream>
void
testGmTimeVsLocaltime()
{
using namespace date;
// Create time
auto timeInfoWrite = 1969_y/jan/1;
sys_days timePoint = timeInfoWrite; // this is a chrono::time_point
std::cout << timePoint.time_since_epoch() << '\n'; // -365 days
// Convert to time_t
// no need
// Read values
year_month_day timeInfoRead = timePoint;
// Output result
std::cout << timeInfoRead.year() << '\n';
}
int
main()
{
testGmTimeVsLocaltime();
}
Output:
-365[86400]s
1969
There are literals to make it easy to fill in a year_month_day struct which is the analog to the year, month and day parts of a tm. You can easily convert this to a std::chrono::time_point<system_clock, days> (sys_days). This is the same as a system_clock::time_point, but with a precision of days instead. It itself will implicitly convert to a seconds-precision time_point (typedef'd to sys_seconds), or to a system_clock::time_point.
Above I just output its time_since_epoch() which shows that it is -365 days prior to the epoch.
There's never really any need to convert to C API data structures, but it is easy if you want to. For example, assuming time_t is seconds since 1970-01-01:
std::time_t timeT = sys_seconds{timePoint}.time_since_epoch().count();
std::cout << timeT << '\n';
which outputs:
-31536000
The reverse conversion (back to year_month_day) is just as easy. If you want to convert from timeT it is just slightly more involved:
year_month_day timeInfoRead = floor<days>(sys_seconds{seconds{timeT}});
This first converts time_t to chrono::seconds, and then to a seconds-precsion time_point, and then to a days-precsion time_point, finally to the year_month_day field type (tm-like).
Finally year_month_day has a year() getter member function which is streamable. You can explicitly convert year to int if desired:
int{timeInfoRead.year()}
But I think it best to keep things like years, months and days as distinct types so that the compiler can help you catch when you accidentally mix them up.
Finally, if you really meant that you wanted 1969-01-01 00:00:00 in your computer's local timezone, there's a library to do that as well. And it is just a minor modification of the simple program above.
#include "tz.h"
#include <iostream>
void
testGmTimeVsLocaltime()
{
using namespace date;
using namespace std::chrono;
// Create time
auto timeInfoWrite = 1969_y/jan/1;
auto timePoint = make_zoned(current_zone(), local_days{timeInfoWrite});
// Convert to time_t
std::time_t timeT = timePoint.get_sys_time().time_since_epoch().count();
std::cout << timeT << '\n';
// Read values
timePoint = sys_seconds{seconds{timeT}};
year_month_day timeInfoRead{floor<days>(timePoint.get_local_time())};
// Output result
std::cout << timeInfoRead.year() << '\n';
}
int
main()
{
testGmTimeVsLocaltime();
}
Output:
-31518000
1969
Now you create a zoned_seconds using the computer's current_zone() timezone, and converting your timeInfoWrite to local_days instead of to sys_days.
You can get the local time or the system time out of timePoint. For converting to time_t, system time makes the most sense:
std::time_t timeT = timePoint.get_sys_time().time_since_epoch().count();
And now the output (for me) is 5h later (18000s).
-31518000
You can get back either the local year, or the system (UTC) year, by either using .get_local_time() or .get_sys_time(). For me it makes no difference ("America/New_York"). But if you're in "Australia/Sydney", you'll get 1968 if you request the UTC year instead of 1969. And that's all very easy to simulate by simply substituting "Australia/Sydney" or "America/New_York" for current_zone() in the program above.
Yes, it works on Windows, VS-2013 and later. There is some installation required for the timezone lib: https://howardhinnant.github.io/date/tz.html#Installation

Convert time_t from localtime zone to UTC

I have a time_t that represents the time in seconds since epoch. Those seconds refer to the local time.
I want to convert them to UTC.
Is there a way to do this in C++?
I'm going to show two ways of doing this:
Using the C API.
Using a modern C++11/14 library based on top of <chrono>.
For the purposes of this demo, I'm assuming that the current number of seconds in the local time zone is 1,470,003,841. My local time zone is America/New_York, and so the results I get reflect that we are currently at -0400 UTC.
First the C API:
This API is not type-safe and is very error prone. I made several mistakes just while coding up this answer, but I was able to quickly detect these mistakes because I was checking the answers against the 2nd technique.
#include <ctime>
#include <iostream>
int
main()
{
std::time_t lt = 1470003841;
auto local_field = *std::gmtime(&lt);
local_field.tm_isdst = -1;
auto utc = std::mktime(&local_field);
std::cout << utc << '\n'; // 1470018241
char buf[30];
std::strftime(buf, sizeof(buf), "%F %T %Z\n", &local_field);
std::cout << buf;
auto utc_field = *std::gmtime(&utc);
std::strftime(buf, sizeof(buf), "%F %T UTC\n", &utc_field);
std::cout << buf;
}
First I initialize the time_t. Now there is no C API to go from a local time_t to a UTC time_t. However you can use gmtime to go from a UTC time_t to a UTC tm (from serial to field type, all in UTC). So the first step is to lie to gmtime, telling it you've got a UTC time_t. And then when you get the result back you just pretend you've got a local tm instead of a UTC tm. Clear so far? This is:
auto local_field = *std::gmtime(&lt);
Now before you go (and I personally messed this part up the first time through) you have to augment this field type to say that you don't know if it is currently daylight saving or not. This causes subsequent steps to figure that out for you:
local_field.tm_isdst = -1;
Next you can use make_time to convert a local tm to a UTC time_t:
auto utc = std::mktime(&local_field);
You can print that out, and for me it is:
1470018241
which is 4h greater. The rest of the function is to print out these times in human readable format so that you can debug this stuff. For me it output:
2016-07-31 22:24:01 EDT
2016-08-01 02:24:01 UTC
A modern C++ API:
There exist no facilities in the std::lib to do this. However you can use this free, open source (MIT license) library for this.
#include "date/tz.h"
#include <iostream>
int
main()
{
using namespace date;
using namespace std::chrono_literals;
auto zt = make_zoned(current_zone(), local_seconds{1470003841s});
std::cout << zt.get_sys_time().time_since_epoch() << '\n'; // 1470018241s
std::cout << zt << '\n';
std::cout << zt.get_sys_time() << " UTC\n";
}
The first step is to create the local time in terms of seconds since the epoch:
local_seconds{1470003841s}
The next thing to do is to create a zoned_time which is a pairing of this local time and the current time zone:
auto zt = make_zoned(current_zone(), local_seconds(1470003841s));
Then you can simply print out the UTC number of seconds of this pairing:
std::cout << zt.get_sys_time().time_since_epoch() << '\n';
This output for me:
1470018241s
(4h later than the input). To print out this result as I did in the C API:
std::cout << zt << '\n';
std::cout << zt.get_sys_time() << " UTC\n";
which outputs:
2016-07-31 22:24:01 EDT
2016-08-01 02:24:01 UTC
In this modern C++ approach, the local time and the UTC time are different types, making it much more likely that I catch accidental mixing of these two concepts at compile time (as opposed to creating run time errors).
Update for C++20
The second technique will be available in C++20 with the following syntax:
#include <chrono>
#include <iostream>
int
main()
{
using namespace std::chrono;
zoned_time zt{current_zone(), local_seconds{1470003841s}};
std::cout << zt.get_sys_time().time_since_epoch() << '\n'; // 1470018241s
std::cout << zt << '\n';
std::cout << zt.get_sys_time() << " UTC\n";
}
You can use gmtime:
Convert time_t to tm as UTC time Uses the value pointed by timer to
fill a tm structure with the values that represent the corresponding
time, expressed as a UTC time (i.e., the time at the GMT timezone).
(c) http://www.cplusplus.com/reference/ctime/gmtime/
If you are okay with using Abseil's time library, one other way to do this is:
auto civil_second =
absl::LocalTimeZone().At(absl::FromTimeT(<your time_t>)).cs;
time_t time_in_utc = absl::ToTimeT(absl::FromCivil(civil_second, absl::UTCTimeZone()));
(Maybe there is a simpler set of calls in the library to do this, but I have not explored further. :))
Normaly, you would convert from time_t to struct tm and there aren't many examples of converting from time_t to time_t in a different time zone (UTC in case of the OP's question). I wrote these 2 functions for that exact purpose. They may be useful when you are only in a need ot using time_t but in a specific time zone.
time_t TimeAsGMT(time_t t)
{
std::chrono::zoned_time zt{"UTC", std::chrono::system_clock::from_time_t(t)};
return std::chrono::system_clock::to_time_t(zt.get_sys_time());
}
or if you want the current time as UTC in the form of time_t
time_t CurTimeAsGMT()
{
std::chrono::zoned_time zt{"UTC", std::chrono::system_clock::now()}; // Get the time in UTC time zone
return std::chrono::system_clock::to_time_t(zt.get_sys_time()); // return this time as time_t
}
If you run both functions and compare the initial value and the result value, you will see that the difference matches the difference between your current time (at your current time zone) and UTC / GMT time zone.

Confusing behaviour of mktime() function : increasing tm_hour count by one

I am executing below code.
int main()
{
struct tm storage={0,0,0,0,0,0,0,0,0};
char *p = NULL;
p = (char *)strptime("2012-08-25 12:23:12","%Y-%m-%d %H:%M:%S",&storage);
char buff[1024]={0};
strftime(buff,1024,"%Y-%m-%d %H:%M:%S",&storage);
cout << buff << endl;
storage.tm_sec += 20;
strftime(buff,1024,"%Y-%m-%d %H:%M:%S",&storage);
cout << buff << endl;
mktime(&storage);
strftime(buff,1024,"%Y-%m-%d %H:%M:%S",&storage);
cout << buff << endl;
return 0;
}
If above Program executed, It prints ' 2012-08-25 13:23:32' instead of '2012-08-25 12:23:32'. Please Help, why it is increasing tm_hour value.
This works correctly if I put input date as '2012-02-25 12:23:32' in program, which is confusing.
OUtput ->
[user#rtpkvm55-vm2 root]$ ./a.out
2012-08-25 12:23:12
2012-08-25 12:23:32
2012-08-25 13:23:32
[user#rtpkvm55-vm2 root]$
Date Info on my system, -->
[user#rtpkvm55-vm2 root]$ date
Sat Aug 25 08:28:26 EDT 2012
What happens
The date you specified has daylight savings in effect but when calling mktime, storage.tm_isdst is zero. mktime sees this and thinks "hey, they gave me a date with an incorrect daylight savings flag, lets fix it". Then it sets tm_isdst to 1 and changes tm_hour.
See also this answer.
To fix it
use timegm instead of mktime
set the timezone to UTC before calling mktime (see also example from timegm) :
setenv("TZ", "", 1);
tzset();
mktime();
use a good date-time library (like boost::locale::date_time/boost::date_time, but read the Q&A section on the boost::locale::date_time page before picking one)
Wow, there just is no way around it. It must be a bug in your system's implementation of mktime(3). mktime(3) should not alter the struct tm * passed to it.
I would suggest checking the value of storage.tm_isdst. Try setting it to 0 to ensure it's not confused about DST. If that doesn't work, try setting it to -1 to let it auto determine the proper value.
mktime - convert broken-down time into time since the Epoch
A positive or 0 value for tm_isdst causes mktime() to presume initially that Daylight Savings Time, respectively, is or is not in effect for the specified time. A negative value for tm_isdst causes mktime() to attempt to determine whether Daylight Saving Time is in effect for the specified time.
I was wrong about mktime(3) not modifying struct tm *. It is the correct behavior to normalize the value.
You have to set tm_isdst in the tm struct otherwise it is uninitialised, and thus gets set to a random garbage value. Then, when you call mktime depending on which random garbage is in tm_isdst variable, it either applies daylight saving time or it doesn't, seemingly unpredictably.
However, if you set it to -1, you tell mktime that you don't know whether daylight saving time is in effect, so the first call to mktime will fix it.
Therefore, the simplest way to fix this issue is adding:
storage.tm_isdst = -1;
before calling mktime.
Here is a trick to fix your code:
int main()
{
// Need to know if daylight saving is on or off, use the following trick
// Get the current time in seconds since epoch, convert it to local time,
// tm_isdst(is daylight saving) value, in the tm variable returned by the localtime(), will be set accordingly
time_t now = time(0);
struct tm *tm2= localtime(&now);
struct tm storage={0,0,0,0,0,0,0,0,tm2->tm_isdst}; // Note: used the is daylight saving on flag fetched above
char *p = NULL;
p = (char *)strptime("2012-08-25 12:23:12","%Y-%m-%d %H:%M:%S",&storage);
char buff[1024]={0};
strftime(buff,1024,"%Y-%m-%d %H:%M:%S",&storage);
cout << buff << endl;
storage.tm_sec += 20;
strftime(buff,1024,"%Y-%m-%d %H:%M:%S",&storage);
cout << buff << endl;
mktime(&storage);
strftime(buff,1024,"%Y-%m-%d %H:%M:%S",&storage);
cout << buff << endl;
return 0;
}

Calculating minutes from now until something is due

I have a project that needs me to accept an input for when (during the same day, I assume) an assignment is due. I was trying to follow some code from a similar question but it's giving me an error, which I assume has to do with time_t and int values. Here's my code:
#include <iostream>
#include <ctime>
using namespace std;
int main() {
int hour_input,min_input;
cout << "What hour is your assignment due?\n";
cin >> hour_input;
cout << "What minute is your assignmnet due?\n";
cin >> min_input;
struct tm* tm;
time_t ts = time(NULL);
long int delta;
tm->tm_hour = hour_input;
tm->tm_min = min_input;
delta = mktime(tm) - ts;
delta += 24*60*60;
cout << "There are "<< delta << " minutes until your assignment is due!\n";
return 0;
}
What I'm looking for is some guidance on how to use the functions within the <ctime> header properly, thanks for your help!
double deltaMinutes = difftime(later,earlier) / 60.0;
I'd initialize each time struct to the current date. Note, that 1970 is designated by 70 in member tm::tm_year.
http://www.cplusplus.com/reference/clibrary/ctime/difftime/
The first error is you creating a pointer to struct tm but not pointing it to anything. You don't need a pointer:
struct tm tm;
/* set fields */
time_t timestamp = mktime(&tm);
The second error is that mktime expects a complete data/time in the structure. If the year, month and day all is zero then the returned time will be the first year, month and day. The call may actually fail because date must be between 1 and 31.
This means it's not easy to use struct tm for differences. Instead use difftime as suggested by Sam.
Removing the salient bits from your code (and your declarations are in a very strange order)
struct tm* tm;
tm->tm_hour = hour_input;
tm->tm_min = min_input;
delta = mktime(tm) - ts;
So, tm is uninitialised and pointing to a random location. Moreover, you don't fill the structure in properly, and you have a random (probably invalid) year, month, day and seconds.
mktime will try to make this valid, but if seconds is sufficiently large it'll cause real havoc with your hours and minutes. Assuming the program doesn't crash before it gets that far. You didn't say what error you were getting.