@tbowmo/node-red-small-timer
Version:
Small timer node for Node-RED with support for sunrise, sunset etc. timers
121 lines (105 loc) • 3.87 kB
text/typescript
import { endOfDay, startOfDay } from 'date-fns'
import SunCalc from 'suncalc'
import { capitalizeFirstLetter, isNotUndefinedOrNull } from './utils'
type MoonTimes = keyof SunCalc.GetMoonTimes
type SunTimes = keyof SunCalc.GetTimesResult
type MoonAndSun = SunTimes | MoonTimes
/**
* Encapsulates logic for start and end times, including dynamically adjusted times (sunset, sunrise etc)
*/
export class SunAndMoon {
// times from suncalc, depends on latitude / longitude and time of year
protected sunTimes: SunCalc.GetTimesResult | undefined = undefined
protected moonTimes: SunCalc.GetMoonTimes | undefined = undefined
private lastSunCalcUpdate = -1
protected sunLookup: { [key: number]: MoonAndSun } = {
5000: 'dawn',
5001: 'dusk',
5002: 'solarNoon',
5003: 'sunrise',
5004: 'sunset',
5005: 'night',
5006: 'nightEnd',
5007: 'rise',
5008: 'set',
5101: 'sunrise',
5102: 'sunriseEnd',
5103: 'goldenHourEnd',
5104: 'solarNoon',
5105: 'goldenHour',
5106: 'sunsetStart',
5107: 'sunset',
5108: 'dusk',
5109: 'nauticalDusk',
5110: 'night',
5111: 'nadir',
5112: 'nightEnd',
5113: 'nauticalDawn',
5114: 'dawn',
5115: 'rise', // moon
5116: 'set', // moon
}
/**
*
* @param latitude
* @param longitude
* @param wrapMidnight
*/
constructor(
protected readonly latitude?: number,
protected readonly longitude?: number,
) {
}
public getTimes(now = new Date()): {id: string, label: string, date: Date}[] {
if (!this.latitude || !this.longitude) {
return []
}
this.sunTimes = SunCalc.getTimes(now, this.latitude, this.longitude)
this.moonTimes = SunCalc.getMoonTimes(now, this.latitude, this.longitude)
return Object.entries(this.sunLookup).map(([id, key]) => {
if (Number(id) < 5100) {
return
}
const labelParts = key.split(/(?=[A-Z])/)
let label = labelParts.join(' ').toLocaleLowerCase()
if (this.sunTimes && key in this.sunTimes) {
label = capitalizeFirstLetter(label)
const date = this.sunTimes[(key as SunTimes)]
return {id, label, date}
}
if (this.moonTimes && key in this.moonTimes) {
label = `Moon${label}`
const date = this.moonTimes[(key as MoonTimes)]
if (typeof date === 'object') {
return {id, label, date}
} else {
return {id, label, date: key === 'rise' ? startOfDay(Date.now()) : endOfDay(Date.now())}
}
}
}).filter(isNotUndefinedOrNull)
}
public getSunOrMoonTime(time: number, now = new Date()): Date | undefined{
const v = this.sunLookup[time]
if (this.sunTimes && v in this.sunTimes) {
const z = this.sunTimes[(v as SunTimes)]
return z
}
if (this.moonTimes && v in this.moonTimes) {
const z = this.moonTimes[(v as MoonTimes)]
if (typeof z === 'object') {
return z
}
return v === 'set' ? startOfDay(now) : endOfDay(now)
}
return undefined
}
protected updateSunCalc(now = new Date()) {
// Only necessary to do the calculations once a day
if (this.lastSunCalcUpdate === now.getDay() || !this.latitude || !this.longitude) {
return
}
this.lastSunCalcUpdate = now.getDay()
this.sunTimes = SunCalc.getTimes(now, this.latitude, this.longitude)
this.moonTimes = SunCalc.getMoonTimes(now, this.latitude, this.longitude)
}
}