Weather Cal Custom Component

I’ve been using the iOS Scriptable app and the fantastic Weather Cal widget to have an information rich homescreen for a while now. The built-in COVID widget only had data to the country level but I wanted to see the New York Times’ data at the US county level.

OK, so “New York City” isn’t technically a county but the Times makes an exception and groups the boroughs together—call it a hometown bias.

This also serves as a decent example of how to make a custom Weather Cal component that loads, caches, and displays data from a custom API.

The New York Times doesn’t present a REST api but they do publish their data to Github about 3 times a day!

# Live moving averages
curl -o data.csv

# Example of the CSV data:
# date,geoid,county,state,cases,cases_avg,cases_avg_per_100k,deaths,deaths_avg,deaths_avg_per_100k
# 2021-12-21,USA-36998,New York City,New York,17958,10049.14,120.54,33,15.43,0.19

# Data.csv holds the last month of data for every US county sorted in ascending date order.
# I only care about the most recent date so we can search it backwards using tac and grep
tac data.csv | grep -m 1 "New York City" > current_cases_avg.txt

# Grab the 6th column
awk -F "\"*,\"*" '{print $6}' current_cases_avg.txt > current_cases_avg_only.txt

# Copy the text file to my webserver
cp current_cases_avg_only.txt /var/www/FAKE/PATH/current_cases_avg_only.txt

This bash script can live in cron and run every hour.

15 * * * * ~/ >/dev/null 2>&1

In Scriptable, weather-cal.js in the existing custom = {} variable add the following:

  async setupDanielC19() {
    const danielC19Path =, "weather-cal-danielC19")
    // getCache(path REQUIRED, minTime OPTIONAL, maxTime OPTIONAL)
    let danielC19Data = code.getCache(danielC19Path, 20, 60)

    if (!danielC19Data || danielC19Data.length == 0 || danielC19Data.cacheExpired) 
      try {

      	 // NOTE: Be sure to put in a real URL here!
        let rawRequest = new Request("")
        let rawData = await rawRequest.loadString()
        if (!rawData || rawData.length == 0) { throw 0 }

        danielC19Data = Number(rawData)

        // Write Data to cache, JSON.stringify(danielC19Data, null, 2))

      } catch (err) {
        danielC19Data = code.getCache(danielC19Path, 20, 60)
    return danielC19Data
  async danielC19(column) {
    if (!this.danielC19Data) {
      danielC19Data = await custom.setupDanielC19()

    // Set up the stack
    let danielC19Stack = code.align(column)
    danielC19Stack.setPadding(code.padding/2, code.padding*2, code.padding/2, code.padding)
    // Round to an integer and add commas
    const c19rkiLine = code.provideText(`NYC 7-Day Avg: ${Number.parseInt(danielC19Data).toLocaleString('en-US')}`, danielC19Stack)

And finally at the top of weather-cal.js add the new component

const layout = `