I’m trying to programmatically edit beeminder roads, and when I examined the existing roadall values I realized I don’t understand the times entirely, which means I don’t know for sure how to submit correct times.
I suspect this is a nontrivial issue, because I while ago I made another attempt to edit roads and apparently made a big mess of one goal somehow, though I don’t remember any details.
In any case, my expectation a priori was that the times are actually just LocalDates specified in an inconvenient/messy format, ideally as timestamps at UTC midnight. But when I look at my current goals, I see something more complex, and I haven’t noticed a particular pattern.
If I look at all the timestamps, convert them to my local timezone, and look at the time portion, I see these counts:
I checked my reminders page, and all of my goals have 3am deadlines, so the variation here (and indeed the times themselves, as 3am local time doesn’t match any of these utc times in either time of the year) doesn’t seem related to the deadlines.
None of these times are close to midnight local time, so it seems plausible that I could naively take the date portion and infer that’s the date they’re describing, but I’m less confident about how I would construct one of these times when I want to add new dates to the goal.
Thanks for asking this; it’s embarrassing how messy it is. Basically the roadall endpoint exposes an implementation detail: the graph matrices want only daystamps, not timestamps, and we represent a daystamp as “the unixtime for noon in New York on the given day”.
(It occurs to me that one way to start untangling that without breaking everyone’s stuff is to make a new, sane version of roadall called graphmatrixall or something – the term “road” is a vestige anyway – and then just encourage everyone to use the new thing. The old thing could exist indefinitely but be undocumented. But in the meantime…)
I suggest wrapping your calls for getting and setting graph matrices with something like this pseudocode:
BZONE = "America/New_York" # Beeminder's hardcoded server timezone
# Take a sane-person date d and return a unixtime in seconds that Beeminder
# understands as effectively a daystamp; namely, noon Eastern time for date d.
function beebrainwash(d):
return unixSeconds(LocalDate(d).atTime(NOON).atZone(BZONE))
# The inverse of above: Turn a unixtime in seconds, ts, that Beeminder uses to
# represent a daystamp and turn it into a sane-person date.
function sanewash(ts):
return datePart(instant(ts).atZone(BZONE))
# Fetch the graph matrix (aka "road") for goal g.
function getroad(g):
raw = apiGetGoal(g).roadall # [[ts, value, rate], ...]
return [ {date: sanewash(ts), value: v, rate: r} for [ts, v, r] in raw ]
# Update goal g with graph matrix m, given as a list of {date, value, rate}
# red line segments.
function setroad(g, m):
roadall = [ [beebrainwash(s.date), s.value, s.rate] for s in m ]
return apiUpdateGoal(g, {roadall: roadall})