OXXO
OXXO

Reputation: 724

daylight saving time influences the result of mktime

I have an issue trying to get the exact GPS time. Looks like the issue is related to command mktime used in the code. To get the correct GPS time I have added -2 hrs in UTC.

But the purpose is to have a code to calculate the GPS time automatically and don't use manually changes at UTC-2 as an example

Input file GMT time ( > 3 hrs in local time )

18/05/21 08:18:29
18/05/21 08:20:25
18/05/21 08:21:02

When I run the code below, got 1 hour less :

gawk -F'[/: ]' -v ts=$(date -d'01/06/1980' +%s)  \
                -v lap=18 '{$1="" d=sprintf(20$0); 
                            print mktime(d)+lap-ts }'  file

result output GPS TIME ( < 1 hr )

1210922327
1210922443
1210922480

When I run the code below, got exactly the correct GPS time.

gawk -F'[/: ]' -v ts=$(TZ=UTC-2 date -d'1/6/1980 0:00' +%s)  \
                -v lap=18 '{$1="" d=sprintf(20$0); 
                            print mktime(d)+lap-ts }' file

Result output GPS TIME ( exactly time: OUTPUT DESIRED)

1210925927
1210926043
1210926080

Thanks in advance

Upvotes: 1

Views: 705

Answers (1)

kvantour
kvantour

Reputation: 26531

What you are encountering is mostly related to the time-zone settings of your system. Due to Daylight Saving Time, your time-zone shifted one hour which causes the discrepancy. A very nice post of detecting DST, can be found here. Copying its examples, we can show that for the time-zone TZ=Europe/Stockholm, the time-zone changes to summer time depending on the date :

$ TZ=Europe/Stockholm date +%Z # CET or CEST depending of when its run
$ TZ=Europe/Stockholm date --date=20170101 +%Z # CET
$ TZ=Europe/Stockholm date --date=20170601 +%Z # CEST
$ TZ=CET date --date=20170101 +%Z # CET
$ TZ=CET date --date=20170601 +%Z # CEST, note that its auto adjusted to CEST

and therefore it will for sure have an effect on the epoch time which is given from 1970-01-01T00:00:00 UTC. With zdump we see when DST comes into affect :

$ zdump -v /usr/share/zoneinfo/Europe/Stockholm | grep 2018
/usr/share/zoneinfo/Europe/Stockholm  Sun Mar 25 00:59:59 2018 UTC = Sun Mar 25 01:59:59 2018 CET isdst=0 gmtoff=3600
/usr/share/zoneinfo/Europe/Stockholm  Sun Mar 25 01:00:00 2018 UTC = Sun Mar 25 03:00:00 2018 CEST isdst=1 gmtoff=7200
/usr/share/zoneinfo/Europe/Stockholm  Sun Oct 28 00:59:59 2018 UTC = Sun Oct 28 02:59:59 2018 CEST isdst=1 gmtoff=7200
/usr/share/zoneinfo/Europe/Stockholm  Sun Oct 28 01:00:00 2018 UTC = Sun Oct 28 02:00:00 2018 CET isdst=0 gmtoff=3600

and this is seen in the epoch time as :

$ TZ=Europe/Stockholm date -d "2018-03-25 01:59:59" +%s
1521939599
$ TZ=Europe/Stockholm date -d "2018-03-25 03:00:00" +%s
1521939600
$ TZ=Europe/Stockholm date -d "2018-03-25 02:00:00" +%s
date: invalid date ‘2018-03-25 02:00:00’

As you see, for TZ=Europe/Stockholm, the time 2018-03-25T02:00:00 does not exist and the other two are only 1 sec apart.


Summary, what does this all mean: it essentially means that your system automatically compensates for DST, unless your TZ is UTC. And this play a role on all date related command such as systime(), date or even Awk's mktime().


Can we avoid DST compensation with awk: Since the OP wants the GPS-time, i.e. the total seconds since 1980-01-06T00:00:00, you essentially subtract two times. So if both are computed in the same TZ without DST correction, you always get the correct result. There are 2 ways to do this :

  • execute your command in a specific timezone: By forcing the system to work in a single TZ (such as UTC, UTC+2, ...), there will be no DST correction. For the OP's question, the TZ of interest is UTC.

    $ TZ="UTC" awk 'BEGIN { ts  = mktime("1980 01 06 00 00 00") }
                    { datespec="20"$0; gsub(/[/:]/," ",datespec);
                      print mktime(datespec) + lap - ts
                    }' lap=18 file
    

    or from awk 4.20 onwards you can tell mktime() to assume that the date is in UTC by using the UTC flag. (mktime(datespec [, utc-flag ]))

    $ awk 'BEGIN { ts  = mktime("1980 01 06 00 00 00",1) }
           { datespec="20"$0; gsub(/[/:]/," ",datespec);
             print mktime(datespec,1) + lap - ts
           }' lap=18 file
    

    both result in the following output.

    1210925927
    1210926043
    1210926080
    

    In both cases, you don't need to worry about your system-timezone and all the mumbo-jumbo related to daylight savings time.

  • disable DST correction with mktime: When adding a DST entry to the datespec part of mktime, you can tell the system to alwyas work in DST or not or let the system figure it out by himself. The latter is what you do not want. datespec is a string of the form YYYY MM DD HH MM SS [DST]. And this brings it down too:

    $ awk 'BEGIN { ts  = mktime("1980 01 06 00 00 00 0") }
           { datespec="20"$0; gsub(/[/:]/," ",datespec);
             print mktime(datespec" 0") + lap - ts
           }' lap=18 file
    

Documentation for mktime awk 4.2.0 onwards:

mktime(datespec [, utc-flag ]) Turn datespec into a timestamp in the same form as is returned by systime(). It is similar to the function of the same name in ISO C. The argument, datespec, is a string of the form "YYYY MM DD HH MM SS [DST]". The string consists of six or seven numbers representing, respectively, the full year including century, the month from 1 to 12, the day of the month from 1 to 31, the hour of the day from 0 to 23, the minute from 0 to 59, the second from 0 to 60,55 and an optional daylight-savings flag.

The values of these numbers need not be within the ranges specified; for example, an hour of -1 means 1 hour before midnight. The origin-zero Gregorian calendar is assumed, with year 0 preceding year 1 and year -1 preceding year 0. If utc-flag is present and is either non-zero or non-null, the time is assumed to be in the UTC time zone; otherwise, the time is assumed to be in the local time zone. If the DST daylight-savings flag is positive, the time is assumed to be daylight savings time; if zero, the time is assumed to be standard time; and if negative (the default), mktime() attempts to determine whether daylight savings time is in effect for the specified time.

If datespec does not contain enough elements or if the resulting time is out of range, mktime() returns -1.

Upvotes: 3

Related Questions