mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-08-01 19:50:37 +00:00
cron: add timezone calculation into the midst. Note: this requires user.preferences.timezoneOffset exist on the user, otherwise it will default to the script-runner timezone (which works fine if user or browser)
This commit is contained in:
parent
b23057593b
commit
6aa9483a0b
3 changed files with 71 additions and 48 deletions
|
|
@ -120,7 +120,7 @@ randomDrop = (user, delta, priority, streak = 0, options={}) ->
|
|||
count: 0
|
||||
paths['items.lastDrop'] = true
|
||||
|
||||
reachedDropLimit = (helpers.daysBetween(user.items.lastDrop.date, +new Date) is 0) and (user.items.lastDrop.count >= 2)
|
||||
reachedDropLimit = (helpers.daysSince(user.items.lastDrop.date, user.preferences) is 0) and (user.items.lastDrop.count >= 2)
|
||||
return if reachedDropLimit
|
||||
|
||||
# % chance of getting a pet or meat
|
||||
|
|
@ -359,7 +359,7 @@ obj.cron = (user, options={}) ->
|
|||
user.lastCron = now; paths['lastCron'] = true
|
||||
return
|
||||
|
||||
daysMissed = helpers.daysBetween(user.lastCron, now, user.preferences?.dayStart)
|
||||
daysMissed = helpers.daysSince user.lastCron, _.defaults({now}, user.preferences)
|
||||
return unless daysMissed > 0
|
||||
|
||||
user.lastCron = now; paths['lastCron'] = true
|
||||
|
|
@ -380,7 +380,7 @@ obj.cron = (user, options={}) ->
|
|||
scheduleMisses = 0
|
||||
_.times daysMissed, (n) ->
|
||||
thatDay = moment(now).subtract('days', n + 1)
|
||||
scheduleMisses++ if helpers.shouldDo(thatDay, repeat, {dayStart:obj.preferences?.dayStart})
|
||||
scheduleMisses++ if helpers.shouldDo(thatDay, repeat, user.preferences)
|
||||
obj.score(user, task, 'down', {times:scheduleMisses, cron:true, paths:paths}) if scheduleMisses > 0
|
||||
|
||||
switch type
|
||||
|
|
|
|||
|
|
@ -2,31 +2,48 @@ moment = require 'moment'
|
|||
_ = require 'lodash'
|
||||
items = require('./items.coffee')
|
||||
|
||||
sod = (timestamp, dayStart=0) ->
|
||||
#sanity-check reset-time (is it 24h time?)
|
||||
dayStart = 0 unless (dayStart = +dayStart) and (0 <= dayStart <= 24)
|
||||
moment(timestamp).startOf('day').add('h', dayStart)
|
||||
###
|
||||
Each time we're performing date math (cron, task-due-days, etc), we need to take user preferences into consideration.
|
||||
Specifically {dayStart} (custom day start) and {timezoneOffset}. This function sanitizes / defaults those values.
|
||||
{now} is also passed in for various purposes, one example being the test scripts scripts testing different "now" times
|
||||
###
|
||||
sanitizeOptions = (o) ->
|
||||
dayStart = if (o.dayStart and 0 <= +o.dayStart <= 24) then +o.dayStart else 0
|
||||
timezoneOffset = if o.timezoneOffset then +(o.timezoneOffset) else +moment().zone()
|
||||
now = if o.now then moment(o.now).zone(timezoneOffset) else moment(+new Date).zone(timezoneOffset)
|
||||
# return a new object, we don't want to add "now" to user object
|
||||
{dayStart, timezoneOffset, now}
|
||||
|
||||
startOfWeek = (options={}) ->
|
||||
o = sanitizeOptions(options)
|
||||
moment(o.now).startOf('week')
|
||||
|
||||
startOfDay = (options={}) ->
|
||||
o = sanitizeOptions(options)
|
||||
moment(o.now).startOf('day').add('h', options.dayStart)
|
||||
|
||||
dayMapping = {0:'su',1:'m',2:'t',3:'w',4:'th',5:'f',6:'s'}
|
||||
|
||||
###
|
||||
Absolute diff between two dates
|
||||
Absolute diff from "yesterday" till now
|
||||
###
|
||||
daysBetween = (yesterday, now, dayStart) -> Math.abs sod(yesterday, dayStart).diff(now, 'days')
|
||||
daysSince = (yesterday, options = {}) ->
|
||||
o = sanitizeOptions options
|
||||
Math.abs startOfDay(_.defaults {now:yesterday}, o).diff(o.now, 'days')
|
||||
|
||||
###
|
||||
Should the user do this taks on this date, given the task's repeat options and user.preferences.dayStart?
|
||||
###
|
||||
shouldDo = (day, repeat, options={}) ->
|
||||
return false unless repeat
|
||||
[dayStart,now] = [options.dayStart||0, options.now||+new Date]
|
||||
selected = repeat[dayMapping[sod(day, dayStart).day()]]
|
||||
return selected unless moment(day).isSame(now,'d')
|
||||
if dayStart <= moment(now).hour() # we're past the dayStart mark, is it due today?
|
||||
o = sanitizeOptions options
|
||||
selected = repeat[dayMapping[startOfDay(_.defaults {now:day}, o).day()]]
|
||||
return selected unless moment(day).zone(o.timezoneOffset).isSame(o.now,'d')
|
||||
if options.dayStart <= o.now.hour() # we're past the dayStart mark, is it due today?
|
||||
return selected
|
||||
else # we're not past dayStart mark, check if it was due "yesterday"
|
||||
yesterday = moment(now).subtract(1,'d').day()
|
||||
return repeat[dayMapping[yesterday]]
|
||||
yesterday = moment(o.now).subtract(1,'d').day() # have to wrap o.now so as not to modify original
|
||||
return repeat[dayMapping[yesterday]] # FIXME is this correct?? Do I need to do any timezone calcaulation here?
|
||||
|
||||
uuid = ->
|
||||
"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace /[xy]/g, (c) ->
|
||||
|
|
@ -124,7 +141,9 @@ module.exports =
|
|||
return undefined if ~path.indexOf('undefined')
|
||||
_.reduce path.split('.'), ((curr, next) -> curr[next]), obj
|
||||
|
||||
daysBetween: daysBetween
|
||||
daysSince: daysSince
|
||||
startOfWeek: startOfWeek
|
||||
startOfDay: startOfDay
|
||||
|
||||
shouldDo: shouldDo
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ expect = require 'expect.js'
|
|||
moment = require 'moment'
|
||||
|
||||
# Custom modules
|
||||
algos = require '../script/algos'
|
||||
helpers = require '../script/helpers'
|
||||
items = require '../script/items'
|
||||
algos = require '../script/algos.coffee'
|
||||
helpers = require '../script/helpers.coffee'
|
||||
items = require '../script/items.coffee'
|
||||
|
||||
### Helper Functions ####
|
||||
|
||||
|
|
@ -16,6 +16,7 @@ beforeAfter = (options={}) ->
|
|||
user = helpers.newUser()
|
||||
[before, after] = [_.cloneDeep(user), _.cloneDeep(user)]
|
||||
before.preferences.dayStart = after.preferences.dayStart = options.dayStart if options.dayStart
|
||||
before.preferences.timezoneOffset = after.preferences.timezoneOffset = (options.timezoneOffset or moment().zone())
|
||||
if options.limitOne
|
||||
before["#{options.limitOne}s"] = [before["#{options.limitOne}s"][0]]
|
||||
after["#{options.limitOne}s"] = [after["#{options.limitOne}s"][0]]
|
||||
|
|
@ -61,15 +62,12 @@ expectDayResetNoDamage = (b,a) ->
|
|||
expect(after).to.eql before
|
||||
|
||||
|
||||
|
||||
|
||||
###### Specs ######
|
||||
|
||||
describe 'User', ->
|
||||
it 'sets correct user defaults', ->
|
||||
user = helpers.newUser()
|
||||
expect(user.stats).to.eql { gp: 0, exp: 0, lvl: 1, hp: 50 }
|
||||
expect(user.party).to.eql { invitation: null }
|
||||
expect(user.items).to.eql { weapon: 0, armor: 0, head: 0, shield: 0 }
|
||||
expect(user.preferences).to.eql { gender: 'm', skin: 'white', hair: 'blond', armorSet: 'v1', dayStart:0, showHelm: true }
|
||||
expect(user.balance).to.eql 0
|
||||
|
|
@ -152,21 +150,28 @@ describe 'Cron', ->
|
|||
describe 'dailies', ->
|
||||
|
||||
describe 'new day', ->
|
||||
#TODO check helpers.isDue()
|
||||
|
||||
###
|
||||
This section runs through a "cron matrix" of all permutations (that I can easily account for). It sets
|
||||
task due days, user custom day start, timezoneOffset, etc - then runs cron, jumps to tomorrow and runs cron,
|
||||
and so on - testing each possible outcome along the way
|
||||
###
|
||||
|
||||
runCron = (options) ->
|
||||
now = moment().startOf('week').add('hours',options.currentHour || 0)
|
||||
{before,after} = beforeAfter({now, daysAgo:1, dayStart:options.dayStart||0, limitOne:'daily'})
|
||||
before.dailys[0].repeat = after.dailys[0].repeat = options.repeat if options.repeat
|
||||
before.dailys[0].streak = after.dailys[0].streak = 10
|
||||
before.dailys[0].completed = after.dailys[0].completed = true if options.checked
|
||||
expect(helpers.shouldDo(now, options.repeat, {dayStart:options.dayStart,now})).to.be.ok() if options.shouldDo
|
||||
algos.cron(after,{now})
|
||||
switch options.expect
|
||||
when 'losePoints' then expectLostPoints(before,after,'daily')
|
||||
when 'noChange' then expectNoChange(before,after)
|
||||
when 'noDamage' then expectDayResetNoDamage(before,after)
|
||||
{before,after}
|
||||
_.each [480, 240, 0, -120], (timezoneOffset) -> # test different timezones
|
||||
now = helpers.startOfWeek({timezoneOffset}).add('hours', options.currentHour||0)
|
||||
{before,after} = beforeAfter({now, timezoneOffset, daysAgo:1, dayStart:options.dayStart||0, limitOne:'daily'})
|
||||
before.dailys[0].repeat = after.dailys[0].repeat = options.repeat if options.repeat
|
||||
before.dailys[0].streak = after.dailys[0].streak = 10
|
||||
before.dailys[0].completed = after.dailys[0].completed = true if options.checked
|
||||
if options.shouldDo
|
||||
expect(helpers.shouldDo(now, options.repeat, {timezoneOffset, dayStart:options.dayStart, now})).to.be.ok()
|
||||
algos.cron(after,{now})
|
||||
switch options.expect
|
||||
when 'losePoints' then expectLostPoints(before,after,'daily')
|
||||
when 'noChange' then expectNoChange(before,after)
|
||||
when 'noDamage' then expectDayResetNoDamage(before,after)
|
||||
{before,after}
|
||||
|
||||
cronMatrix =
|
||||
steps:
|
||||
|
|
@ -178,7 +183,8 @@ describe 'Cron', ->
|
|||
'(simple)': {expect:'losePoints'}
|
||||
|
||||
'due today':
|
||||
defaults: {repeat:{su:true,m:1,t:1,w:1,th:1,f:1,s:true}}
|
||||
# NOTE: a strange thing here, moment().startOf('week') is Sunday, but moment.zone(myTimeZone).startOf('week') is Monday.
|
||||
defaults: {repeat:{su:1,m:true,t:1,w:1,th:1,f:1,s:1}}
|
||||
steps:
|
||||
'pre-dayStart':
|
||||
defaults: {currentHour:3, dayStart:4, shouldDo:true}
|
||||
|
|
@ -192,7 +198,7 @@ describe 'Cron', ->
|
|||
'unchecked': {checked:false, expect: 'losePoints'}
|
||||
|
||||
'NOT due today':
|
||||
defaults: {repeat:{su:false,m:1,t:1,w:1,th:1,f:1,s:1}}
|
||||
defaults: {repeat:{su:1,m:false,t:1,w:1,th:1,f:1,s:1}}
|
||||
steps:
|
||||
'pre-dayStart':
|
||||
defaults: {currentHour:3, dayStart:4, shouldDo:true}
|
||||
|
|
@ -206,7 +212,7 @@ describe 'Cron', ->
|
|||
'unchecked': {checked:false, expect: 'losePoints'}
|
||||
|
||||
'not due yesterday':
|
||||
defaults: {repeat:{su:1,m:1,t:1,w:1,th:1,f:1,s:false}}
|
||||
defaults: {repeat:{su:false,m:1,t:1,w:1,th:1,f:1,s:1}}
|
||||
steps:
|
||||
'(simple)': {expect:'noDamage'}
|
||||
'post-dayStart': {currentHour:5,dayStart:4, expect:'noDamage'}
|
||||
|
|
@ -226,13 +232,11 @@ describe 'Cron', ->
|
|||
it '3 day missed, only 1 due'
|
||||
it '3 day missed, only 1 due, not day start yet'
|
||||
|
||||
it 'calculates day differences with dayStart properly', ->
|
||||
dayStart = 4
|
||||
yesterday = moment().subtract('d', 1).add('h', dayStart)
|
||||
now = moment().startOf('day').add('h', dayStart-1) #today
|
||||
console.log {yesterday: yesterday.format('MM/DD HH:00'), now: now.format('MM/DD HH:00')}
|
||||
console.log {diff: Math.abs(moment(yesterday).diff(moment(now), 'days'))}
|
||||
expect(helpers.daysBetween(yesterday, now, dayStart)).to.eql 0
|
||||
now = moment().startOf('day').add('h', dayStart)
|
||||
console.log {now: now.format('MM/DD HH:00')}
|
||||
expect(helpers.daysBetween(yesterday, now, dayStart)).to.eql 1
|
||||
it 'calculates day differences with dayStart properly', ->
|
||||
dayStart = 4
|
||||
yesterday = helpers.startOfDay {now: moment().subtract('d', 1), dayStart}
|
||||
now = helpers.startOfDay {dayStart: dayStart-1}
|
||||
expect(helpers.daysSince(yesterday, {now, dayStart})).to.eql 0
|
||||
now = moment().startOf('day').add('h', dayStart).add('m', 1)
|
||||
console.log {yesterday,now}
|
||||
expect(helpers.daysSince(yesterday, {now, dayStart})).to.eql 1
|
||||
|
|
|
|||
Loading…
Reference in a new issue