Linux daylight savings notification - c

I was trying to find a way to receive a notification from the system (Linux) when daylight savings are applied, but I do not seem to be able to find anything like that.
Consider a program sits on a pselect() waiting for a number of timer fd's, all which have exactly 24-hour intervals, but differing start times, which are defined by a user; "07:00 ON, 07:25 OFF" (for example, if it were a coffee maker).
Because the user gives these times in local time and Linux runs on UTC, the time zone adjusted timer fd's need to be readjusted each time a daylight savings occure. (user expects coffee when his daylight savings compliant alarm clock has woken him up...)
Intelligent way to go about this, as I would imagine, would be to register to the system/kernel/init/whatever to be notified when daylight savings are applied, and avoid getting into the messy business of trying to determine such dates and times yourself and hope the system agrees with your results (ie. your resync actions and actual daylight savings happen in the same time).
Is there any way to be notified on DST changes? Or perhaps on any changes to local time (assuming DST change modifies that)?

Consider a program sits on a pselect() waiting for a number of timer fd's, all which have exactly 24-hour intervals, but differing start times
Therein lies your fundamental problem. All days are not exactly 24 hours long -- sometimes they are off by an hour (daylight savings time), or by seconds (leap seconds); just like not every February has 28 days.
A much simpler and lightweight (less resources consumed) way is to use a min-heap of future events in UTC, something like
struct trigger {
/* Details on how the event is defined;
for example, "each day at 07:00 local time".
*/
};
struct utc_event {
struct trigger *trigger;
time_t when;
};
struct event_min_heap {
size_t max_events;
size_t num_events;
struct utc_event event[];
};
The event C99 flexible array member in struct event_min_heap is an array with num_events events (memory allocated for max_events; can be reallocated if more events are needed) in a min heap keyed by the when field in each event entry. That is, the earliest event is always at the root.
Whenever current time is at least event[0].when, it is "triggered" -- meaning whatever action is to be taken, is taken --, and based on the struct trigger it refers to, the time of the next occurrence of that event is updated to event[0], then it is percolated down in the heap to its proper place. Note that you simply use mktime() to obtain the UTC time from broken-down local time fields.
(If this were a multi-user service, then you can support multiple concurrent timezones, one for each trigger, by setting the TZ environment variable to the respective timezone definition, and calling tzset() before the call to mktime(). Because the environment is shared by all threads in the process, you would need to ensure only one thread does this at a time, if you have a multithreaded process. Normally, stuff like this is perfectly implementable using a single-threaded process.)
When the event in the root (event[0]) is deleted or percolated (sifted), the event with the next smallest when will be at the root. If when is equal or less to current time in UTC, it too is triggered.
When the next when is in the future, the process can sleep the remaining interval.
That is all there is to it. You don't need multiple timers -- which are a system-wide finite resource --, and you don't need to worry about whether some local time is daylight savings time or not; the C library mktime() will take care of such details for you.
Now, if you don't like this approach (which, again, uses fewer resources than the approach you outlined in your question), contact the SystemD developers. If you kiss up to them obsequiously enough, I'm sure they'll provide a dbus signal for you. It's not like there is any sanity in its current design, and one more wart certainly won't make it any worse. Switching to C# is likely to be considered a plus.
It is crucial to understand that mktime() computes the Unix Epoch time (time_t) for the specified moment, applying daylight savings time if it applies at that specific moment. It does not matter whether daylight savings time is in effect when the function is called!
Also, UTC time is Coordinated Universal Time, and is not subject to timezones or daylight savings time.
Consider the following program, mktime-example.c:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
static time_t epoch(struct tm *const tm,
const int year, const int month, const int day,
const int hour, const int minute, const int second,
const int isdst)
{
struct tm temp;
time_t result;
memset(&temp, 0, sizeof temp);
temp.tm_year = year - 1900;
temp.tm_mon = month - 1;
temp.tm_mday = day;
temp.tm_hour = hour;
temp.tm_min = minute;
temp.tm_sec = second;
temp.tm_isdst = isdst;
result = mktime(&temp);
if (isdst >= 0 && isdst != temp.tm_isdst) {
/* The caller is mistaken about DST, and mktime()
* adjusted the time. We readjust it. */
temp.tm_year = year - 1900;
temp.tm_mon = month - 1;
temp.tm_mday = day;
temp.tm_hour = hour;
temp.tm_min = minute;
temp.tm_sec = second;
/* Note: tmp.tm_isdst is kept unchanged. */
result = mktime(&temp);
}
if (tm)
memcpy(tm, &temp, sizeof temp);
return result;
}
static void show(const time_t t, const struct tm *const tm)
{
printf("(time_t)%lld = %04d-%02d-%02d %02d:%02d:%02d",
(long long)t, tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
if (tm->tm_isdst == 1)
printf(", DST in effect");
else
if (tm->tm_isdst == 0)
printf(", DST not in effect");
else
if (tm->tm_isdst == -1)
printf(", Unknown if DST in effect");
if (tzname[0] && tzname[0][0])
printf(", %s timezone", tzname[0]);
printf("\n");
fflush(stdout);
}
int main(int argc, char *argv[])
{
struct tm tm;
time_t t;
long long secs;
int arg, year, month, day, hour, min, sec, isdst, n;
char ch;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s [ :REGION/CITY | =TIMEZONE ] #EPOCH | YYYYMMDD-HHMMSS[+-] ...\n", argv[0]);
fprintf(stderr, "Where:\n");
fprintf(stderr, " EPOCH is in UTC seconds since 19700101T000000,\n");
fprintf(stderr, " + after time indicates you prefer daylight savings time,\n");
fprintf(stderr, " - after time indicates you prefer standard time.\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
for (arg = 1; arg < argc; arg++) {
if (argv[arg][0] == ':') {
if (argv[arg][1])
setenv("TZ", argv[arg], 1);
else
unsetenv("TZ");
tzset();
continue;
}
if (argv[arg][0] == '=') {
if (argv[arg][1])
setenv("TZ", argv[arg] + 1, 1);
else
unsetenv("TZ");
tzset();
continue;
}
if (argv[arg][0] == '#') {
if (sscanf(argv[arg] + 1, " %lld %c", &secs, &ch) == 1) {
t = (time_t)secs;
if (localtime_r(&t, &tm)) {
show(t, &tm);
continue;
}
}
}
n = sscanf(argv[arg], " %04d %02d %02d %*[-Tt] %02d %02d %02d %c",
&year, &month, &day, &hour, &min, &sec, &ch);
if (n >= 6) {
if (n == 6)
isdst = -1;
else
if (ch == '+')
isdst = +1; /* DST */
else
if (ch == '-')
isdst = 0; /* Not DST */
else
isdst = -1;
t = epoch(&tm, year, month, day, hour, min, sec, isdst);
if (t != (time_t)-1) {
show(t, &tm);
continue;
}
}
fflush(stdout);
fprintf(stderr, "%s: Cannot parse parameter.\n", argv[arg]);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Compile it using e.g.
gcc -Wall -O2 mktime-example.c -o mktime-example
Run it without arguments to see the command-line usage. Run
./mktime-example :Europe/Helsinki 20161030-035959+ 20161030-030000- 20161030-030000+ 20161030-035959- 20161030-040000-
to examine the Unix timestamps around the time when DST ends in 2016 in Helsinki, Finland. The command will output
(time_t)1477789199 = 2016-10-30 03:59:59, DST in effect, EET timezone
(time_t)1477789200 = 2016-10-30 03:00:00, DST not in effect, EET timezone
(time_t)1477785600 = 2016-10-30 03:00:00, DST in effect, EET timezone
(time_t)1477792799 = 2016-10-30 03:59:59, DST not in effect, EET timezone
(time_t)1477792800 = 2016-10-30 04:00:00, DST not in effect, EET timezone
The output will be the same regardless of whether at the time of running this DST is in effect in some timezone or not!
When calling mktime() with .tm_isdst = 0 or .tm_isdst = 1, and mktime() changes it, it also changes the time specified (by the daylight savings time). When .tm_isdst = -1, it means caller is unaware of whether DST is applied or not, and the library will find out; but if there is both a valid standard time and DST time, the C library will pick one (you should assume it does so randomly). The epoch() function above corrects for this when necessary, un-adjusting the time if the user is not correct about DST.

Unix/linux systems only deal with UTC, and they use the time_t data (the number of seconds since 00:00h jan, 1st of 1970 UTC till now) as the internal time. Conversions to local time (with the complexities due to exceptions, variations for summer-winter periods, etc.) is done only when displaying the information to the user, so only on converting to local time it is done. As said, no provision to schedule something or preparation for it is made in the unix system.
From zdump(1) you can get all the info you want, per timezone, and use it to construct a crontab to notify you when the switch is to be made. It consults the local database of timezones and extracts all the info about switching (including historic) from winter to summer or the reverse.
$ zdump -v Europe/Madrid
Europe/Madrid Fri Dec 13 20:45:52 1901 UTC = Fri Dec 13 20:45:52 1901 WET isdst=0 gmtoff=0
Europe/Madrid Sat Dec 14 20:45:52 1901 UTC = Sat Dec 14 20:45:52 1901 WET isdst=0 gmtoff=0
Europe/Madrid Sat May 5 22:59:59 1917 UTC = Sat May 5 22:59:59 1917 WET isdst=0 gmtoff=0
Europe/Madrid Sat May 5 23:00:00 1917 UTC = Sun May 6 00:00:00 1917 WEST isdst=1 gmtoff=3600
Europe/Madrid Sat Oct 6 22:59:59 1917 UTC = Sat Oct 6 23:59:59 1917 WEST isdst=1 gmtoff=3600
Europe/Madrid Sat Oct 6 23:00:00 1917 UTC = Sat Oct 6 23:00:00 1917 WET isdst=0 gmtoff=0
Europe/Madrid Mon Apr 15 22:59:59 1918 UTC = Mon Apr 15 22:59:59 1918 WET isdst=0 gmtoff=0
Europe/Madrid Mon Apr 15 23:00:00 1918 UTC = Tue Apr 16 00:00:00 1918 WEST isdst=1 gmtoff=3600
Europe/Madrid Sun Oct 6 22:59:59 1918 UTC = Sun Oct 6 23:59:59 1918 WEST isdst=1 gmtoff=3600
Europe/Madrid Sun Oct 6 23:00:00 1918 UTC = Sun Oct 6 23:00:00 1918 WET isdst=0 gmtoff=0
Europe/Madrid Sat Apr 5 22:59:59 1919 UTC = Sat Apr 5 22:59:59 1919 WET isdst=0 gmtoff=0
Europe/Madrid Sat Apr 5 23:00:00 1919 UTC = Sun Apr 6 00:00:00 1919 WEST isdst=1 gmtoff=3600
Europe/Madrid Mon Oct 6 22:59:59 1919 UTC = Mon Oct 6 23:59:59 1919 WEST isdst=1 gmtoff=3600
Europe/Madrid Mon Oct 6 23:00:00 1919 UTC = Mon Oct 6 23:00:00 1919 WET isdst=0 gmtoff=0
Europe/Madrid Wed Apr 16 22:59:59 1924 UTC = Wed Apr 16 22:59:59 1924 WET isdst=0 gmtoff=0
Europe/Madrid Wed Apr 16 23:00:00 1924 UTC = Thu Apr 17 00:00:00 1924 WEST isdst=1 gmtoff=3600
Europe/Madrid Sat Oct 4 22:59:59 1924 UTC = Sat Oct 4 23:59:59 1924 WEST isdst=1 gmtoff=3600
Europe/Madrid Sat Oct 4 23:00:00 1924 UTC = Sat Oct 4 23:00:00 1924 WET isdst=0 gmtoff=0
Europe/Madrid Sat Apr 17 22:59:59 1926 UTC = Sat Apr 17 22:59:59 1926 WET isdst=0 gmtoff=0
Europe/Madrid Sat Apr 17 23:00:00 1926 UTC = Sun Apr 18 00:00:00 1926 WEST isdst=1 gmtoff=3600
Europe/Madrid Sat Oct 2 22:59:59 1926 UTC = Sat Oct 2 23:59:59 1926 WEST isdst=1 gmtoff=3600
Europe/Madrid Sat Oct 2 23:00:00 1926 UTC = Sat Oct 2 23:00:00 1926 WET isdst=0 gmtoff=0
Europe/Madrid Sat Apr 9 22:59:59 1927 UTC = Sat Apr 9 22:59:59 1927 WET isdst=0 gmtoff=0
...
By the way, if you want to be advised of an imminent localtime change, you can use the previous info to construct a crontab file, including all the info, or simply construct a crontab file that include the rules that apply at your localty. For example, if I want to be advised one day before a switch change in Spain (it changes on last sunday of march/october, at 02/03h) you can add some rules in your crontab file:
0 0 24-30 3,10 5 echo Time daylight savings change scheduled for tomorrow | mail $USER#your.domain.com
and a mail will be sent to you in on every saturday(5) that happens to be in the week from 24-30th of march and october (3,10 part) of each year at 00:00h (localtime). I'm sure you'll be able to adapt this example to your localty or time of advance (so, the day before a time change happens).

Related

mktime or tz database unexpected return for 1941

mktime returns for july 3rd 1941 (00:00:00) and july 4th 1941 (00:00:00) are unexpected.
The difference between the two is 82800 seconds, lacking a full hour (3600).
The C program diff1941.c (see below) shows the following :
$> diff1941
july3=-899337600i
diff:82800 should be 86400
At first I thought it was a TZ Database hour shift, but as far as I understand, and according to zdump command, there is no such shift for 1941.
zdump -v -c 1940,1943 /etc/localtime
/etc/localtime Sun Feb 25 01:59:59 1940 UT = Sun Feb 25 01:59:59 1940 WET isdst=0 gmtoff=0
/etc/localtime Sun Feb 25 02:00:00 1940 UT = Sun Feb 25 03:00:00 1940 WEST isdst=1 gmtoff=3600
/etc/localtime Fri Jun 14 21:59:59 1940 UT = Fri Jun 14 22:59:59 1940 WEST isdst=1 gmtoff=3600
/etc/localtime Fri Jun 14 22:00:00 1940 UT = Sat Jun 15 00:00:00 1940 CEST isdst=1 gmtoff=7200
/etc/localtime Mon Nov 2 00:59:59 1942 UT = Mon Nov 2 02:59:59 1942 CEST isdst=1 gmtoff=7200
/etc/localtime Mon Nov 2 01:00:00 1942 UT = Mon Nov 2 02:00:00 1942 CET isdst=0 gmtoff=3600
So at this point I am confused. Either my program has a bug that I can not see (possible), or there is a bug in lib C mktime function(unlikely), or there is something subtle in the TZ database and I can not find it (probable): what do you think of it ?
I am using:
Ubuntu 20.04 64 bits,
libc 2.31-0ubuntu9,
tzdata 2019c-3ubuntu1
/etc/localtime points on /usr/share/zoneinfo/Europe/Paris
diff1941.c:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
long int stamp(int d,int m,int y)
{
struct tm date;
memset(&date,0,sizeof(date));
date.tm_mday=d;
date.tm_mon=m-1;
date.tm_year=y-1900;
return mktime(&date);
}
int main(int argc, char **argv)
{
if (argc>1)
setenv("TZ","0",1);
long int july3=stamp(3,7,1941);
long int july4=stamp(4,7,1941);
printf("july3=%ldi\n",july3);
printf("diff:%ld should be 86400\n",july4-july3);
}
Usually, when you see 1 hour difference in time, that means you should take a look at daylight saving time settings.
On both dates 3rd July 1941 and 4th July 1941 daylight saving time was in effect in France. You specify tm_isdst = 0, that means the daylight saving time is not in effect for your dates. So your dates are invalid - there was no such time as 3rd July 1941 with 00:00:00 with no DST.
Glibc mktime tries it's best to determine what time do you have in mind. Actually it determines the first of the dates as 3rd July 1941 02:00:00 and the second of the dates as 4th July 1941 01:00:00. The difference is one day minus one hour.
Either set isdst=-1 to let mktime "automagically" determine the current DST for your input times. In France it was DST all the time in 1941, it will determine isdst=1. Or explicit specify that you want DST by setting date.isdst=1.

A stopwatch in C which captures the current time

I am trying to make a stopwatch in C to collect the time period of a pendulum. Although not shown here, the i is a distance counter, the sensor measures the distance between it and the pendulum. So my objective was to start a timer when the distance between the pendulum and the sensor is less than or equal to 5. As at this distance the pendulum is directly above the sensor. And then to start the timer again when the pendulum comes back onto the sensor.
Doing this will give me the time period. In my attempt I decided to use the <time.h> library and get the current time when the pendulum is on and off the sensor and then a subtraction to get the time period.
My code is below:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int timeinfo;
int timeinfo1;
int timePeriod;
int main()
{
int i =0;
for (i=0;i<20;i++){
if (i<=5){
time_t rawtime;
struct tm * timeinfo;
time ( &rawtime );
timeinfo = localtime ( &rawtime );
printf ( "Current local time and date: %s", asctime (timeinfo) ); //Collecting the current time when i is less than 5
}
else{
time_t rawtime;
struct tm * timeinfo1;
time ( &rawtime );
timeinfo1 = localtime ( &rawtime );
printf ( "Current local time and date: %s", asctime (timeinfo1) );//Collecting the current time when i is greater than 5
}
}
timePeriod = timeinfo1-timeinfo;
printf("%s", timePeriod); // Calculating and printing the time period, the initial minus the final time.
}
The results of the code were not what I was expected and are shown as follows:
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
Current local time and date: Thu Jan 30 13:00:10 2020
(null)
Process returned 6 (0x6) execution time : 1.604 s
Press any key to continue.
As evident there is no time change in the values for each iteration and the final string value is returned as null. I think this is because subtracting strings is not possible, but I am not certain.
First of all you define 'timeinfo' and 'timeinfo1' as global integer variables then re-define them as local variables in your code, why ?
Secondly the function you use (localtime) is giving you results with a granularity of second, since that for loop is executing very fast you -most likely- will never get a result bigger than 0 or 1. Therefore i suggest using clock_gettime function so that you can get a resolution up to nanoseconds.
Third problem is the way you print an integer using (%s)
I tried to fix your code as below:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
struct timespec sub_tspec(struct timespec minuend, struct timespec subtrahend)
{
struct timespec result;
if (subtrahend.tv_nsec > minuend.tv_nsec) {
result.tv_sec = minuend.tv_sec - subtrahend.tv_sec - 1;
result.tv_nsec = (minuend.tv_nsec + 1e9) - subtrahend.tv_nsec;
} else {
result.tv_sec = minuend.tv_sec - subtrahend.tv_sec;
result.tv_nsec = minuend.tv_nsec - subtrahend.tv_nsec;
}
return result;
}
int main()
{
int i;
struct timespec timePeriod;
struct timespec tspec1, tspec2;
time_t rawtime;
for (i = 0; i < 20; i++) {
if (i <= 5) {
clock_gettime(CLOCK_REALTIME, &tspec1);
printf("Current time: %ld.%ld\n", tspec1.tv_sec, tspec1.tv_nsec); //Collecting the current time when i is less than 5
} else {
clock_gettime(CLOCK_REALTIME, &tspec2);
printf("Current time: %ld.%ld\n", tspec2.tv_sec, tspec2.tv_nsec); //Collecting the current time when i is greater than 5
}
}
timePeriod = sub_tspec(tspec2, tspec1);
printf("%ld.%ld\n", timePeriod.tv_sec, timePeriod.tv_nsec); // Calculating and printing the time period, the initial minus the final time.
return 0;
}
You can find more detail about clock_gettime here: https://linux.die.net/man/3/clock_gettime

Using Linux mktime to get timezone

I use the following codes to caculate timezone. When I set the timezone to Asia/Singapore, I think I should get 28800 which 28800/3600=8, it should be GMT +8, but it returns 27000/3600=7.5, Am I wrong?
struct tm tSysTime;
long int secs;
memset(&tSysTime,0,sizeof(tSysTime));
tSysTime.tm_year = 70;
tSysTime.tm_mon = 0;
tSysTime.tm_mday = 1;
tSysTime.tm_hour = 0;
tSysTime.tm_min = 0;
tSysTime.tm_sec = 0;
secs = 0-mktime(&tSysTime);
printf("[main] time zone %ld\n",secs);
Diagnosis
According to zdump -v Asia/Singapore on a Mac, the time zone offset from UTC for Singapore between August 1965 and January 1982 was 7h 30m east of UTC:
$ zdump -v Asia/Singapore
Asia/Singapore Fri Dec 13 20:45:52 1901 UTC = Sat Dec 14 03:41:17 1901 SMT isdst=0
Asia/Singapore Sat Dec 14 20:45:52 1901 UTC = Sun Dec 15 03:41:17 1901 SMT isdst=0
Asia/Singapore Wed May 31 17:04:34 1905 UTC = Wed May 31 23:59:59 1905 SMT isdst=0
Asia/Singapore Wed May 31 17:04:35 1905 UTC = Thu Jun 1 00:04:35 1905 MALT isdst=0
Asia/Singapore Sat Dec 31 16:59:59 1932 UTC = Sat Dec 31 23:59:59 1932 MALT isdst=0
Asia/Singapore Sat Dec 31 17:00:00 1932 UTC = Sun Jan 1 00:20:00 1933 MALST isdst=1
Asia/Singapore Tue Dec 31 16:39:59 1935 UTC = Tue Dec 31 23:59:59 1935 MALST isdst=1
Asia/Singapore Tue Dec 31 16:40:00 1935 UTC = Wed Jan 1 00:00:00 1936 MALT isdst=0
Asia/Singapore Sun Aug 31 16:39:59 1941 UTC = Sun Aug 31 23:59:59 1941 MALT isdst=0
Asia/Singapore Sun Aug 31 16:40:00 1941 UTC = Mon Sep 1 00:10:00 1941 MALT isdst=0
Asia/Singapore Sun Feb 15 16:29:59 1942 UTC = Sun Feb 15 23:59:59 1942 MALT isdst=0
Asia/Singapore Sun Feb 15 16:30:00 1942 UTC = Mon Feb 16 01:30:00 1942 JST isdst=0
Asia/Singapore Tue Sep 11 14:59:59 1945 UTC = Tue Sep 11 23:59:59 1945 JST isdst=0
Asia/Singapore Tue Sep 11 15:00:00 1945 UTC = Tue Sep 11 22:30:00 1945 MALT isdst=0
Asia/Singapore Sun Aug 8 16:29:59 1965 UTC = Sun Aug 8 23:59:59 1965 MALT isdst=0
Asia/Singapore Sun Aug 8 16:30:00 1965 UTC = Mon Aug 9 00:00:00 1965 SGT isdst=0
Asia/Singapore Thu Dec 31 16:29:59 1981 UTC = Thu Dec 31 23:59:59 1981 SGT isdst=0
Asia/Singapore Thu Dec 31 16:30:00 1981 UTC = Fri Jan 1 00:30:00 1982 SGT isdst=0
Asia/Singapore Mon Jan 18 03:14:07 2038 UTC = Mon Jan 18 11:14:07 2038 SGT isdst=0
Asia/Singapore Tue Jan 19 03:14:07 2038 UTC = Tue Jan 19 11:14:07 2038 SGT isdst=0
$
Consequently, the result you're getting is the correct offset from UTC in Singapore for 1970-01-01.
Subsidiary question
The current timezone of Singapore is GMT8. What I should do to fix this problem?
The current time zone offset of Singapore is UTC+8 (GMT+8), but historically (specifically, in 1970), that was not the case. You will have to devise a time nearer to the current time when the time zone offset in the database is correct. That seems to mean a time since 1982-01-01 00:30:00 +08:00. So, maybe you should use 2000-01-01 00:00:00? You'll need the number of seconds in the 30 years since 1970-01-01 00:00:00 to get the answer right, and you'll use that in place of the 0 in 0 - mktime(&tSysTime). I believe the relevant number is 946684800.
Revised C code
#include <stdio.h>
#include <string.h>
#include <time.h>
int main(void)
{
struct tm tSysTime;
long int secs;
memset(&tSysTime,0,sizeof(tSysTime));
tSysTime.tm_year = 100;
tSysTime.tm_mon = 0;
tSysTime.tm_mday = 1;
tSysTime.tm_hour = 0;
tSysTime.tm_min = 0;
tSysTime.tm_sec = 0;
secs = 946684800 - mktime(&tSysTime);
printf("[main] time zone %ld\n",secs);
return 0;
}
Example runs
$ TZ=Asia/Singapore tzoff
[main] time zone 28800
$ TZ=US/Pacific tzoff
[main] time zone -28800
$ TZ=UTC0 tzoff
[main] time zone 0
$
How did you find 946684800
That's a deep-seated mystery. No, it isn't. I have a pair of programs, strptime and timestamp that helped (plus a casual use of bc).
I live in the 'US/Pacific' (officially, America/Los_Angeles, but I don't like that name much — I live a lot closer to San Francisco) time zone (UTC-08:00 in the winter; UTC-07:00 during the summer).
$ strptime '2000-01-01 00:00:00'
946713600 = 2000-01-01 00:00:00
$ timestamp 946713600
946713600 = Sat Jan 01 00:00:00 2000
$ timestamp -Z 946713600
946713600 = Sat Jan 01 00:00:00 2000 -08:00
$ bc <<< '946713600 - 8 * 3600'
946684800
$ timestamp -Z 946684800
946684800 = Fri Dec 31 16:00:00 1999 -08:00
$ TZ=UTC0 timestamp -Z 946684800
946684800 = Sat Jan 01 00:00:00 2000 +00:00
$
If you spend the time working out how, you can probably do that with GNU date.

mktime() giving different results for same input in different timezone

Here is the piece of code for converting Fri Jan 1 00:00:00 IST 1970 to EPOCH
memset(&Date_st,0,sizeof(struct tm));
Date_st.tm_year = 70;
Date_st.tm_mon = 0;
Date_st.tm_mday = 1;
Date_st.tm_hour = 24;
Date_st.tm_min = 0;
Date_st.tm_sec = 0;
Date_st.tm_isdst = 0 ;
date_in_seconds = mktime( &Date_st );
The code is running on two servers having different time zones
Server_1!:user_1> Tue Aug 25 11:03:51 IDT 2015
Server_2!:user_2> Tue Aug 25 05:05:03 CLT 2015
Now the code gives different output on different servers for same input which is Fri Jan 1 00:00:00 IST 1970
Server_1 -> 79200
Server_2 -> 100800
Can someone suggest why output is different? and how can it be make same {I want it to be same} ?
That's what timezones are all about, the local time is different.
You might want to try the gmtime function instead, if you want a common reference for the time.

DST-switch-aware getter for UNIX timestamp of current day's local time midnight

(Language/API: Standard C 89 library and / or POSIX)
Probably a trivial question, but I've got a feeling that I'm missing something.
I need to implement this function:
time_t get_local_midnight_timestamp(time_t ts);
That is, we get arbitrary timestamp (from the last year, for example), and return it rounded up to the midnight of the same day.
The problem is that the function must be aware of DST switches and DST rules changes (like DST cancellation and/or extension).
The function must also be future-proof, and cope with weird TZ changes (like shift of time zone 30 minutes ahead etc.).
(The reason I need all this that I need to implement look up into some older statistics data.)
As far as I understand, naïve approach with zeroing out struct tm time fields would not work — precisely because of DST stuff (looks like in DST-change day there are two local midnight time_t timestamps).
Please point me in the right direction...
I doubt that it can be done with standard C 89, so POSIX-specific solutions are acceptable. If not POSIX, then something Debian-specific would do...
Update: Also: Something tells me that I should also take leap seconds in account. Maybe I should look into trying to directly use Tz database... (Which is rather sad — so much /perceived/ overhead for so small task.) ...Or not — seems that libc should use it, so maybe I'm just doing it wrong...
Update 2: Here is why I think that naïve solution does not work:
#include <stdio.h>
#include <time.h>
int main()
{
struct tm date_tm;
time_t date_start = 1301173200; /* Sunday 27 March 2011 0:00:00 AM MSK */
time_t midnight = 0;
char buf1[256];
char buf2[256];
int i = 0;
for (i = 0; i < 4 * 60 * 60; i += 60 * 60)
{
time_t date = date_start + i;
localtime_r(&date, &date_tm);
strftime(buf1, 256, "%c %Z", &date_tm);
date_tm.tm_sec = 0;
date_tm.tm_min = 0;
date_tm.tm_hour = 0;
midnight = mktime(&date_tm);
strftime(buf2, 256, "%c %Z", &date_tm);
printf("%d : %s -> %d : %s\n", (int)date, buf1, (int)midnight, buf2);
}
}
Output (local time was MSD at the moment when I run this):
$ gcc time.c && ./a.out
1301173200 : Sun Mar 27 00:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301176800 : Sun Mar 27 01:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301180400 : Sun Mar 27 03:00:00 2011 MSD -> 1301169600 : Sat Mar 26 23:00:00 2011 MSK
1301184000 : Sun Mar 27 04:00:00 2011 MSD -> 1301169600 : Sat Mar 26 23:00:00 2011 MSK
As you can see, two midnights.
I ran your code with the TZ environment variable set to "Europe/Moscow" and was able to reproduce your output. Here's what I think is going on:
On the first two lines, everything is fine. Then we "spring ahead" and 2 AM becomes 3 AM. Let's use gdb to break on entry to mktime and see what its argument is each time:
hour mday mon year wday yday isdst gmtoff tm_zone
0 27 2 111 0 85 0 10800 MSK
0 27 2 111 0 85 0 10800 MSK
0 27 2 111 0 85 1 14400 MSD
0 27 2 111 0 85 1 14400 MSD
So what has happened? Your code sets the hour to 0 each time, but this is a problem after the DST switch, because the impossible has happened: it is now "before" the DST switch in terms of the time of day, yet isdst is now set and gmtoff has been increased by one hour. By hacking up the time, you have "created" a time of midnight but with DST enabled, which is basically invalid.
You may now wonder, how can we get out of this mess? Do not despair! When you are adjusting the tm_hour field by hand, simply admit that you no longer know what the DST status is by setting tm_isdst to -1. This special value, which is documented in man localtime, means the DST status is "not available." So the computer will figure it out, and everything should work fine.
Here's my patch for your code:
date_tm.tm_hour = 0;
+ date_tm.tm_isdst = -1; /* we no longer know if it's DST or not */
Now I get this output, I hope is what you want:
$ TZ='Europe/Moscow' ./a.out
1301173200 : Sun Mar 27 00:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301176800 : Sun Mar 27 01:00:00 2011 MSK -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301180400 : Sun Mar 27 03:00:00 2011 MSD -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK
1301184000 : Sun Mar 27 04:00:00 2011 MSD -> 1301173200 : Sun Mar 27 00:00:00 2011 MSK

Resources