Three layers of abstraction
timescales is built around three concentric ideas.
Working out from the centre:
-
Timeframes — the atomic vocabulary
(
YEAR,MONTH,HOUR, …). -
Tokens — named recipes for the labels at one
timeframe (e.g.
m12=m01..m12,m12a=JAN..DEC). - Calendars — the actual partition of a year into labelled leaves, formed by composing tokens.
Most users only ever touch layer 3.
1. Timeframes
A timeframe is a unit of time at which a calendar can carry labels. The package ships with a fixed core set:
CORE_TIMEFRAMES
#> [1] "YEAR" "QUARTER" "MONTH" "MDAY" "YDAY" "HOUR" "MINUTE"
#> [8] "SECOND" "WDAY" "MWEEK" "WEEK"The function as_timeframe() extracts the value of a
chosen timeframe from any datetime:
t <- as.POSIXct("2025-04-15 13:45", tz = "UTC")
as_timeframe(t, "MONTH")
#> [1] 4
as_timeframe(t, "MONTH", format = "token")
#> [1] "m04"
as_timeframe(t, "HOUR")
#> [1] 13
as_timeframe(t, "WDAY", format = "token")
#> [1] "TUE"format = "token" returns the same label style that the
built-in tokens use, which is what the calendar machinery needs
internally.
2. Tokens
A token is a named, reusable recipe for the labels of one timeframe, plus each label’s within-year share. Tokens are how vocabularies are shared across calendars.
list_tokens()
#> [1] "d360" "d364" "d365" "d366" "h168" "h24" "m12" "m12a" "min60"
#> [10] "q4" "w52" "w53" "wd7"
get_token("m12")$expand() |> head(3)
#> label share
#> 1 m01 0.08493151
#> 2 m02 0.07671233
#> 3 m03 0.08493151
get_token("h24")$expand() |> head(3)
#> label share
#> 1 h00 0.04166667
#> 2 h01 0.04166667
#> 3 h02 0.04166667A few built-ins:
| Token | Timeframe | Labels | Notes |
|---|---|---|---|
d365 |
YDAY | d001..d365 |
day-of-year, 365-day calendar |
m12 |
MONTH | m01..m12 |
numeric month |
m12a |
MONTH | JAN..DEC |
abbreviated month |
q4 |
QUARTER | Q1..Q4 |
day-weighted shares |
wd7 |
WDAY | MON..SUN |
ISO weekday order |
h24 |
HOUR | h00..h23 |
hour-of-day |
h168 |
HOUR | h000..h167 |
hour-of-week |
Custom tokens are added with register_token().
3. Calendars
A Calendar is the actual product of one or more tokens. The leaves of the resulting tree are the slices the rest of your model talks about.
Three constructors, in increasing flexibility:
calendar("m12_h24") # layer 1: by name
calendar_build("m12", "h24") # layer 2: by token list
calendar_from_leaves(leaves, ...) # layer 3: by raw leaf tableThe first two compose tokens with a Cartesian product; share is the
product of per-token shares scaled to year_fraction. The
third is the escape hatch for irregular calendars (e.g. representative
weeks that intentionally do not cover the full year).
4. Recasting between calendars
Conversion is the central verb. The same recast()
function handles upsampling, downsampling, and irregular-to-irregular
mappings, given that both calendars cover the same year fraction.
cal_m <- calendar("m12") # source: monthly
cal_q <- calendar("q4") # target: quarterly
monthly <- data.frame(
slice = cal_m@leaves$slice,
load = c(120, 118, 105, 92, 85, 88, 95, 100, 98, 90, 105, 122)
)
recast(monthly, from = cal_m, to = cal_q, year = 2025,
rule = "weighted_mean", by = "day")
#> slice load
#> 1 Q1 114.21111
#> 2 Q2 88.29670
#> 3 Q3 97.66304
#> 4 Q4 105.67391Design boundaries
- Year-bounded. A calendar describes one year (or a fraction of one). Multi-year horizons are out of scope for v0.1; build them on top of this layer.
- Label-stable. A calendar’s slice IDs and ordering are fixed at construction time. Conversions never mutate them.
- No side effects. Every constructor returns a value; nothing is registered or globally configured.