I already saw some questions/answers related to this question for other programming languages or operating systems but I found no common solution for my specific scenario. I want to get the GMT offset of a given timezone (normal identifier like EDT, CET or Europe/Berlin) in a Linux shell script or in pure C.

One easy solution would be:

$ TZ=":Europe/Berlin" date +%z

But this will print the current GMT offset (including DST if it's currently in use).

Here a better example to show what I mean:

$ TZ=":Europe/Berlin" date +%z --date="1 Jan 2014"
+0100

$ TZ=":Europe/Berlin" date +%z --date="1 May 2014"
+0200

I need an option to always get the non-DST value (here: +0100) for any country/city.

Other questions that could solve my problem and are related to this one:

How to find the correct non-DST timezone (Etc/GMT-1) of a given city (Europe/Berlin) w/o knowing the offset in advance?

How do you generally know that DST is active at a given timestamp?

$ TZ=":Europe/Berlin" date --date="2014-01-01"
Wed Jan  1 00:00:00 CET 2014

$ TZ=":Europe/Berlin" date --date="2014-05-01"
Thu May  1 00:00:00 CEST 2014

The identifiers CEST (Central European Summer Time) and CET (Central European Time) are maybe not commonly known in other parts of the world, so an option to simply get something like DST=1 or DST=0 would be better if someone needs it. Via zdump you can get isdst=0 or isdst=1 for all timestamps that change DST, but it's not so easily usable IMO.

有帮助吗?

解决方案

This will return a given time zone's current year's non-DST offset from GMT in seconds:

zdump -v "Europe/Berlin" | \
sed ":a;N;\$!ba;s/^.*$(echo -n $(date +%Y)) [^ ]* isdst=0 \
gmtoff=\([^\n]*\)\n.*$/\1/"

3600

What's going on here is that sed matches the current year's first non-dst line returned by zdump for that time zone, and then replaces the entirety of the zdump output with just the end of that first matched line, which is the GMT offset in seconds.

If you need it formatted into +HHMM or -HHMM:

offsetArray=($(zdump -v "America/New_York" | \
sed ":a;N;\$!ba;s/^.*$(echo -n $(date +%Y)) [^ ]* isdst=0 \
gmtoff=\(-*\)\([^\n]*\)\n.*$/\1+ \2/"))
echo "${offsetArray[0]:0:1}$(date -ud @${offsetArray[1]} +%H%M)"

-0500

This creates a an array with two elements, the sign and the absolute value of the offset in seconds. It outputs the former, and formats the latter by passing it through date as time since the epoch (1-Jan-1970 00:00:00 UTC/GMT). Because Bash array assignment ignores leading whitespace, the first element of the array is actually "+" or "-+", and what is displayed is the first character (i.e. + or -).

As an aside, I would think that every year listed by zdump should have the same non-DST offset from GMT, and my original answer just had sed grab the first year it found, but I edited the answer to check the current year to accomodate the theoretical edge case where a time zone has changed since 1901 or something.

(You can remove the trailing backslashes to get the instructions all on a single line, or two lines in the case of the second example.)

其他提示

Unless I totally misinterpret your question, this is rather easy by using January, 1st of the year of the given date as reference, e.g.:

TZ=":${city}" date +%z --date="$(date +%Y --date="${date}")-01-01"

Demonstration:

$ cat t.sh
#!/bin/bash

get_current_modifier()
{
    echo $(TZ=":$1" date +%z --date="$2" 2>/dev/null)
}

get_non_dst_modifier()
{
    echo $(TZ=":$1" date +%z --date="$(date +%Y --date="$2")-01-01" 2>/dev/null)
}

is_dst()
{
    local city=$1
    local date=$2
    local offset1=$(get_non_dst_modifier "${city}" "${date}")
    local offset2=$(get_current_modifier "${city}" "${date}")

    if [ -z "${offset1}" ] || [ -z "${offset2}" ]; then
        return 2
    fi
    [ "${offset1}" != "${offset2}" ]
}

today=$(date +%Y-%m-%d)
printf "%-15s  %-10s  %-5s  %-8s  %-8s\n" CITY DATE 'DST?' 'current' 'non-DST'
while read -r city date; do
    [ -n "${city}" ] || continue
    is_dst "${city}" "${date}"
    case $? in
        0) is_dst=true  ;;
        1) is_dst=false ;;
        2) is_dst=error ;;
    esac
    cur_mod=$(get_current_modifier "${city}" "${date}")
    non_dst=$(get_non_dst_modifier "${city}" "${date}")
    printf "%-15s  %-10s  %-5s  %-8s  %-8s\n" "${city}" "${date}" "${is_dst}" "${cur_mod}" "${non_dst}"
done <<EOT
Europe/Berlin $today
Europe/Berlin 2014-01-01
US/Alaska     $today
US/Alaska     2014-01-01
Europe/Moscow $today
Europe/Moscow 2014-01-01
EOT

.

$ ./t.sh
CITY             DATE        DST?   current   non-DST
Europe/Berlin    2014-05-04  true   +0200     +0100
Europe/Berlin    2014-01-01  false  +0100     +0100
US/Alaska        2014-05-04  true   -0800     -0900
US/Alaska        2014-01-01  false  -0900     -0900
Europe/Moscow    2014-05-04  false  +0400     +0400
Europe/Moscow    2014-01-01  false  +0400     +0400
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top