Minutes or hours till sunset

Hi, I´m searching for a function / jsonata which gives me every minute the acutal time to sunset (as country germany would be sufficient in terms of accuracy)

I´m using cronplus, where I get solar events (exactly to my address by geocoordinates), but I don´t know how to get the time out of it, and this every minute.

greetings

You can interrogate cron-plus by passing the name of a schedule and command "status".

[{"id":"237eedde7a76e2ef","type":"cronplus","z":"3b562c3873fb06e7","name":"","outputField":"payload","timeZone":"","storeName":"","commandResponseMsgOutput":"output2","defaultLocation":"","defaultLocationType":"default","outputs":2,"options":[{"name":"sunset","topic":"topic1","payloadType":"default","payload":"","expressionType":"solar","expression":"0 * * * * * *","location":"54.159 -2.959","offset":"0","solarType":"selected","solarEvents":"sunset"}],"x":400,"y":120,"wires":[[],["d66e720a7a347e3d","96eb168eac5b07d2","2a18cb95db91c940"]]},{"id":"b9b68a630f6ddc34","type":"inject","z":"3b562c3873fb06e7","name":"Inject every minute","props":[{"p":"payload"}],"repeat":"60","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"command\": \"status\", \"name\": \"sunset\"}","payloadType":"json","x":180,"y":120,"wires":[["237eedde7a76e2ef"]]},{"id":"d66e720a7a347e3d","type":"debug","z":"3b562c3873fb06e7","name":"sunset in ","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.result.status.nextDescription","targetType":"msg","statusVal":"","statusType":"auto","x":600,"y":80,"wires":[]},{"id":"96eb168eac5b07d2","type":"debug","z":"3b562c3873fb06e7","name":"Time Now","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.result.status.serverTime","targetType":"msg","statusVal":"","statusType":"auto","x":600,"y":120,"wires":[]},{"id":"2a18cb95db91c940","type":"debug","z":"3b562c3873fb06e7","name":"Sunset Time","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.result.status.nextDate","targetType":"msg","statusVal":"","statusType":"auto","x":610,"y":160,"wires":[]}]
3 Likes

I don't use CRON+ for that as I find it a bit over-complex for something so specific. Instead I use node-red-contrib-sun-position with this flow which kicks off automatically at startup:

The output goes to MQTT (the location contains a number of location references including country, lat/lon):

It would be easy to add a countdown by subtracting Now from the sunset time. In the function:

const msg1 = {}
msg1.var = "INFO"
msg1.prop = "sun"
msg1.val = {
    "sunrise": msg.sunrise, // The next sunrise (start) time
    "sunset": msg.sunset,   // The next sunset (end) time
    // Countdown in milliseconds. Divide by 60,000 to get minutes.
    "sunsetCountdown": msg.sunset - (new Date()),
    "culmination": {
        "time": msg.culmination.time,
    },
}
node.send(msg1)

I keep the sun position in MQTT as well because it strongly influences light levels inside the house.

Corrected version that also gets rid of the spurious function warning:

"sunsetCountdown": (Number(new Date(msg.sunset)) - Number(new Date())) / 60000,

With cron-plus I get "sunset in 3 hours 47 minutes 12 seconds".

how can I transform this text into minutes ?

x hours *60 + y minutes (ignore seconds) = new payload

The nextDate and ServerTime outputs are strings. Use them to initialise Date objects and subtract one from the other.

I got it:

let text = msg.payload;
let regex = /\d+/g;
let numbers = text.match(regex).map(num => parseInt(num, 10));
msg.payload = numbers[0]*60 + numbers[1];
return msg;

I think you will find that will fail in the last hour before sunset?

yes you are right, any ideas :grimacing:

I think I got it:

const text = msg.payload;
const regex = /(\d+)\s*(hours?|minutes?|seconds?)/g;
let hours = 0;
let minutes = 0;
let seconds = 0;
let match;
while ((match = regex.exec(text)) !== null) {
  const value = parseInt(match[1], 10);
  const unit = match[2].toLowerCase();
  if (unit.startsWith('hour')) {
    hours = value;
  } else if (unit.startsWith('minute')) {
    minutes = value;
  } else if (unit.startsWith('second')) {
    seconds = value;
  }
}
msg.payload = (hours * 60) + minutes;
return msg;

Did you consider this?

It would be much easier if you got a timestamp string rather than the human string. Then you could use my example.

If continuing with the current approach, simply check for the length of the match array. If it is 3 then you still have >1hr, if 2 then >1min and if 1, then you are in the final seconds.

Better still as a Date object like it already is...

Then you can just use getHour() etc. Or use $moment in jsonata or....

Hmm I saw the description of these datatypes as "Date" but I ignored it because clicking on the value does not cycle through different representations the way it does with a timestamp.

Checking just now, if I inject a timestamp, which does cycle through the different representations, is shows as datatype "number". So how does the debug know to perform this trick, why not for date objects?
junction

At a guess, likely because it just wasn't implemented. Injecting dates is new so probably not a consideration at the time that feature was introduced.

1 Like

my solution is:

[{"id":"237eedde7a76e2ef","type":"cronplus","z":"7b74e2db318add18","name":"Sunset","outputField":"payload","timeZone":"","storeName":"","commandResponseMsgOutput":"output2","defaultLocation":"52.565241463993694 8.154653012752533","defaultLocationType":"fixed","outputs":2,"options":[{"name":"sunset","topic":"sunset","payloadType":"default","payload":"","expressionType":"solar","expression":"0 * * * * * *","location":"54.159 -2.959","offset":"-60","solarType":"selected","solarEvents":"sunset"}],"x":380,"y":2580,"wires":[[],["5eb99ae9e7ad9a76"]]},{"id":"b9b68a630f6ddc34","type":"inject","z":"7b74e2db318add18","name":"jede Minute","props":[{"p":"payload"}],"repeat":"60","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"command\": \"status\", \"name\": \"sunset\"}","payloadType":"json","x":110,"y":2580,"wires":[["237eedde7a76e2ef"]]},{"id":"5eb99ae9e7ad9a76","type":"change","z":"7b74e2db318add18","name":"Sonnenuntergang","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.result.status.nextDescription","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":2580,"wires":[["c8d3bdde39344515"]]},{"id":"31b170440cd34e5d","type":"change","z":"7b74e2db318add18","name":"","rules":[{"t":"set","p":"sunset","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":2700,"wires":[[]]},{"id":"c8d3bdde39344515","type":"function","z":"7b74e2db318add18","name":"Sonnenuntergang","func":"const text = msg.payload;\nconst regex = /(\\d+)\\s*(hours?|minutes?|seconds?)/g;\nlet hours = 0;\nlet minutes = 0;\nlet seconds = 0;\nlet match;\nwhile ((match = regex.exec(text)) !== null) {\n  const value = parseInt(match[1], 10);\n  const unit = match[2].toLowerCase();\n  if (unit.startsWith('hour')) {\n    hours = value;\n  } else if (unit.startsWith('minute')) {\n    minutes = value;\n  } else if (unit.startsWith('second')) {\n    seconds = value;\n  }\n}\nmsg.payload = (hours * 60) + minutes;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":610,"y":2640,"wires":[["31b170440cd34e5d"]]}]

thanks to @jbudd and regex

const text = msg.payload;
const regex = /(\d+)\s*(hours?|minutes?|seconds?)/g;
let hours = 0;
let minutes = 0;
let seconds = 0;
let match;
while ((match = regex.exec(text)) !== null) {
  const value = parseInt(match[1], 10);
  const unit = match[2].toLowerCase();
  if (unit.startsWith('hour')) {
    hours = value;
  } else if (unit.startsWith('minute')) {
    minutes = value;
  } else if (unit.startsWith('second')) {
    seconds = value;
  }
}
msg.payload = (hours * 60) + minutes;
return msg;
1 Like