offset calculation for day light saving(DST) without using localtime - c

I have CODE as below to calculate offset,
Here I executed below code on EST timezone machine whose offset is -18000 seconds i.e. UTC-5.00
Please note I can't use localtime() due to some limitations which is very long story to mention here.
So I used below logic to do minus of GMT epoch from localtime epoch as below and it returned perfectly the offset.
Now what I want here is suppose case of DST(Day light Saving), where offset will be -14400 seconds i.e. UTC-4.00. Due to +1 adjustment of DST(Day light Saving) for EST time zone.
So will my code mentioned below work? in case system is having DST(Day light Saving).
I can not test live right now, as DST(Day light Saving) is not active on my machine.
Its planned as below:
/etc/localtime Sun Mar 14 06:59:59 2021 UT = Sun Mar 14 01:59:59 2021 EST isdst=0 gmtoff=-18000
/etc/localtime Sun Mar 14 07:00:00 2021 UT = Sun Mar 14 03:00:00 2021 EDT isdst=1 gmtoff=-14400
/etc/localtime Sun Nov 7 05:59:59 2021 UT = Sun Nov 7 01:59:59 2021 EDT isdst=1 gmtoff=-14400
/etc/localtime Sun Nov 7 06:00:00 2021 UT = Sun Nov 7 01:00:00 2021 EST isdst=0 gmtoff=-18000
So wanted to know, when DST is active, i.e. say between 14 March 2021 to 7 Nov 2021, will my below code return offset of -14400.
Does time() function consider epoch time after DST adjustment.
#include <stdio.h>
#include <time.h>
int main ()
{
time_t rawtime, g_epoch, l_epoch;
struct tm * gtm, *ltm;
signed long int offset;
//Get current epoch time
time ( &rawtime );
// convert rawtime epoch to struct tm in GMT
gtm = gmtime ( &rawtime );
// again convert back GMT struct tm to epoch
g_epoch = mktime(gtm);
//convert rawtime epoch to "struct tm* ltm" in local time zone
ltm= localtime(&rawtime);
// again convert back local time zone "struct tm* ltm" to epoch l_epoch
l_epoch= mktime(ltm);
//calculate offset
offset= l_epoch - g_epoch;
printf("Rawtime: %u\n",rawtime);
printf("GMTepoch: %u\n",g_epoch);
printf("Local Epoch: %u\n",l_epoch);
printf("Offset: %ld",offset);
return 0;
}
O/p as below:
Rawtime: 1640176204
GMTepoch: 1640194204
Local Epoch: 1640176204
Offset: -18000

By making this small modification to your program:
gtm = gmtime (&rawtime);
gtm->tm_isdst = -1;
g_epoch = mktime(gtm);
it should work when DST is in effect. Setting tm_isdst to -1 forces mktime to decide for itself whether DST is in effect.
You can also skip the manipulations involving ltm and l_epoch and the call to localtime. (I thought the whole point of the exercise was to avoid calling localtime?) You can compute your offset just fine with
offset = rawtime - g_epoch;
But in the end this is still a pretty dreadful hack, and as #ikegami has pointed out, it won't work properly in the vicinity of DST transitions. For example, the time_t value 1636261200 properly corresponds to 1am EDT on November 7, 2021, but this code will wrongly determine that it has already fallen back to EST. This problem will be worse (will apply for a larger number of hours) the farther your local time zone is from GMT. There's probably a way around this, but it won't be particularly pretty.

I don't think it will work at all, but I'm not sure. Best case, it will give incorrect results near switches to/from DST.
If you can't trust the system time zone database, you can always use your own copy of the tz database. Steve Summit points out the database even comes with a reference implementation of all the time zone-related functions such as localtime, so there would be no need to parse the database yourself.
Note: The database is frequently updated, so you will need some kind of process to update your application frequently if you need to work with arbitrary time zones.
Does time() function consider epoch time after DST adjustment.
time returns the number of seconds since the start of 1970 UTC.
As such, the result doesn't change where you are in the world.
Local time Time Zone Examples time()
------------------------- ------------------ ----------
2021-06-01T12:00:00Z Etc/UTC, E./Dublin 1622548800
2021-06-01T08:00:00-04:00 "EDT", A./New_York 1622548800
2021-06-01T07:00:00-05:00 "EST", A./Bogota 1622548800
2021-06-01T12:00:00Z Etc/UTC, E./Dublin 1622548800
2021-06-01T12:00:00-04:00 "EDT", A./New_York 1622563200
2021-06-01T12:00:00-05:00 "EST", A./Bogota 1622566800
So will my code mentioned below work? in case system is having DST(Day light Saving).
Doesn't mktime take it's information about local time from the same place localtime does? So why would mktime work on a system where localtime doesn't?
But let's say mktime does work.
Even if we assume that mktime works correctly on a system where localtime doesn't, I don't think it will work. I think gtm.tm_isdst will always be zero (since gmtime uses UTC which doesn't have DST), and I think that will cause the following mktime to return something different than desired. But this can be addressed by setting gtm .tm_isdst to -1.
So let's say mktime does return what we expect.
Even if mktime returns what we expect, the approach is flawed. It will give the wrong offsets near switches to/from DST.
// Current time zone: America/New_York
// Current local time: 2022-03-12T22:30:00-05:00
// Change to DST: In 3.5 hours.
// Get the current epoch time.
// This returns the same thing worldwide.
time(&rawtime); // rawtime = 1647142200
// Convert current epoch time to UTC.
gtm = gmtime(&rawtime); // gtm = 2021-03-13T03:30:00
gtm .tm_isdst = -1;
// Note that mktime expects a local time.
// But we're passing the current UTC time.
// So g_epoch is the epoch time for
// 2021-03-13T03:30:00 America/New_York
g_epoch = mktime(gtm); // rawtime = 1647156600
And there's the problem. 2021-03-13T03:30:00 (local) is after the change to DST, but we're only 2022-03-12T22:30:00 (local), which is still before the change to DST. This is going to give us the wrong offset.

Related

How do I calculate the difference in days, months and years between two dates with leap years?

I'm requesting the user to enter two dates (day, month and year).
Using the time.h header, I'm storing it in a struct tm variable like following (let's say date1):
date1.tm_mday = day;
date1.tm_mon = month;
date1.tm_year = year - 1900;
date1.tm_hour = 0;
date1.tm_min = 0;
date1.tm_sec = 0;
date1.tm_isdst = 0;
The second date (date2) is the same above but with "date2" instead of "date1" (two structures).
Next, I did the mktime of date1 and date2 to calculate the seconds of both dates:
date1TimeT = mktime(&date1);
date2TimeT = mktime(&date2);
Following, I checked whether the date1 is bigger than date2, so I placed:
if(date1TimeT > date2TimeT)
finalDateTimeT = date1TimeT - date2TimeT;
else
finalDateTimeT = date2TimeT - date1TimeT;
Now to calculate the days, months and years that have passed through this two dates:
totalDays = (int) finalDateTimeT / 86400;
remainingYears = totalDays / 365;
remainingMonths = (totalDays - remainingYears * 365) / 30;
remainingDays = (totalDays - remainingYears * 365 - remainingMonths * 30);
However, this is not 100% accurate, as this does not take care of the leap years (those who are divisible by 4).
Is there a safer way to calculate this? Keeping in mind the leap years.
How do I calculate the difference in days, months and years between two dates?
There are two totally different ways to do this. The more popular one is the way you've already tried: First convert both dates to some common, monotonic timebase, such as a Unix-style time_t value. Then subtract. This gives you a simple difference in, in this case, seconds. You can divide by 86400 to get days, but it's hard to see how to convert to months or years.
The reason it's hard is that our familiar year/month/day/hour/minute/second system of date/timekeeping is a mixed base scheme, and it's worse than that, in that different months have different numbers of days. There's no single constant days-per-month value you can divide by, the way there is for, say, inches-per-foot (or even hours-per-day).
And then time zones, daylight saving time, leap years, and leap seconds can make things even more complicated.
But in fact, those complications are one reason that the convert-to-monotonic-timebase technique is so popular, because it cuts through most of that complexity rather nicely. Assuming that library functions like mktime() and localtime() have been implemented properly, they take care of all the nasty details of time zones, daylight saving time, and leap years. Just about the only thing left for you to do is to make sure you use them correctly.
Using them correctly involves:
making sure to encode the tm_year and tm_mon fields of struct tm correctly (this is easy to get wrong, and in fact your posted code makes one of the common mistakes, forgetting that tm_mon is 0-based)
making sure to set tm_isdst to -1 if you want mktime to figure out whether DST applied for the date/time in question or not
setting tm_hour to 12 (not 0) if you're only interested in dates, not times (this helps to avoid certain anomalies that can crop up near DST transitions, although this probably wouldn't matter for your problem), and
when converting to days, remembering to round, not truncate, when dividing by 86400, since you may see an effective 23- or 25-hour day length if you're straddling a DST transition.
Leap years are generally not a problem when using this technique, and I'm not sure what you meant when you said that it was "not 100% accurate" because it "does not take care of the leap years". mktime and localtime understand leap years perfectly, and will allow you to compute a proper difference (in seconds) between two dates which straddle the February/March transition, in both leap and non-leap years. It's true that you won't get an accurate number of years if you just divide by the constant 365, but that's no worse than the way you'll often get the wrong number of months if you divide by a constant 30.
So if you've converted to a monotonic number of seconds, the answer to the question of how you convert to years, months, and days is: you don't. It's an impossible, ill-defined problem. You'll have to settle for presenting an answer in terms of just days, or perhaps divide by 7 to get weeks and days. (Or you can present a decidedly approximate answer after dividing by an average number of days per month, like 30.43.)
Now, just about everyone would agree that, informally at least, the difference between, say, February 5 and March 7 is "one month and two days". Notice that this is equally true in leap and non-leap years. If you want to implement this more "informal" or "human-centric" algorithm in a C program, you certainly can, although it's not going to involve mktime or localtime. Instead, it's going to be an implementation of mixed-base arithmetic, with a complication due to the fact that the number of days per month is not constant.
Suppose we want to find the difference between April 5, 2021 and June 10, 2022. That will look like
year month day
2022 6 10
- 2021 4 5
---- -- --
1 2 5
The answer is "One year, two months, and five days". This problem was simple, because we didn't have to borrow.
Here's a harder one: what's the difference between January 25,
2020 and March 5, 2020? That looks like
year month day
2020 3 5
- 2020 1 25
---- -- --
???? ?? ??
What are we supposed to do with this? The first date is clearly greater than the second, but of course 5 is less than 20. Informally, from January 25 to February 25 is one month, and then it's a few more days until March 5, but that "few more days" hits the worst case, because it straddles the end of February, so we have to worry about whether it's a leap year or not.
The way to work the problem is probably like this:
year month day
2020 2 34
- 2020 1 25
---- -- --
0 1 9
Here I have borrowed 1 from the month's column, and added it in to the day's column. Since 2020 was a leap year, there were 29 days in February, so the adjusted days number after the borrow is 5+29=34.
You might have to implement a similar borrow from the year's to the month's column, although that's easier, because the number of months per year is always 12. (That is, I'm pretty sure you will not have to worry about leap years at that point in the algorithm.)
I'm out of time for this too-long answer, so I'm not going to write any C code to do this mixed-base arithmetic, but with this prose outline I think you should be able to implement it straightforwardly enough.
Footnote 1: The algorithm I've presented isn't quite complete. It will not handle, for example, the interval from January 31 to March 1 properly. It's not necessarily obvious how to handle that rather problematic case, but the answer is probably to add the possibility of looping and borrowing twice, as #chux shows in his answer (see comment "Might loop twice").
Footnote 2: Remember that in our Gregorian calendar, the leap-year determination is not a simple test for divisibility by 4.
Footnote 3: For completeness and for anyone who might be curious, I'll say a little more about leap seconds. Unlike leap years, leap seconds are not just automatically taken care of for you by mktime and localtime. In fact, handling leap seconds properly is more or less impossible under the conventional mktime/localtime scheme, for the simple reason that the Unix time_t representation denies the existence of leap seconds altogether. Due to the presence of leap seconds, UTC is not the uniform, continuous, monotonic time scale that time_t assumes. Handling leap seconds properly requires thinking pretty carefully about how you want them to affect your addition and subtraction algorithms, and it requires using some data type other than time_t.
Footnote 4. Best not to worry at all about the anomalies that happened in 1752 (or whenever your country changed over to the Gregorian calendar). :-)
The idea to go through the delta days is a bad one. Once you pass the month boundary you are in trouble. Each month has 28..31 days. In your original post you assume every month to have 30 days.
In order not to loose relationship with the month's lengths we have to work directly with day/month/year. We can combine month+year in one variable as every year has 12 months, like the expression month+year*12. Here my code:
#include <time.h>
#include <stdio.h>
void PrintDelta (int StartYear, int StartMonth, int StartDay, int EndYear, int EndMonth, int EndDay)
{
//Month and Day are 1-based
int Start_MonthYear = StartYear * 12 + StartMonth-1;
int End_MonthYear = EndYear * 12 + EndMonth-1;
int Delta_MonthYear = End_MonthYear-Start_MonthYear;
int Delta_Days = EndDay-StartDay;
if (Delta_Days < 0)
{
//Like Jan 31st --> Feb 1st
//remove one month from Delta_MonthYear and add one month to Delta_Days
if (0)
{ //old buggy code used month before end date
static const int MonthDays[2][12]=
{
//MonthDays[][0] is the number of days in the month BEFORE January!!
//12 01 02 03 04 05 06 07 08 09 10 11 <--- month
{ 31,31,28,31,30,31,30,31,31,30,31,30}, //non-leap year
{ 31,31,29,31,30,31,30,31,31,30,31,30} //leap year
};
Delta_Days += MonthDays[(EndYear & 3) ? 0 : 1][EndMonth-1];
}
else
{ //better code uses month from start date
static const int MonthDays[2][12]=
{
{ 31,28,31,30,31,30,31,31,30,31,30,31}, //non-leap year
{ 31,29,31,30,31,30,31,31,30,31,30,31} //leap year
};
Delta_Days += MonthDays[(StartYear & 3) ? 0 : 1][StartMonth-1];
}
Delta_MonthYear--;
}
printf("Timespan from %d-%02d-%02d to %d-%02d-%02d is %d year(s), %d month(s), %d day(s)\n",
StartYear, StartMonth, StartDay,
EndYear, EndMonth, EndDay,
Delta_MonthYear/12, Delta_MonthYear%12, Delta_Days
);
}
int main(int argc, char **argv)
{
PrintDelta(2000,2,28, 2001,3,1); //1yr 1day, including Feb 29th
PrintDelta(2001,2,28, 2002,3,1); //1yr 1day, no Feb 29th
PrintDelta(2022,1,11, 2022,1,21); //10 days
PrintDelta(2022,1,31, 2022,2,1); //one day
PrintDelta(2022,1,31, 2022,3,1); //1 month 1 day
}
Subtract days, then months, then years. The only challenging issue is how to borrow from months
Some illustrative sample code below -simplifications exist.
#include <stdio.h>
int is_leap_year(int year) {
// Leave this for OP
}
// year: Gregorian calender
// mont: 0 = Dec, 1 = Jan, ... 12 = Dec
static int days_per_month(int year, int month) {
static const char dpm[] = {31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month == 2 && is_leap_year(year)) {
return 29;
}
return dpm[month];
}
typedef struct {
int year, month, day;
} ymd_type;
// *a - *b
// Assume: a and b elements are already in their preferred ranges and a >= b.
ymd_type date_subtract(const struct tm *a, const struct tm *b) {
ymd_type ymd = {a->tm_year + 1900, a->tm_mon + 1, a->tm_mday};
ymd.day -= b->tm_mday;
while (ymd.day < 0) { // Might loop twice
ymd.month--;
ymd.day += days_per_month(ymd.year, ymd.month);
}
ymd.month -= b->tm_mon + 1;
if (ymd.month < 0) {
ymd.year--;
ymd.month += 12; // 12 months per year
}
ymd.year -= b->tm_year + 1900;
return ymd;
}
int test_ymd(int y1, int m1, int d1, int y0, int m0, int d0) {
const struct tm t1 = {.tm_year = y1 - 1900, .tm_mon = m1 - 1, .tm_mday = d1};
const struct tm t0 = {.tm_year = y0 - 1900, .tm_mon = m0 - 1, .tm_mday = d0};
ymd_type ymd = date_subtract(&t1, &t0);
printf(" %4d/%2d/%2d - %4d/%2d/%2d = %2dy %2dm %2dd\n", y1, m1, d1, y0, m0,
d0, ymd.year, ymd.month, ymd.day);
return 0;
}
Sample
int main() {
test_ymd(2022, 2, 17, 2020, 1, 1);
test_ymd(2022, 3, 1, 2020, 12, 31);
test_ymd(2022, 3, 1, 2022, 1, 31);
test_ymd(2020, 3, 1, 2020, 1, 31);
return 0;
}
Output
2022/ 2/17 - 2020/ 1/ 1 = 2y 1m 16d
2022/ 3/ 1 - 2020/12/31 = 1y 1m 29d
2022/ 3/ 1 - 2022/ 1/31 = 0y 0m 29d
2020/ 3/ 1 - 2020/ 1/31 = 0y 0m 30d
To Do:
Handle cases when dates are in the opposite order.
Handle cases when the date is not in the primary rage.
Handle extreme case like when year is near INT_MAX.

Cant get right localtime in C

Im trying to get the current localtime in C, lets say Italy, I tried the code below but it returns a time three hours earlier than the real one.
For example if executed at 17:34 it will return 14:34, what am I doing wrong?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
setenv("TZ", "UTC+1", 1); // Italy is UTC+1
tzset();
time_t sec = 1634224877; // Just for test
struct tm * end = localtime(&sec);
// Check for daylight save
if(end->tm_isdst == 1)
{
end->tm_hour += 1;
}
printf("Daylight save time?: %d\n", end->tm_isdst);
printf("Time is says: %.2d/%.2d/%d - %.2d:%.2d:%.2d\n", end->tm_mday, end->tm_mon + 1, end->tm_year + 1900,
end->tm_hour, end->tm_min, end->tm_sec);
}
Thanks
tl;dr If you want the time at a location, you have to set TZ to a location.
1634224877 is 2021-10-14 15:21:17 UTC. setenv takes POSIX time zones which don't work like you think. UTC+1 means one hour head of UTC. That is 14:21:17. If you want "UTC+1" you actually ask for UTC-1, one hour behind UTC.
But don't ask for UTC-1.
setenv("TZ", "UTC+1", 1); // Italy is UTC+1
That comment is not correct. Italy is not UTC+1. Sometimes Italy is UTC+1, sometimes it is UTC+2. In order to know about daylight savings time (and other wacky time zone issues), TZ needs to know your location.
Set TZ to the city closest to you, like Europe/Rome. Now localtime can figure out whether it's daylight savings time or not, you do not need to correct it.
int main()
{
setenv("TZ", "Europe/Rome", 1);
tzset();
time_t sec = 1634224877; // Just for test
struct tm * end = localtime(&sec);
printf("Daylight savings time?: %d\n", end->tm_isdst);
printf("Time is says: %.2d/%.2d/%d - %.2d:%.2d:%.2d\n", end->tm_mday, end->tm_mon + 1, end->tm_year + 1900,
end->tm_hour, end->tm_min, end->tm_sec);
}
Daylight savings time?: 1
Time is says: 14/10/2021 - 17:21:17
The system that manages time zones is called tzdata. It is a database of locations, their time zone information, daylight savings time switches, and a host of other wacky time zone information. It's what lets your computer know that Rome is usually UTC+1 but should sometimes be UTC+2.
A list of all tzdata locations can be had on Wikipedia, but these do not necessarily match the tzdata installed on your machine.
The TZ environment variable value FOO+1 (I changed it from UTC+1 to avoid confusion) is interpreted as a standard time zone designated "FOO" with no alternate (daylight savings) zone. The +1 (the + is optional) means that 1 hour needs to be added to the local time to convert it to Coordinated Universal Time (UTC). To specify an alternate (daylight savings) time zone, it is added after the standard time offset, e.g. FOO+1BAR0. The offset after the alternate zone can be omitted in which case it defaults to one less than the standard offset, so FOO+1BAR0 can be shortened to FOO1BAR. This means that local time will be 1 hour behind UTC when standard time is in effect and will be UTC when alternate (daylight savings) time is in effect. Optionally, the offset can include minutes or minutes and seconds, e.g. FOO+01:00BAR+00:00:00.
Italy uses Central European Time (CET, corresponding to UTC+1) when standard time is in effect (e.g. during winter), and uses Central European Summer Time (CEST, corresponding to UTC+1) when alternate (daylight savings) time is in effect (e.g. during summer). That can be expressed by the TZ environment variable value CET-1CEST-2 or CET-1CEST. Notice that the offsets used in the TZ environment variable have the opposite sign to the usual convention.
When TZ has one of the previously mentioned values with an alternate time (e.g. CET-1CEST), it is left up to the system libraries to use some arbitrary (and usually incorrect for most of the world) rule to determine the date and time of transitions between standard time and alternate time. Simple rules for the date and time of exactly two transitions per year can be encoded after the alternate zone designation and offset in the TZ variable, separated by commas. The transition date can be specified as Mm.n.d, meaning the dth day (0 = Sunday, 1 = Monday, ..., 6 = Saturday) of the nth week (counting from 1) of the mth month (1 = January, ..., 12 = December). n = 5 is interpreted as the last d day of the month m. The transition date can also be specified as Jn where n is the day of the year, not counting February 29 (so March 1 is always day 60). The (optional) transition time can be specified as /time, where time specifies the current local time on the transition date at which the transition to the other time is made, defaulting to 02:00:00. The time value can be abbreviated in the same way as time zone offsets, so /2 is the same as /02:00:00, but no leading plus or minus sign is allowed (by the current standard).
Italy currently operates on EU time zone transition rules (the abolition of which is currently postponed), where the transitions occur at 01:00 UTC and local time advances 1 hour on the last Sunday of March (M3.5.0) and retreats 1 hour on the last Sunday of October (M10.5.0). For Italy, the local transition times are 02:00:00 in March (advancing to 03:00:00) and 03:00:00 in October (retreating to 02:00:00), so the rules are M3.5.0/2 and M10.5.3. (M3.5.0/2 can be shortened to M3.5.0 since it uses the default transition time.)
The following, modified code will work to show time in Italy, at least until the current EU time zone rules are abolished:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1); // Italy rules!
tzset();
time_t sec;
#if 0
sec = 1634224877; // Just for test
#else
sec = time(NULL); // For current time
#endif
struct tm * end = localtime(&sec);
printf("Daylight save time?: %d\n", end->tm_isdst);
printf("Time is says: %.2d/%.2d/%d - %.2d:%.2d:%.2d\n",
end->tm_mday, end->tm_mon + 1, end->tm_year + 1900,
end->tm_hour, end->tm_min, end->tm_sec);
}

localtime_s depends from current system time for Chile, Santiago time zone

I'm currently working with "localtime_s" function, here is the syntax:
errno_t localtime_s (struct tm* _tm, const time_t *time);
and I've found a bit strange behavaiour.
This function returns different "tm_isdst" field of "_tm" structure(for the same "time" argument) for different current system time (the time that is set on the Windows Control panel) if we have Chile, Santiago time zone.
Another words, "localtime_s" gives us different DST transition date(for the same year passed in "time") for different system time (DST transition date is the date where "tm_isdst" field value is "False" and previous day "tm_isdst" value is "True", or vice versa).
It's always correct if the year passed with the "time" argument is the same as the year in the system time. But when the year of system time differs from the year in "time" argument, DST transition date based on the "tm_isdst" values returned by "localtime_s" function becomes wrong.
Example:
Santiago, Chile time zone. 2014 year (passed with "time" argument). First DST transition was between 26/27 of April.
If current system time year is also 2014, all is ok - "tm_isdst" is "True" for April 26 and "False" for April 27.
But if system time year is 2018, "tm_isdst" will be "True" for the dates less equal May 10, and only from May 11 it becomes "False". This means "localtime_s" function consider DST transition date as 11 May, which is wrong.
Another example, if system year is 2010, DST transition date (for year 2014 passed with "time" argument) from the "localtime_s" will be 6 April.
So we are getting different DST transition dates for the same year (but for different years set in Windows).
I don't think it's default behaviour because:
I tried to google "localtime_s" function and I didn't found any info about dependency from current system time, specially in function description.
I tested "localtime_s" for another time zones (like UTC-8 Pacific Time(US & Canada) and UTC+2 Helsinki, Kyiv, Riga, Sofia, Talinn, Vilnius) and it works fine with them - DST transition dates don't depend from current system time.
I tested the same "localtime_r" function on Linux and it works fine - DST transition dates also don't depend from current system time (for Santiago time zone as well).
If we have 2015 year on Windows system time, "lolaltime_s" function considers we don't have DST transition for any year at all ("tm_isdst" is always "True" for any time and any year). It's obviously wrong from my point of view.
System is Windows Server 2012 R2 Standart, x64.
Could some please say what can cause this issue? Is this "localtime_s" function bug?
P.s. Here are attached few examples of DST transition date dependency from system year:
DST transition dates, Santiago, 2014 system time year
DST transition dates, Santiago, 2018 system time year
DST transition dates, Santiago, 2020 system time year
P.p.s. Here is a simple example to test (sorry for the missed '#' before include, stackoverflow doesn't want to save my post with this symbols):
include <iostream>
include <time.h>
int main()
{
time_t t_26_Apr = 1398517200; // 26-Apr-2014
time_t t_27_Apr = 1398517200 + 24 * 3600; // 27-Apr-2014
time_t t_10_May = 1398517200 + 14 * 24 * 3600; // 10-May-2014
time_t t_11_May = 1398517200 + 15 * 24 * 3600; // 11-May-2014
tm tm_26_Apr;
tm tm_27_Apr;
tm tm_10_May;
tm tm_11_May;
localtime_s(&tm_26_Apr, &t_26_Apr);
localtime_s(&tm_27_Apr, &t_27_Apr);
localtime_s(&tm_10_May, &t_10_May);
localtime_s(&tm_11_May, &t_11_May);
std::cout << "26-Apr-2014 DST status is:\t" << tm_26_Apr.tm_isdst << std::endl;
std::cout << "27-Apr-2014 DST status is:\t" << tm_27_Apr.tm_isdst << std::endl;
std::cout << "10-May-2014 DST status is:\t" << tm_10_May.tm_isdst << std::endl;
std::cout << "11-May-2014 DST status is:\t" << tm_11_May.tm_isdst << std::endl;
return 0;
}
Real DST transition date in Santiago for 2014 year is at midnight between 26/27 April. But if Windows system time year is 2018, transition date become 10/11 May. I tested this on 2 different OS (Windows 10 and Windows Server 2012 R2) and 2 different machines, and got the same result.
It does indeed look odd. On Windows 10, I checked the dates for Santiago. Year 2014 and 2016 have different daylight savings dates. Year 2015 has no information at all (maybe no daylight savings for that year) It's not unusual that these dates change over time. For much of US and Canada it also changed in 2007. Daylight saving is a useless concept to begin with, and governments screw with it often for no good reason. Not much you can do about it except rely on the operating system. Use UTC to store time internally if possible, convert to local time for output.
#include <stdio.h>
#include <Windows.h>
int main()
{
for(int year = 2014; year < 2020; year++)
{
TIME_ZONE_INFORMATION tz;
GetTimeZoneInformationForYear((USHORT)year, NULL, &tz);
printf("Year %d, bias: %d\n", year, tz.Bias);
printf("Standard: %02d/%02d %02d:%02d\n",
tz.StandardDate.wMonth, tz.StandardDate.wDay,
tz.StandardDate.wHour, tz.StandardDate.wMinute);
printf("Daylight: %02d/%02d %02d:%02d\n\n",
tz.DaylightDate.wMonth, tz.StandardDate.wDay,
tz.DaylightDate.wHour, tz.DaylightDate.wMinute);
}
return 0;
}
Output:
Year 2014, bias: 240
Standard: 04/05 23:59
Daylight: 09/05 23:59
Year 2015, bias: 180
Standard: 00/00 00:00
Daylight: 00/00 00:00
Year 2016, bias: 240
Standard: 05/02 23:59
Daylight: 08/02 23:59
Year 2017, bias: 240
Standard: 05/02 23:59
Daylight: 08/02 23:59
Year 2018, bias: 240
Standard: 05/02 23:59
Daylight: 08/02 23:59
Year 2019, bias: 240
Standard: 05/02 23:59
Daylight: 08/02 23:59

C: Julian day library - How do I get a date (ie. yyyy/mm/dd) from a date offset (number of days since X)?

I'm looking for a way in C to determine a date from a date offset (ie. number of days since December 31, 1899). For example, void get_date(const struct date start_date, int offset);. Basically, a simple library with a date struct (but no support for time). It should handle leap years and date-specific things, but nothing related to time (for example, leap seconds and other complications).
struct date start_date = {
.date_day = 31, // 31
.date_month = 12, // December
.date_year = 1899, // 1899
};
/* From and including: December 31, 1899
* To, but not including : December 8, 2010
* It's 40,519 days from the start date to the end date,
* but not including the end date: [start_date, end_date) */
get_date(start_date, 40519); // Prints something like "2010-12-08".
Ideally, the library should support Julian days, potentially going back to epoch January 1, 4713 BC.
You can do this with standard C (since you only care about dates, you don't have to worry about the timezone headaches that come with these functions). This assumes the POSIX definition of time_t, but AFAIK there is no currently-in-use OS with a different definition.
#include <time.h>
/* before calling, memset() the 'struct tm' argument to
all-bits-zero, set tm_hour to 12, then fill in the
tm_year, tm_mon, and tm_mday fields; on return, pay
attention to those fields only. */
struct tm
add_days(const struct tm *base, long days)
{
time_t t = mktime(base);
t += 3600*24*days;
return *localtime(&t);
}

Converting unix timestamp to YYYY-MM-DD HH:MM:SS

I have a Unix timestamp that I need to get the individual year, month, day, hour, minute and second values from. I never was very good in math class so I was wondering if you guys could help me out a little :)
I have to do everything myself (no time.h functions). The language is C.
Disclaimer: The following code does not account for leap years or leap seconds [Unix time does not account for leap seconds. They're overrated, anyway. -Ed]. Also, I did not test it, so there may be bugs. It may kick your cat and insult your mother. Have a nice day.
Let's try a little psuedocode (Python, really):
# Define some constants here...
# You'll have to figure these out. Don't forget about February (leap years)...
secondsPerMonth = [ SECONDS_IN_JANUARY, SECONDS_IN_FEBRUARY, ... ]
def formatTime(secondsSinceEpoch):
# / is integer division in this case.
# Account for leap years when you get around to it :)
year = 1970 + secondsSinceEpoch / SECONDS_IN_YEAR
acc = secondsSinceEpoch - year * SECONDS_IN_YEAR
for month in range(12):
if secondsPerMonth[month] < acc:
acc -= month
month += 1
month += 1
# Again, / is integer division.
days = acc / SECONDS_PER_DAY
acc -= days * SECONDS_PER_DAY
hours = acc / SECONDS_PER_HOUR
acc -= hours * SECONDS_PER_HOUR
minutes = acc / SECONDS_PER_MINUTE
acc -= minutes * SECONDS_PER_MINUTE
seconds = acc
return "%d-%d-%d %d:%d%d" % (year, month, day, hours, minutes, seconds)
If I goofed up, please let me know. Doing this in C shouldn't be too much harder.
You really do not want to do this by hand. You could write up some simple code that assumes years, months, days, hours, minutes and seconds are all the same lengths (12 months; 28, 30, or 31 days; 24 hours; 60 minutes; and 60 seconds) and come up with the wrong answer.
To get the right answer, you have to handle leap years and leap seconds, and convert to the local time zone (with the right DST mode). (Unless you choose to only display in UTC time.)
I suggest that you have a look at the code of glibc and see how strftime works.
Edit: UNIX time does not use the leap second.
That format is often called ISO 8601, and searching for that might help you out.
You should have done a search - I posted a complete answer to this question over here just the other day (for UTC, at least - to adjust for other timezones just add or subtract the timezone offset in seconds from the unixtime before you call the function).
You don't have to do math. This can be easily handled in C like this,
char *ISO_Time (
time_t time
)
{
static char mStr[128];
struct tm *gmt;
gmt = gmtime (&time);
strftime (mStr, sizeof(mStr), "%Y-%m-%dT%H:%M:%SZ", gmt);
return mStr;
}
Since yours is not exactly ISO time, you just need to change one line,
strftime (mStr, sizeof(mStr), "%Y-%m-%d %H:%M:%S", gmt);

Resources