user1424739
user1424739

Reputation: 13735

How to convert datestring to local time?

2021-08-04T22:55:12+0000

I want to convert the above string to local time.

But fromdateiso8601 does not work on this format. What is the best way to convert this kind of string to local time?

EDIT: I tried the following but the part of 23:55:12 does not change when the timezone is changed. I'd expect that this should be changed as TZ is changed.

$ TZ=America/New_York jq '.x | gsub("[+]0000"; "Z") | fromdateiso8601| gmtime | strftime("%Y-%m-%dT%H:%M:%S%Z")' <<< '{"x": "2021-08-04T22:55:12+0000" }'
"2021-08-04T23:55:12EST"
$ TZ=Europe/Madrid jq '.x | gsub("[+]0000"; "Z") | fromdateiso8601| gmtime | strftime("%Y-%m-%dT%H:%M:%S%Z")' <<< '{"x": "2021-08-04T22:55:12+0000" }'
"2021-08-04T23:55:12CET"

Upvotes: 1

Views: 713

Answers (1)

peak
peak

Reputation: 116880

The first problem here is that jq's handling of timezones (TZ) is buggy; the second is that jq's built-ins do not recognize timezone offsets.

Unfortunately, there is to my knowledge not much that can easily be done about jq's TZ-related bugginess other than using gojq, the Go implementation of jq, instead.

Fortunately, time offsets can be handled quite easily, e.g. using the datetime_to_seconds filter as defined below.

So, for the time being, the following solution to the stated problem assumes the use of gojq rather than stedolan/jq. It has two main steps:

  1. Use the generic filter datetime_to_seconds to convert the timestamp with an offset to "seconds since the epoch";
  2. Use strflocaltime, which recognizes the environment variable TZ.

The solution is embedded in a bash script to facilitate a comparison between different versions of jq and gojq.

bash script

#!/bin/bash

# Syntax: go TZ

function go {

TZ="$1" $jq -Rr '

# Convert a timestamp with a possibly empty timezone offset to seconds since the Epoch.
# Input should be a string of the form yyyy-mm-ddThh:mm:ss or yyyy-mm-ddThh:mm:ss<OFFSET>
# where <OFFSET> is Z, or has the form [-+]hh:mm or [-+]hhmm 
# If no timezone offset is explicitly given, it is taken to be Z.
def datetime_to_seconds: 
  if test("[-+]")
  then
    sub("(?<s>[-+])(?<d1>[0-9]{2})(?<d2>[0-9]{2})$"; "\(.s)\(.d1):\(.d2)")
    | capture("(?<datetime>^.*T[0-9:]+)(?<s>[-+])(?<hh>[0-9]+):?(?<mm>[0-9]*)")
    | (.datetime +"Z" | fromdateiso8601) as $seconds
    | (if .s == "+" then -1 else 1 end) as $plusminus
    | (.mm | if . == "" then 0 else . end) as $mm
    | ([.hh,$mm] | map(tonumber) |.[0] *= 60 | add * 60 * $plusminus) as $offset
    | ($seconds + $offset)
  else . + (if test("Z") then "" else "Z" end) | fromdateiso8601
       end;

datetime_to_seconds
| strflocaltime("%Y-%m-%dT%H:%M:%S %Z")
'
}

for jq in jq-1.6 jqMaster gojq ; do
    echo $jq is $($jq --version)
done
echo

for TZ in America/New_York  Europe/Madrid  ;do
    for jq in jq-1.6 jqMaster gojq  ; do
    for time in 2021-08-04T22:55:12+0000 ; do
        echo $jq $TZ $time
        echo $time | go $TZ
        echo
     done
    done
done

Output

Here is the output with some "#" annotations.

jq-1.6 is jq-master-2e01ff1
jqMaster is jq-1.6-129-g80052e5-dirty
gojq is gojq 0.12.4 (rev: 244f9f7/go1.16.4)

jq-1.6 America/New_York 2021-08-04T22:55:12+0000
2021-08-04T19:55:12 EST # wrong

jqMaster America/New_York 2021-08-04T22:55:12+0000
2021-08-04T18:55:12 EST # wrong

gojq America/New_York 2021-08-04T22:55:12+0000
2021-08-04T18:55:12 EDT # correct

jq-1.6 Europe/Madrid 2021-08-04T22:55:12+0000
2021-08-05T01:55:12 CET # wrong

jqMaster Europe/Madrid 2021-08-04T22:55:12+0000
2021-08-05T00:55:12 CET # wrong

gojq Europe/Madrid 2021-08-04T22:55:12+0000
2021-08-05T00:55:12 CEST # correct

Upvotes: 1

Related Questions