| 1 | package calc |
| 2 | |
| 3 | import "time" |
| 4 | |
| 5 | func init() { |
| 6 | registerClock("NOW", fnNow) |
| 7 | registerClock("TODAY", fnToday) |
| 8 | } |
| 9 | |
| 10 | // excelEpoch is day zero in spreadsheet date-serial numbering — Dec 30, |
| 11 | // 1899, the convention both Excel and LibreOffice Calc use by default. |
| 12 | // A date is not a distinct Value kind in this engine: it is a plain |
| 13 | // number that a date number format later displays as a calendar date — |
| 14 | // the same "currency is a format, not a type" rule (global constraint) |
| 15 | // extended to dates. Cell values are IEEE doubles end to end, so this |
| 16 | // applies here exactly as everywhere else: no rounding at this boundary. |
| 17 | var excelEpoch = time.Date(1899, time.December, 30, 0, 0, 0, 0, time.UTC) |
| 18 | |
| 19 | // dateSerial converts t to spreadsheet date-serial form: whole days since |
| 20 | // excelEpoch, with the time of day as a fraction of one day. |
| 21 | func dateSerial(t time.Time) float64 { |
| 22 | return t.UTC().Sub(excelEpoch).Hours() / 24 |
| 23 | } |
| 24 | |
| 25 | // fnNow returns the pass's pinned instant (env.Now()) as a date serial |
| 26 | // with a fractional day for the time of day. It must call env.Now() and |
| 27 | // nothing else — see corpus_test.go's TestClockIsPinnedWithinAPass, |
| 28 | // written specifically to catch a version of this function that reached |
| 29 | // past env for a live clock instead of the one the pass pinned. |
| 30 | // |
| 31 | // NOW is volatile — its answer differs on every recalculation pass by |
| 32 | // design — so it is never checked against testdata/corpus/formulas.json |
| 33 | // (see testdata/corpus/volatile-excluded.txt); nothing about that |
| 34 | // exclusion weakens the guarantee this function itself must hold, that |
| 35 | // two calls within ONE pass agree. |
| 36 | func fnNow(env Env, args []Value) Value { |
| 37 | if len(args) != 0 { |
| 38 | return errValue(ErrValue) |
| 39 | } |
| 40 | return Value{Kind: KindNum, Num: dateSerial(env.Now())} |
| 41 | } |
| 42 | |
| 43 | // fnToday is fnNow with the time of day truncated to midnight. |
| 44 | func fnToday(env Env, args []Value) Value { |
| 45 | if len(args) != 0 { |
| 46 | return errValue(ErrValue) |
| 47 | } |
| 48 | t := env.Now().UTC() |
| 49 | midnight := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC) |
| 50 | return Value{Kind: KindNum, Num: dateSerial(midnight)} |
| 51 | } |
| 52 | |