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
There is some homework for SAS and I just can't seem to find the right way to do it. Hopefully, some of you will be able to help.
We start with a table where we have the following variables:
City State Temp January Temp Feb Temp Mar ... Temp Dec
First, we have to calculate the mean temperature (per month, so for 12 different variables) and per state (so there are always a few cities per state).
I used this code:
PROC SORT DATA=Homework;
BY state;
RUN;
PROC MEANS DATA=Homework;
VAR JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC;
BY State
OUTPUT OUT=MTSM (DROP=_type_ _freq_) MEAN=;
RUN;
My result is a table in which I have 53 rows (one per state) and 1 column per month (and a first column for the states of cours). Something like this:
State JAN FEB ... DEC
State1 xjan xfeb ... xdec
State2
...
State53
Now I need to use an Array statement to make a new table in long format:
State Month Mean_temp
State1 JAN xjan
state1 FEB xfeb
. MAR ...
. APR ...
. ... ...
State1 DEC xdec
State 2 JAN ...
...
DEC
...
State53 JAN
FEB
...
Does someone have an idea of how to do this? I'm completely lost.
This is what I tried:
DATA MTSM2;
SET MTSM;
BY state;
ARRAY newvars {1} Mean_Temp;
ARRAY oldvars {1, 12} JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC;
DO Month = JAN to DEC;
DO k=1;
newvars{k} = oldvars{k, Month};
END;
OUTPUT;
END;
KEEP state Month Mean_Temp;
RUN;
I got following error: ERROR: Array subscript out of range at line 30 column 22 :'(
What am I doing wrong? I have been changing this in many ways, but always get the same error.
Thanks in advance!
You can get the table you want by using a more specific output statement in proc means/proc summary:
/*Generate some dummy data*/
data have;
call streaminit(1);
do j = 1 to 10;
do state = 'a', 'b', 'c';
array months[12] m1-m12;
do i = 1 to dim(months);
months[i] = rand('uniform');
end;
output;
end;
end;
drop i j;
run;
proc summary nway data = have;
var m1-m12;
class state;
output out = want(drop = _TYPE_ _FREQ_) mean=;
run;
You are very close.
There is no need to use ARRAY for the new variable since it is just one. There is no need to tell SAS how many variables there are in the array when you have listed the actual variable names. And arrays are indexed by integers, not strings. You can use the VNAME() function to find the name of the variable addressed by the index into the array. The BY statement is not needed.
DATA MTSM2;
SET MTSM;
ARRAY oldvars JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC;
length month $32 mean_temp 8;
DO month_number = 1 to 12 ;
month=vname( oldvars[month_number] );
mean_temp = oldvars[month_number] ;
OUTPUT;
END;
KEEP state Month Mean_Temp;
RUN;
If the homework is to pivot the data, using ARRAY, from the categorically organized layout (state/month/mean) to a wide layout (state/month-1...month-12) you can use BY processing and index determination to fill an array.
Essentially for each BY group there will be one row output.
One way is to use a DOW loop in which the SET statement is inside an explicit loop.
data want(keep=state jan--dec);
do until (last.state);
set have;
by state;
array months jan feb mar apr may jun jul aug sep oct nov dec;
index = (index('JANFEBMARAPRMAYJUNJULAUGSEPOCTNOVDEC', trim(month))+2)/3;
months(index) = mean;
end;
run;
If the data is known to have every month, the index 'lookup' is not needed and can be retrieved directly from the do loop index variable:
data want(keep=state jan--dec);
do _n_ = 1 by 1 until (last.state); /* repurpose _n_ */
set have;
by state;
array months jan feb mar apr may jun jul aug sep oct nov dec;
months(_n_) = mean;
end;
run;
Update
Using array to pivot data from an across layout to a down layout. Iterate a loop over the array elements and output name/value pairs within the loop.
data want (keep=state month percent);
set have;
array months jan feb mar apr may jun jul aug sep oct nov dec;
do _n_ = 1 to dim(months);
month = vname(months(_n_)); /* name */
percent = months(_n_); /* value */
OUTPUT;
end;
run;
Proc TRANSPOSE can perform the same data transformation.
Array based pivoting is very useful when you want to transpose two or more arrays at the same time. An example would be if you had variables
jan_percent to dec_percent and
jan_rating to dec_rating
that you wanted to pivot into a data form of month/percent/rating. Such a transformation with TRANSPOSE requires multiple proc steps (one per array).
Sounds like you simply want to use a Class Statement instead of a By Statement?
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).
ctime() function is supposed to give the time in string format since Epoch for the seconds passed in. This is my code:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main(int argc, int **argv)
{
time_t tmp=86400; // Seconds for one day
char *s;
s = ctime(&tmp);
if(!s) perror("ctime");
else printf("%s", s);
return 0;
}
The program gives correct output on my Ubuntu 12.04, 32 bit machine:
Fri Jan 2 01:00:00 1970
But the same code gives wrong output on my Scientific Linux, 64-bit machine:
Thu Jan 1 16:00:00 1970
Apparently, according to this machine Epoch is:
Wed Dec 31 16:00:00 1969
Any idea why Scientific Linux 6 is giving wrong output?!
A time_t value always [1] represents the number of seconds since the epoch, which is 1970-01-01 00:00:00 UTC -- plus or minus whatever handwaving is required to acknowledge that we're probably ignoring leap seconds. So (time_t)86400 represents a single moment in time, regardless of your current time zone.
The ctime() function returns a pointer to an awkwardly formatted[2] string expressed in local time. I'm 8 hours west of UTC, so on my system your program's output is:
Thu Jan 1 16:00:00 1970
(the epoch plus 24 hours from your 86400, minus 8 hours for my time zone).
A system's idea of the current time zone can be determined in any of several ways. On my Ubuntu system, /etc/timezone contains the line America/Los_Angeles. On CentOS, which should be similar to Scientific Linux, /etc/localtime is a binary timezone data file.
On both systems, the system default time zone can be overridden by setting the $TZ environment variable, with UTC or the empty string denoting UTC. If I set $TZ to either UTC or "", your program's output is:
Fri Jan 2 00:00:00 1970
The epoch is always the same moment in history, but it can be expressed differently. For example (using the GNU Coreutils date command, showing the epoch itself rather than 24 hours after the epoch as your program does):
$ date -d #0
Wed Dec 31 16:00:00 PST 1969
$ date -u -d #0
Thu Jan 1 00:00:00 UTC 1970
$ TZ=UTC date -d #0
Thu Jan 1 00:00:00 UTC 1970
$ TZ= date -d #0
Thu Jan 1 00:00:00 UTC 1970
$
Note that if you're in the UK, your local time matches UTC only part of the year.
[1] Well, nearly always. POSIX guarantees that a time_t value represents seconds since the epoch, but the C standard says only that it's an arithmetic type capable of representing times. And you can store any value you like in a time_t object, and have it mean whatever you like, but all the standard C and POSIX functions that deal with time_t values treat them as seconds since the epoch.
[2] The format produced by ctime() and asctime() is rather awkward, for historical reasons. It's of the form Sun Sep 16 01:03:52 1973\n\0. Note the lack of any time zone information; also the trailing '\n' can be confusing (I've seen extraneous blank lines in log files because of it). The strftime() function gives you a lot more flexibility. I recommend using ISO 8601 format wherever possible.
the number of seconds should be in UTC - number of seconds elapsed since 00:00 hours, Jan 1, 1970 UTC
Look at the description for time_t:
http://www.cplusplus.com/reference/ctime/time_t/
The manual page you refer to says
The ctime(), gmtime() and localtime() functions all take an argument of data
type time_t which represents calendar time. When interpreted as an absolute
time value, it represents the number of seconds elapsed since the Epoch,
1970-01-01 00:00:00 +0000 (UTC).
And this some ctime implementation
char *
ctime(timep)
const time_t * const timep;
{
return asctime(localtime(timep));
}
As you can see, it uses localtime, ie time relative to timezone of the current process.
(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