UNPKG

@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
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) } }