module.exports = (env) ->
Promise = env.require 'bluebird'
assert = env.require 'cassert'
M = env.matcher
_ = env.require 'lodash'
suncalc = require 'suncalc'
events = {
sunrise:
name: 'sunrise'
desc: 'top edge of the sun appears on the horizon'
sunriseEnd:
name: 'sunrise ends'
desc: 'bottom edge of the sun touches the horizon'
goldenHourEnd:
name: 'morning golden hour'
desc: 'soft light, best time for photography ends'
solarNoon:
name: 'solar noon'
desc: 'sun is in the highest position'
goldenHour:
name: 'golden hours'
desc: 'evening golden hour starts'
sunsetStart:
name: 'sunset starts'
desc: 'bottom edge of the sun touches the horizon'
sunset:
name: 'sunset'
desc: 'sun disappears below the horizon, evening civil twilight starts'
dusk:
name: 'dusk'
desc: 'evening nautical twilight starts'
nauticalDusk:
name: 'nautical dusk'
desc: 'evening astronomical twilight starts'
night:
name: 'night starts'
desc: 'dark enough for astronomical observations'
nightEnd:
name: 'night ends'
desc: 'morning astronomical twilight starts'
nauticalDawn:
name: 'nautical dawn'
desc: 'morning nautical twilight starts'
dawn:
name: 'dawn'
desc: 'morning nautical twilight ends, morning civil twilight starts'
nadir:
name: 'nadir'
desc: 'darkest moment of the night, sun is in the lowest position'
}
class SunrisePlugin extends env.plugins.Plugin
init: (app, @framework, @config) =>
framework.ruleManager.addPredicateProvider(new SunrisePredicateProvider @config)
class SunrisePredicateProvider extends env.predicates.PredicateProvider
constructor: (@config) ->
env.logger.info """
Your location is set to lat: #{@config.latitude}, long: #{@config.longitude}
"""
return
parsePredicate: (input, context) ->
justNames = (o.name for id, o of events)
allIdsAndNames = _([id, o.name] for id, o of events).flatten().unique().valueOf()
matchToken = null
fullMatch = null
eventId = null
timeOffset = 0
modifier = null
M(input, context)
.match(['its ', 'it is '], optional: yes)
.match(['before ', 'after '], optional: yes, (m, match) => modifier = match.trim())
.optional( (m) =>
next = m
m.matchTimeDuration((m, tp) =>
m.match([' before ', ' after '], (m, match) =>
next = m
timeOffset = tp.timeMs
if match.trim() is "before"
timeOffset = -timeOffset
)
)
return next
)
.match(allIdsAndNames, {acFilter: (s) => s in justNames}, (m, match) =>
if matchToken? and match.length < matchToken.length then return
matchToken = match
fullMatch = m.getFullMatch()
)
if matchToken?
for id, o of events
if id is matchToken or o.name is matchToken
eventId = id
assert eventId?
unless modifier? then modifier = 'exact'
return {
token: fullMatch
nextInput: input.substring(fullMatch.length)
predicateHandler: new SunrisePredicateHandler(@config, eventId, modifier, timeOffset)
}
else
return null
class SunrisePredicateHandler extends env.predicates.PredicateHandler
constructor: (@config, @eventId, @modifier, @timeOffset) ->
gets overwritten by tests
_getNow: -> new Date()
_getEventTime: (refDate, eventId = @eventId, timeOffset = @timeOffset)->
https://github.com/mourner/suncalc/issues/11
eventTimes = suncalc.getTimes(
new Date(refDate.getFullYear(), refDate.getMonth(), refDate.getDate(), 12, 0, 0, 0, 0),
@config.latitude,
@config.longitude
)
add offset
eventTimeWithOffset = new Date(eventTimes[eventId].getTime() + timeOffset)
return eventTimeWithOffset
_getTimeTillEvent: ->
now = @_getNow()
refDate = new Date(now)
if @timeOffset > 0
refDate = new Date(refDate.getTime() + @timeOffset)
eventTimeWithOffset = @_getEventTime(refDate)
if eventTimeWithOffset > now
timediff = eventTimeWithOffset.getTime() - now.getTime()
assert timediff > 0
return timediff
else
get the event for next day:
refDate.setDate(refDate.getDate()+1)
eventTimeWithOffset = @_getEventTime(refDate)
timediff = eventTimeWithOffset.getTime() - now.getTime()
assert timediff > 0
return timediff
_getTimeTillTomorrow: ->
now = @_getNow()
tomorrow = new Date(now)
tomorrow.setDate(now.getDate() + 1)
tomorrow.setHours(0)
tomorrow.setMinutes(0)
tomorrow.setSeconds(0)
tomorrow.setMilliseconds(0)
return tomorrow.getTime() - now.getTime()
setup: ->
setNextTimeOut = =>
switch @modifier
when 'exact'
timeTillEvent = @_getTimeTillEvent()
@timeoutHandle = setTimeout( (=>
setNextTimeOut()
@emit('change', 'event')
), timeTillEvent)
when 'before'
val = @getValueSync()
if val is true
If its before the evnet then next change is the event date:
timeTillEvent = @_getTimeTillEvent()
@timeoutHandle = setTimeout( (=>
setNextTimeOut()
@emit('change', false)
), timeTillEvent)
else
else its after the event, so next event date is 0:00 next day
timeTillTomorrow = @_getTimeTillTomorrow()
@timeoutHandle = setTimeout( (=>
setNextTimeOut()
@emit('change', true)
), timeTillTomorrow)
when 'after'
val = @getValueSync()
if val is false
If its before the evnet then next change is the event date:
timeTillEvent = @_getTimeTillEvent()
@timeoutHandle = setTimeout( (=>
setNextTimeOut()
@emit('change', true)
), timeTillEvent)
else
else its after the event, so next event date is 0:00 next day
timeTillTomorrow = @_getTimeTillTomorrow()
@timeoutHandle = setTimeout( (=>
setNextTimeOut()
@emit('change', false)
), timeTillTomorrow)
setNextTimeOut()
getType: -> if @modifier is 'exact' then 'event' else 'state'
getValueSync: ->
if @modifier is 'exact' then return false
now = @_getNow()
eventTime = @_getEventTime(now)
switch @modifier
when 'before' then return now < eventTime
when 'after' then return now > eventTime
getValue: -> Promise.resolve(@getValueSync())
destroy: ->
clearTimeout(@timeoutHandle)
Finally
Create a instance of sunrise
sunrisePlugin = new SunrisePlugin()
and return it to the framework.
return sunrisePlugin
Sunrise plugin