Skip to content

Instantly share code, notes, and snippets.

@RickCogley
Last active November 17, 2022 07:21
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save RickCogley/369f9ca6428e425ada718d1193c69723 to your computer and use it in GitHub Desktop.
Save RickCogley/369f9ca6428e425ada718d1193c69723 to your computer and use it in GitHub Desktop.
Use jq to do date math

Current

I am using the fantastic jq to manipulate a REST API's json, into a csv for upsertting into another database system, via its API. Once in the target system, I'm doing some date math, including rounding time stamps to 30 min intervals to allow me to do group them, for standard deviation calculation. The problem is, there is a lot of data and the database chokes when it has to round every record and then do std dev calculations on each.

  • Sample.json shows a small sample of what the input data looks like, but actually it's just stdout from the curl command in the shell script.
  • example-initial.sh shows the important bit of the initial shell script, that uses curl to authenticate against the data location's API, and then use jq to add a couple of columns and export to csv. It works like a charm.
  • ACME-X1-A.csv is an example of the output CSV file, that's then upsertted into the target db.

Goal

Instead of doing compute extensive calculations in the target db every time the user queries the db, thus slowing the UI down, I'd like to pre-calculate some number that will let me group all the data in a 30 min window.

My concept is to do it with MOD, which jq is said to support via % operator.

I want to convert the timestamps to unix time, get the modulus dividing by 1800 (seconds in 30 min), then subtract the modulus from the unix time to get a value that groups the entries in 30 min chunks.

Idea.csv illustrates the concept. I can group on the "Epoch-Modulus", which will be a new field in the db, which will just upload as text, no date-time calculation needed in there.

Shortening the jq code and assuming I'll pipe the json to it:

jq '.' | jq 'map(. + {deviceId:"6e09a5d5-6e1f-7672-4ff0486d588074d8"} + {sensorType:"A"})' | jq -r '["CreatedOn","Value","AlarmId","AlarmName","deviceId","Type"], (.[] | [.CreatedOn, .Value, .AlarmId, .AlarmName, .deviceId, .sensorType]) | @csv' > output.csv

... my question is, how do I manipulate the timestamp I have, with jq, to convert it to unix time, store that as say $myepoch then do $myepoch - ($myepoch % 1800)? I'd like to add those values as a new extra column in the CSV.

I've tried a number of things using fromdate in https://jqplay.org/s/u3_pDNyYfD but, to no avail. I just keep getting errors.

Solution

IRC channel #jq user "go|dfish" pointed out that fromdate expects a timezone, so, if we add the timezone to the timestamp and pass that, we can do the calculation:

 map(.CreatedOn + "Z" | fromdate - (fromdate % 1800))

Thanks go|dfish!

Cleanup timestamps with milliseconds:

 map((.CreatedOn | split(".") | .[0]) + "Z" | fromdate - (fromdate  % 1800))

The example-final.sh shell script shows the working solution in situ.

CreatedOn Value AlarmId AlarmName deviceId Type
2017-01-16T06:59:21.547 12 Device online 6e09a5d5-6e1f-7672-4ff0486d588074d8 A
2017-01-17T00:05:16.98 13 Device offline 6e09a5d5-6e1f-7672-4ff0486d588074d8 A
2017-01-16T00:21:17 20.13 6e09a5d5-6e1f-7672-4ff0486d588074d8 A
2017-01-16T00:31:18 20.17 6e09a5d5-6e1f-7672-4ff0486d588074d8 A
2017-01-16T00:41:18 20.07 6e09a5d5-6e1f-7672-4ff0486d588074d8 A
2017-01-16T00:51:19 20.04 6e09a5d5-6e1f-7672-4ff0486d588074d8 A
2017-01-16T01:01:20 19.98 6e09a5d5-6e1f-7672-4ff0486d588074d8 A
2017-01-16T01:11:21 19.94 6e09a5d5-6e1f-7672-4ff0486d588074d8 A
# Grab json from api and output a csv
compcode="ACME"
rest_token="f9e207a4a70298c"
periodparam="lastHours"
periodnum="24"
deviceid="6e09a5d5-6e1f-7672-4ff0486d588074d8"
sensorname="X1"
sensortype="A"
sensornametype=$sensorname-$sensortype
sensorindex="0"
curl -X "GET" "https://myurl.com/api/getvals?deviceId=$deviceid&sensorIndex=$sensorindex&$periodparam=$periodnum" -H "Token: $rest_token" | jq '.' | jq --arg devid "$deviceid" --arg senstyp "$sensortype" --arg sensnmtyp "$sensornametype" 'map(. + {deviceId:$devid} + {sensorType:$senstyp}) + {Matcher:((.CreatedOn | split(".") | .[0]) + "Z" | fromdate - (fromdate % 1800) | tostring | $sensnmtyp + "-" + .)})' | jq -r '["CreatedOn","Value","AlarmId","AlarmName","deviceId","Type","Matcher"], (.[] | [.CreatedOn, .Value, .AlarmId, .AlarmName, .deviceId, .sensorType, .Matcher]) | @csv' > $compcode-$sensornametype.csv
# Grab json from api and output a csv
compcode="ACME"
rest_token="f9e207a4a70298c"
periodparam="lastHours"
periodnum="24"
deviceid="6e09a5d5-6e1f-7672-4ff0486d588074d8"
sensorname="X1"
sensortype="A"
sensornametype=$sensorname-$sensortype
sensorindex="0"
curl -X "GET" "https://myurl.com/api/getvals?deviceId=$deviceid&sensorIndex=$sensorindex&$periodparam=$periodnum" -H "Token: $rest_token" | jq '.' | jq --arg devid "$deviceid" --arg senstyp "$sensortype" 'map(. + {deviceId:$devid} + {sensorType:$senstyp})' | jq -r '["CreatedOn","Value","AlarmId","AlarmName","deviceId","Type"], (.[] | [.CreatedOn, .Value, .AlarmId, .AlarmName, .deviceId, .sensorType]) | @csv' > $compcode-$sensornametype.csv
Timestamp Unix Epoch Time Secs in 30 min Modulus of Epoch/1800 Epoch-Modulus
2017-01-09T11:01:38 1483927298 1800 98 1483927200
2017-01-09T11:05:38 1483927538 1800 338 1483927200
2017-01-09T11:11:38 1483927898 1800 698 1483927200
2017-01-09T11:31:38 1483929098 1800 98 1483929000
[
{
"CreatedOn": "2017-01-09T12:52:38",
"Value": 19.68,
"AlarmId": null,
"AlarmName": null
},
{
"CreatedOn": "2017-01-09T13:02:39",
"Value": 20.03,
"AlarmId": null,
"AlarmName": null
},
{
"CreatedOn": "2017-01-09T13:12:40",
"Value": 19.28,
"AlarmId": null,
"AlarmName": null
}
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment