I am using QtCharts to display simulation data. The simulation starts at time zero, but my chart axis always seems to start at 19 hours. This confounds me. The set up of the chart is straight forward:
std::vector<SimData> data;
// ... Populate data
auto series = new QLineSeries();
for(auto i : data)
{
// Append time in milliseconds and a value
series->append(i.msTime, i.value);
}
this->legend()->hide();
this->addSeries(series);
this->axisX = new QDateTimeAxis;
this->axisX->setTickCount(10);
this->axisX->setFormat("HH:mm:ss");
this->axisX->setTitleText("Sim Time");
this->axisX->setMin(QDateTime());
this->addAxis(this->axisX, Qt::AlignBottom);
series->attachAxis(this->axisX);
this->axisY = new QValueAxis;
this->axisY->setLabelFormat("%i");
this->axisY->setTitleText(x->getID().c_str());
this->addAxis(this->axisY, Qt::AlignLeft);
series->attachAxis(this->axisY);
If I run with no data, but just display the chart, I get this:
If I add data, starting at time zero, the total amount of data is correct, but the time still starts at 19:00:00. Why does the time not start at 00:00:00?
I believe this is because you are on the East Coast (UTC-5) so while 0 represents 12am (2400) at UTC-5 0ms would represent 5 hours earlier (1900 the previous day). I was having the same problem, set my time zone to UTC (under ubuntu) and voila axis started at 0 hours instead of 19.
The problem was indeed confirmed to be UTC offset. SO had a good example of how to get the UTC Offset which I then used to offset the data going into the chart:
Easy way to convert a struct tm (expressed in UTC) to time_t type
I created a utility function from this to use with QDateTimeAxis series data.
double GetUTCOffsetForQDateTimeAxis()
{
time_t zero = 24 * 60 * 60L;
struct tm* timeptr;
int gmtime_hours;
// get the local time for Jan 2, 1900 00:00 UTC
timeptr = localtime(&zero);
gmtime_hours = timeptr->tm_hour;
// if the local time is the "day before" the UTC, subtract 24 hours
// from the hours to get the UTC offset
if(timeptr->tm_mday < 2)
{
gmtime_hours -= 24;
}
return 24.0 + gmtime_hours;
}
Then the data conversion was simple.
std::vector<SimData> data;
// ... Populate data
auto series = new QLineSeries();
const auto utcOffset = sec2ms(hours2sec(GetUTCOffsetForQDateTimeAxis()));
for(auto i : data)
{
// Append time in milliseconds and a value
series->append(i.msTime - utcOffset, i.value);
}
// ...
For the lonesome wanderer that probably ends up here.
My KISS solution is actually setting the time, then some a second and finally add a new data point:
for(int i = 0; i <= points; i++) {
QDateTime timeStamp;
timeStamp.setDate(QDate(1980, 1, 1));
timeStamp.setTime(QTime(0, 0, 0));
timeStamp = timeStamp.addSecs(i);
data->append(timeStamp.toMSecsSinceEpoch(), /* your y here */);
}
later where the diagram gets painted I used:
QSplineSeries *temps1 = /* wherever you get your series */;
QChart *chTemp = new QChart();
tempAxisX->setTickCount(5);
tempAxisX->setFormat(QString("hh:mm:ss"));
tempAxisX->setTitleText("Time");
chTemp->addAxis(tempAxisX, Qt::AlignBottom);
temps1->attachAxis(tempAxisX);
Hope that that helps a future visitor once (including myself).
For the lonesome wanderer blah blah. As solution of Tillo Reilly didn't work for me, I did my own (which works). In addition, my code converts timestamp to particular timezone. The thing is - QtCharts don't knows nothing about timezones (but better should to) but know and force-uses local PC timezone in a wierd way. So, if you want to change displayed date/time on charts to specified timezone - the only thing you can do is to modify dataset.
Answer to original question
auto temp_time = QDateTime::fromSecsSinceEpoch( timestamp );
auto local_offset = temp_time.offsetFromUtc();
auto fixed_timestamp = timestamp - local_offset;
With specified timezone
auto temp_time = QDateTime::fromSecsSinceEpoch( timestamp );
auto local_offset = temp_time.offsetFromUtc();
temp_time.setTimeSpec( Qt::TimeZone );
temp_time.setTimeZone( QTimeZone("Europe/Moscow") ); //for example
auto timezone_offset = temp_time.offsetFromUtc();
auto fixed_timestamp = timestamp + timezone_offset - local_offset;
important notice 1: you shouldn't create any random QDateTime object to calculate offsets, for example with functions like QDateTime::currentDateTime() or QDateTime::fromSecsSinceEpoch(0) - offset from UTC depends on point in time! So, with functions above you'll get QDateTime object, but its offset from UTC may differ from what you expect.
For the very same reason, you shouldn't calculate offset once and use it for all the dataset, especially for datasets with significant time ranges. The larger your dataset size - the bigger chances are that you'll get into trouble with unexpected Daylight Saving Time somewhere in the middle of it.
important notice 2: I don't know if QtCharts calculates offset from UTC for local timezone for current time or for timestamp passed to it via data series. I really hope it's the latter.
Related
TL;DR: How to use a std::chrono::system_clock::time_point to compare based on only certain parameters (e.g. I just want hours, minutes and seconds, but not day, month, etc.).
Also: After converting the std::chrono::system_clock::time_point to a std::tm, the std::tm.tm_hours contains a value one higher than originally input to the std::chrono::system_clock::time_point.
My theoretical approach on getting a std::chrono::system_clock::time_point to work:
typedef std::chrono::system_clock::time_point TimePoint;
TimePoint MainWindow::createTimePoint(int h, int m)
{
TimePoint createdTime = std::chrono::system_clock::time_point{std::chrono::hours(h) + std::chrono::minutes(m)};
time_t tt = std::chrono::system_clock::to_time_t(createdTime);
tm timeExtracted = *localtime(&tt);
std::cout << "input:\t\t" << "H = " << h << ", M = " << m << std::endl;
std::cout << "timeExtracted:\t" << "H = " << timeExtracted.tm_hour << ", M = " << timeExtracted.tm_min << std::endl;
return createdTime;
}
If I run this, the hours of timeExtracted are always +1 from the input h.
Why is that so? And how to fix this? I went over a few other posts that showed this, but they couldnt help me. Probably also because of this:
I think that when I create a TimePoint, the day, month, etc. is also set to a random value or initiated to a certain value. The point is: I want them to always be the same value, so that my TimePoint (after converting) basically shows this:
timeExtracted.tm_sec = 0
timeExtracted.tm_min = m
timeExtracted.tm_hour = h
timeExtracted.tm_mon = 0
timeExtracted.tm_wday = 0
timeExtracted.tm_mday = 0
timeExtracted.tm_yday = 0
timeExtracted.tm_year = 0
timeExtracted.tm_isdst = 0
How can I compare two of these TimePoint utilising using the compare operations of std::chrono on them, but only compare the hour and minute.
If my question is unclear, I'm sorry, it's late in the evening. I'll check again next morning. Thank you.
I'm going to start an answer, but this isn't going to be a complete answer because I'm not yet sure of the complete question. However, I can help.
TimePoint createdTime = system_clock::time_point{hours(h) + minutes(m)};
(I've clipped the std::chrono:: qualifiers so that this is easier to read and discuss)
This creates a time stamp that is 1970-01-01 hh:mm:00 UTC. In a nutshell, system_clock::time_point is measuring the duration of time (in some units like microseconds or nanoseconds) since New Years 1970, UTC. Technically the above is an approximation, system_clock doesn't count leap seconds, but we can (and should) ignore that detail for now.
This:
tm timeExtracted = *localtime(&tt);
is going to introduce UTC offset corrections based on your computer's setting for the local time zone. The time zone adjustment rules are (hopefully) going to be based on what was in effect in 1970 in your area.
There exist techniques and libraries for taking a system_clock::time_point and breaking it up into fields such as {year, month, day, hours, minutes, seconds, microseconds}. But that conversion also depends on if you want these fields in UTC, local time, or some other arbitrary time zone.
And the very first step is to apply the UTC offset associated with some time zone if desired. It may be that your {h, m} input needs a UTC offset adjustment prior to putting them into system_clock::time_point if the intent is that {h, m} represent local time instead of UTC.
Update: Store hours example
This example will use my free, open-source time zone library, because I feel it is much easier to work with and allows for more readable and expressive code.
This example takes as input a system_clock::time_point and compares it to a list of open/close times for each day of the week and determines if the input time is inside or outside of those time-of-day ranges for the weekday associated with the input time t. The store hours are presumed to be stated with respect to the store's local time zone, which is also the current time zone set for the computer running this code.
#include "date/tz.h"
#include <algorithm>
#include <cassert>
#include <chrono>
bool
is_store_open_at(std::chrono::system_clock::time_point tp)
{
using namespace date;
using namespace std::chrono;
struct day_schedule
{
weekday wd;
minutes open;
minutes close;
};
// hours are expressed in terms of local time
static constexpr day_schedule store_hours[]
{
// week day open-time close-time
{Monday, 0h, 0h}, // closed all day
{Tuesday, 8h, 18h},
{Wednesday, 8h, 18h},
{Thursday, 8h, 18h},
{Friday, 8h, 18h},
{Saturday, 8h, 15h+30min},
{Sunday, 9h+30min, 15h}
};
auto local_tp = current_zone()->to_local(tp);
auto local_day = floor<days>(local_tp);
auto local_time_of_day = local_tp - local_day;
weekday local_weekday{local_day};
auto ds = std::find_if(std::begin(store_hours), std::end(store_hours),
[local_weekday](day_schedule const& x)
{
return x.wd == local_weekday;
});
assert(ds != std::end(store_hours));
return ds->open <= local_time_of_day && local_time_of_day < ds->close;
}
#include <iostream>
int
main()
{
std::cout << is_store_open_at(std::chrono::system_clock::now()) << '\n';
}
The function begins by defining some handy data structures to store the open and close times for each day of the week. The open and close members of day_schedule measure "minutes since midnight" in local time.
The input time tp is in terms of UTC, since its type is system_clock::time_point. This is not currently specified by the C++ standard, but will be for next year's C++20.
zoned_seconds is used to convert the UTC time t into local time according to the computers time zone setting obtained by calling current_zone(). I've truncated t to seconds to simplify some of the syntax. This isn't strictly necessary. I've edited to use slightly simpler syntax to eliminate the zoned_seconds. zoned_seconds can be really useful in other examples, but in this one was more trouble than it was worth. auto local_tp = current_zone()->to_local(tp) is a simpler way to translate UTC to a local time point.
local_tp is a chrono::time_point that is considered "local time", and is distinct from the family of chrono::time_points associated with system_clock. The advantage of doing this is so that if local time and UTC time are accidentally mixed, it is a compile-time error.
local_days is simply local_tp truncated to days precision. It is still a chrono::time_point, just a coarse one that points to the beginning of the day as described by the local time zone.
The time duration since the local midnight is simply local_tp - local_day.
The day of the week (as defined by the local time zone) can be obtained by converting local_day to type weekday. This is the local day of the week associated with tp.
Now it is a simple matter to search store_hours for the entry that matches local_weekday.
The store is open if local_time_of_day is at or past the open time and has not yet reached the close time.
If the "store hours" are specified in UTC instead of local time, then this program simplifies somewhat, but is still similar.
I would like to use the ICU library to obtain the current time by given timezone offset (also need to calculate the daylight saving), I've tried below codes, but the snext function return NULL, the ICU library version is 6.1, CentOS 7.6 64 bit + gcc 6.4.1.
timezoneOffset = 1; //(UTC + 1)
UErrorCode success = U_ZERO_ERROR;
U_ICU_NAMESPACE::UnicodeString dateReturned, curTZNameEn, curTZNameFr;
int32_t stdOffset = 0;
int32_t dstOffset = 0;
//
// Create a Calendar to get current date
U_ICU_NAMESPACE::Calendar* calendar = U_ICU_NAMESPACE::Calendar::createInstance(success);
if (!calendar)
{
return;
}
success = U_ZERO_ERROR;
UErrorCode ec;
std::string errorName;
int32_t rawOffset = timezoneOffset * 3600 * 1000;
U_ICU_NAMESPACE::StringEnumeration* se = U_ICU_NAMESPACE::TimeZone::createEnumeration(rawOffset); // Obtain timezones by GMT timezone offset
if (se)
{
auto next = se->snext(ec);
if (next && ec == U_ZERO_ERROR)
{
U_ICU_NAMESPACE::TimeZone *tzWest = U_ICU_NAMESPACE::TimeZone::createTimeZone(*next);
if (tzWest)
{
UDate curDate = calendar->getNow();
tzWest->getOffset(curDate, false, stdOffset, dstOffset, success);
if (U_SUCCESS(success))
{
timezoneOffset = (stdOffset / (1000 * 60 * 60) + dstOffset / (1000 * 60 * 60));
}
}
}
else
{
errorName = u_errorName(ec); // The error name is "bogus error"
}
delete se;
}
delete calendar;
From the ICU documentation:
There may be several times zones with the same GMT offset that differ in the way they handle daylight savings time. For example, the state of Arizona doesn't observe daylight savings time. If you ask for the time zone IDs corresponding to GMT-7:00, you'll get back an enumeration over two time zone IDs: "America/Denver," which corresponds to Mountain Standard Time in the winter and Mountain Daylight Time in the summer, and "America/Phoenix", which corresponds to Mountain Standard Time year-round, even in the summer.
In other words, you can't calculate daylight saving time properly if all you have for input is an offset. Many different zones may share this offset, some may use it at standard time, some may use it at daylight time, and some may use it all year long.
Also understand that different parts of the world use DST differently. Some start earlier or later than the US, and some don't use it at all. Those that use it in the Southern hemisphere are usually on winter time while the Northern hemisphere is on summer time, and vice versa. One time zone only switches by 30 minutes instead of the usual 60. There is no single worldwide implementation of DST.
In your code, you are creating an enumeration of zones that use the given offset during standard time (or all year), but you are only examining the first item of the enumeration. There are no assurances that the first item is the correct one to use.
The best you could do would be to get the current time for the offset that you have, and you don't really need ICU to do that.
I created my own DateTime class. It accepts the current date/time, and it also accepts a custom date/time. The custom date/time is what I'm interested in.
If I set the date to 1/5/1953 with a time of 1:05:31 PM, and call updateTime(), I want the time to update based on the difference between when it was first created and how many milliseconds followed afterwards.
However, when I do this, it's always giving me today's date and time, which is not the desired result.
This is my current code.
if (m_isCustomDate)
{
time_t currentRawTime;
// Get the current raw time
time(¤tRawTime);
// Get the time lapse
time_t time_diff = (time_t)difftime(currentRawTime, m_rawTime);
// Increment the time difference to the old raw time
m_rawTime += time_diff;
// Update the tm structure
localtime_s(&m_tm, &m_rawTime);
}
Updated problem:
With a date of date to 1/5/1953 and with a time of 1:05:10 PM, and when I call getSecond(), it's not giving me the 10 as I expect, but it gives me the current second on my computer (4 in this case). Is localtime_s() not the right function to use in this case?
My getSecond() function:
/// <summary>
/// Gets the current second between 0 and 60.
/// </summary>
/// <returns>Returns the second.</returns>
int DateTime::getSecond()
{
updateTime();
return m_tm.tm_sec;
}
Updated specific question:
How can I get the time lapse between the original custom date (m_rawTime) and the time lapse since the app's start up, and then update the tm structure?
Edit:
This solution worked. Posting if anyone needs a working example:
const DWORD curr_time = GetTickCount();
DWORD time_diff = (curr_time - m_init_time) / 1000;
m_rawTime += time_diff;
localtime_s(&m_tm, &m_rawTime);
m_init_time = curr_time;
Given that m_rawTime holds the 'custom' time, I noticed the following. With time_t time_diff = (time_t)difftime(currentRawTime, m_rawTime); you get the difference between the current time and m_rawTime. Then, with m_rawTime += time_diff; you make m_rawTime equal to the current time. I think, this is not what you want to do.
You say you want the time to "update based on the difference between when it was first created and how many milliseconds followed afterwards". So, you effectively want the difference between the time the object was last updated and the current time. To do that, initialize some counter (say, this->init_time) to the current time in the constructor and make each call to updateTime add the difference between the current time and this->init_time and make the latter equal to that current time:
void DateTime::updateTime() {
const auto curr_time = get_time(); // this is not an actual function
const auto diff = curr_time - this->init_time;
m_rawTime += diff;
// update the tm structure here...
this->init_time = curr_time;
}
Now, if you want to work with milliseconds, microseconds or smaller time periods, you should use std::chrono::high_resolution_clock, but struct tm doesn't support time periods shorter than one second, so you can't actually make your custom time any more precise than that with it. In other words, if you stick to struct tm, you can only work with precision of one second, no more.
(i'm using C++Builder 2006, if this matters)
I'm not able to solve this problem:
What i have (and i cannot change this):
typedef struct {
uint16_t Leaps; // How many Leaps from the previous event (see below)
uint16_t Ticks; // Event "machine ticks" (see below)
uint16_t Code;
} sMachineEvents;
typedef struct {
TDateTime Date;
uint16_t Code;
} sConvertEvents;
TDateTime Sync // Contains the date and time of Ev1
TICKS_PER_SECOND // #defined elsewhere: How many Ticks in a second
TICKS_PER_LEAP // #defined elsewhere: How many ticks to make a "Leap"
// (this means that when the Tick counter reaches TICKS_PER_LEAP
// it becomes 0 and the Leaps counter increases by 1)
sMachineEvents Ev[3];
sConvertEvents cEv[3];
Ev[0].Leaps = 0x0005;
Ev[0].Ticks = 0x5975;
Ev[0].Code = 0x0001;
Ev[1].Leaps = 0x0001;
Ev[1].Ticks = 0x0124;
Ev[1].Code = 0x0002;
Ev[2].Leaps = 0x0000;
Ev[2].Ticks = 0x70AC;
Ev[2].Code = 0x0003;
I need to "convert" these "MachineEvents" in "ConvertEvents".
The first one is easy:
cEv[0].Date = Sync;
cEv[0].Code = Ev[0].Code;
Now: Ev[1] happened BEFORE Ev[0].
How much? I need to go "back in time" by Ev[0].Leaps+Ev[0].Ticks and then go "forward in time" by Ev[1].Ticks.
Ev[2] is the same: it happened
Ev[1].Leaps + Ev[1].Ticks - Ev[2].Ticks
BEFORE Ev[1]....
HOW should I compute the DateTime for Ev[1] and Ev[2]?
TDateTime is actually a floating point value representing the number of days; an hour is represented by 1.0 / 24.0, and a second is represented by 1.0 / SecsPerDay (SecsPerDay is a constant equal to 60 * 60 * 24 = 86400).
So, if one tick is 1.0 / TICKS_PER_SECOND seconds:
TDateTime TimeAsTDateTime = TimeInTicks / (SecsPerDay * TICKS_PER_SECOND)
Also, I think your code is not correct: instead of Ev[0].Leaps+Ev[0].Ticks, as far as I can see you need to use Ev[0].Leaps * TICKS_PER_LEAP + Ev[0].Ticks.
If I understand you correct, you compare "ticks" with "time".
You have to convert the ticks in time format, done by tick_count / TICKS_PER_SECOND
So float Seconds = Tick / TICKS_PER_SECOND. As it is a float, you might better use Milliseconds
int ms = (int)(ticks / TICKS_PER_SECOND * 1000);
With the Seconds (or Milliseconds) you can create a new Date object and add the two Date Objects, or simply add the seconds to the old Date.
Another way would be saving all Ticks since start of the program and simply using this as "Date Reference".
It's unbelievable how difficult the above is to accomplish in C++. I'm looking for a way to do this as efficiently as possible while still maintaining millisecond precision.
The solutions I have so far have either required a lot of code and function calls making the implementation slow, or they require me to change the code twice a year to account for daylight savings time.
The computer this will be running on is synced using ntp and should have direct access to the local time adjusted for DST. Can somebody with expertise on this share some solutions?
My platform is CentOS5, g++ 4.1.2, Boost 1.45, solution doesn't need to be portable, can be platform specific. It just needs to be quick and avoid twice a year code changing.
New answer for old question.
Rationale for new answer: We have better tools now.
I'm assuming the desired result is "actual" milliseconds since the local midnight (getting the correct answer when there has been a UTC offset change since midnight).
A modern answer based on <chrono> and using this free, open-source library is very easy. This library has been ported to VS-2013, VS-2015, clang/libc++, macOS, and linux/gcc.
In order to make the code testable, I'm going to enable an API to get the time since midnight (in milliseconds) from any std::chrono::system_clock::time_point in any IANA time zone.
std::chrono::milliseconds
since_local_midnight(std::chrono::system_clock::time_point t,
const date::time_zone* zone);
And then to get the current time since midnight in the local time zone is easy to write on top of this testable primitive:
inline
std::chrono::milliseconds
since_local_midnight()
{
return since_local_midnight(std::chrono::system_clock::now(),
date::current_zone());
}
Writing the meat of the matter is relatively straight-forward:
std::chrono::milliseconds
since_local_midnight(std::chrono::system_clock::time_point t,
const date::time_zone* zone)
{
using namespace date;
using namespace std::chrono;
auto zt = make_zoned(zone, t);
zt = floor<days>(zt.get_local_time());
return floor<milliseconds>(t - zt.get_sys_time());
}
The first thing to do is create a zoned_time which really does nothing at all but pair zone and t. This pairing is mainly just to make the syntax nicer. It actually doesn't do any computation.
The next step is to get the local time associated with t. That is what zt.get_local_time() does. This will have whatever precision t has, unless t is coarser than seconds, in which case the local time will have a precision of seconds.
The call to floor<days> truncates the local time to a precision of days. This effectively creates a local_time equal to the local midnight. By assigning this local_time back to zt, we don't change the time zone of zt at all, but we change the local_time of zt to midnight (and thus change its sys_time as well).
We can get the corresponding sys_time out of zt with zt.get_sys_time(). This is the UTC time which corresponds to the local midnight. It is then an easy process to subtract this from the input t and truncate the results to the desired precision.
If the local midnight is non-existent, or ambiguous (there are two of them), this code will throw an exception derived from std::exception with a very informative what().
The current time since the local midnight can be printed out with simply:
std::cout << since_local_midnight().count() << "ms\n";
To ensure that our function is working, it is worthwhile to output a few example dates. This is most easily done by specifying a time zone (I'll use "America/New_York"), and some local date/times where I know the right answer. To facilitate nice syntax in the test, another since_local_midnight helps:
inline
std::chrono::milliseconds
since_local_midnight(const date::zoned_seconds& zt)
{
return since_local_midnight(zt.get_sys_time(), zt.get_time_zone());
}
This simply extracts the system_clock::time_point and time zone from a zoned_time (with seconds precision), and forwards it on to our implementation.
auto zt = make_zoned(locate_zone("America/New_York"), local_days{jan/15/2016} + 3h);
std::cout << zt << " is "
<< since_local_midnight(zt).count() << "ms after midnight\n";
This is 3am in the middle of the Winter which outputs:
2016-01-15 03:00:00 EST is 10800000ms after midnight
and is correct (10800000ms == 3h).
I can run the test again just by assigning a new local time to zt. The following is 3am just after the "spring forward" daylight saving transition (2nd Sunday in March):
zt = local_days{sun[2]/mar/2016} + 3h;
std::cout << zt << " is "
<< since_local_midnight(zt).count() << "ms after midnight\n";
This outputs:
2016-03-13 03:00:00 EDT is 7200000ms after midnight
Because the local time from 2am to 3am was skipped, this correctly outputs 2 hours since midnight.
An example from the middle of Summer gets us back to 3 hours after midnight:
zt = local_days{jul/15/2016} + 3h;
std::cout << zt << " is "
<< since_local_midnight(zt).count() << "ms after midnight\n";
2016-07-15 03:00:00 EDT is 10800000ms after midnight
And finally an example just after the Fall transition from daylight saving back to standard gives us 4 hours:
zt = local_days{sun[1]/nov/2016} + 3h;
std::cout << zt << " is "
<< since_local_midnight(zt).count() << "ms after midnight\n";
2016-11-06 03:00:00 EST is 14400000ms after midnight
If you want, you can avoid an exception in the case that midnight is non-existent or ambiguous. You have to decide before hand in the ambiguous case: Do you want to measure from the first midnight or the second?
Here is how you would measure from the first:
std::chrono::milliseconds
since_local_midnight(std::chrono::system_clock::time_point t,
const date::time_zone* zone)
{
using namespace date;
using namespace std::chrono;
auto zt = make_zoned(zone, t);
zt = make_zoned(zt.get_time_zone(), floor<days>(zt.get_local_time()),
choose::earliest);
return floor<milliseconds>(t - zt.get_sys_time());
}
If you want to measure from the second midnight, use choose::latest instead. If midnight is non-existent, you can use either choose, and it will measure from the single UTC time point that borders the local time gap that midnight is in. This can all be very confusing, and that's why the default behavior is to just throw an exception with a very informative what():
zt = make_zoned(locate_zone("America/Asuncion"), local_days{sun[1]/oct/2016} + 3h);
std::cout << zt << " is "
<< since_local_midnight(zt).count() << "ms after midnight\n";
what():
2016-10-02 00:00:00.000000 is in a gap between
2016-10-02 00:00:00 PYT and
2016-10-02 01:00:00 PYST which are both equivalent to
2016-10-02 04:00:00 UTC
If you use the choose::earliest/latest formula, instead of an exception with the above what(), you get:
2016-10-02 03:00:00 PYST is 7200000ms after midnight
If you want to do something really tricky like use choose for non-existent midnights, but throw an exception for ambiguous midnights, that too is possible:
auto zt = make_zoned(zone, t);
try
{
zt = floor<days>(zt.get_local_time());
}
catch (const date::nonexistent_local_time&)
{
zt = make_zoned(zt.get_time_zone(), floor<days>(zt.get_local_time()),
choose::latest);
}
return floor<milliseconds>(t - zt.get_sys_time());
Because hitting such a condition is truly rare (exceptional), the use of try/catch is justified. However if you want to do it without throwing at all, there exists a low-level API within this library to achieve that.
Finally note that this long winded answer is really about 3 lines of code, and everything else is about testing, and taking care of rare exceptional cases.
It really depends on why you need "milliseconds since midnight" and what you plan to use it for.
Having said that, you need to take into account the fact that 3am doesn't really mean 3 hours since midnight, when DST is involved. If you really need "milliseconds since midnight" for some reason, you can get one Epoch time at midnight, another at 3am, and subtract the two.
But again, the notion of "midnight" may not be that stable in some cases; if a region's rule is to fall back from 1am to midnight when DST ends, you have two midnights within a day.
So I'm really doubtful of your dependence on "midnight". Typically, those broken-down times are for display and human understanding only, and all internal timekeeping is done with Epoch times.
If you're on Linux, gettimeofday gives the number of seconds/microseconds since the Epoch, which may help. But this really doesn't have anything to do with DST, since DST matters only with broken-down times (i.e. year, month, day, hour, minute, second).
To get the broken-down time, use gmtime or localtime with the "seconds" part of the result of gettimeofday:
struct timeval tv;
gettimeofday(&tv, 0);
struct tm *t = localtime(&tv.tv_sec); // t points to a statically allocated struct
localtime gives the broken-down time in your local timezone, but it may be susceptible to DST. gmtime gives the broken-down time in UTC, which is immune to DST.
None of the answers provided really does what I need it to do. I've come up with something standalone that I think should work. If anybody spots any errors or can think of a faster method, please let me know. Present code takes 15 microseconds to run. I challenge SO to make something quicker (and I really hope SO succeeds =P)
inline int ms_since_midnight()
{
//get high precision time
timespec now;
clock_gettime(CLOCK_REALTIME,&now);
//get low precision local time
time_t now_local = time(NULL);
struct tm* lt = localtime(&now_local);
//compute time shift utc->est
int sec_local = lt->tm_hour*3600+lt->tm_min*60+lt->tm_sec;
int sec_utc = static_cast<long>(now.tv_sec) % 86400;
int diff_sec; //account for fact utc might be 1 day ahead
if(sec_local<sec_utc) diff_sec = sec_utc-sec_local;
else diff_sec = sec_utc+86400-sec_local;
int diff_hour = (int)((double)diff_sec/3600.0+0.5); //round to nearest hour
//adjust utc to est, round ns to ms, add
return (sec_utc-(diff_hour*3600))*1000+(int)((static_cast<double>(now.tv_nsec)/1000000.0)+0.5);
}
You can run localtime_r, and mktime after adjusting the result of localtime_r to compute the value of "midnight" relative to the Epoch.
Edit: Pass now into the routine to avoid an unnecessary call to time.
time_t global_midnight;
bool checked_2am;
void update_global_midnight (time_t now, bool dst_check) {
struct tm tmv;
localtime_r(&now, &tmv);
tmv.tm_sec = tmv.tm_min = tmv.tm_hour = 0;
global_midnight = mktime(&tmv);
checked_2am = dst_check || (now >= (global_midnight + 2*3600));
}
Assume global_midnight is initially 0. Then, you would adjust it's value at 2am, and the next day, so that it stays in sync with DST. When you call clock_gettime, you can compute the difference against global_midnight.
Edit: Since the OP wants to benchmark the routine, tweaking code for compilers that assume true to be the fast path, and round to nearest msec.
unsigned msecs_since_midnight () {
struct timespec tsv;
clock_gettime(CLOCK_REALTIME, &tsv);
bool within_a_day = (tsv.tv_sec < (global_midnight + 24*3600));
if (within_a_day)
if (checked_2am || (tsv.tv_sec < (global_midnight + 2*3600))
return ((tsv.tv_sec - global_midnight)*1000
+ (tsv.tv_nsec + 500000)/1000000);
update_global_midnight(tsv.tv_sec, within_a_day);
return ((tsv.tv_sec - global_midnight)*1000
+ (tsv.tv_nsec + 500000)/1000000);
}
I have referred to the post [here] and made a change so that the below function can return the milliseconds since midnight in GMT time.
int GetMsSinceMidnightGmt(std::chrono::system_clock::time_point tpNow) {
time_t tnow = std::chrono::system_clock::to_time_t(tpNow);
tm * tmDate = std::localtime(&tnow);
int gmtoff = tmDate->tm_gmtoff;
std::chrono::duration<int> durTimezone(gmtoff); // 28800 for HKT
// because mktime assumes local timezone, we shift the time now to GMT, then fid mid
time_t tmid = std::chrono::system_clock::to_time_t(tpNow-durTimezone);
tm * tmMid = std::localtime(&tmid);
tmMid->tm_hour = 0;
tmMid->tm_min = 0;
tmMid->tm_sec = 0;
auto tpMid = std::chrono::system_clock::from_time_t(std::mktime(tmMid));
auto durSince = tpNow - durTimezone - tpMid;
auto durMs = std::chrono::duration_cast<std::chrono::milliseconds>(durSince);
return durMs.count();
}
If you want to have local time, it is much more easier.