The Calendar class
Calendar is an S7 class with four
properties:
| Property | Type | Purpose |
|---|---|---|
@leaftable |
data.frame |
One row per timeslice (the terminal cells). |
@timeframes |
character |
Ordered timeframe names, coarsest first. |
@members |
list of character
|
Ordered label vocabulary per timeframe. |
@meta |
list |
Free-form metadata (name, desc, …). |
cal <- calendar("q4_h24", desc = "Quarter × hour-of-day")
S7::S7_class(cal)
#> <timescales::Calendar> class
#> @ parent : <nestedscales::NestedScale>
#> @ constructor: function(leaftable, timeframes, members, meta) {...}
#> @ validator : function(self) {...}
#> @ properties :
#> $ leaftable : S3<data.frame>
#> $ frames : <character>
#> $ members : <list>
#> $ key : <character>
#> $ meta : <list>
#> $ timeframes: <ANY>
@leaftable
A plain data.frame with three required columns plus one
column per timeframe:
| Column | Type | Required | Notes |
|---|---|---|---|
timeslice |
character |
yes | Unique non-empty timeslice ID. |
share |
numeric |
yes |
> 0; sums to meta$year_fraction. |
weight |
numeric |
yes |
>= 0; default share * 8760. |
| one per timeframe | character |
yes | Label at that timeframe. |
str(calendar_leaftable(cal))
#> 'data.frame': 96 obs. of 5 variables:
#> $ QUARTER : chr "Q1" "Q2" "Q3" "Q4" ...
#> $ HOUR : chr "h00" "h00" "h00" "h00" ...
#> $ share : num 0.0103 0.0104 0.0105 0.0105 0.0103 ...
#> $ weight : num 90 91 92 92 90 91 92 92 90 91 ...
#> $ timeslice: chr "Q1_h00" "Q2_h00" "Q3_h00" "Q4_h00" ...timeslice is auto-generated by joining the per-timeframe
labels with an internal separator when not supplied. Users never need to
parse it.
@timeframes
The hierarchy, coarsest first. This sets the column order of
@leaftable and the join order during recasting.
cal@timeframes
#> [1] "QUARTER" "HOUR"
@members
Per-timeframe label vocabulary in the order the calendar uses. This fixes deterministic ordering for downstream sorting and plotting.
cal@members
#> $QUARTER
#> [1] "Q1" "Q2" "Q3" "Q4"
#>
#> $HOUR
#> [1] "h00" "h01" "h02" "h03" "h04" "h05" "h06" "h07" "h08" "h09" "h10" "h11"
#> [13] "h12" "h13" "h14" "h15" "h16" "h17" "h18" "h19" "h20" "h21" "h22" "h23"
@meta
A free-form named list. Recognised keys today:
| Key | Type | Default | Used by |
|---|---|---|---|
name |
character | "" |
print(), register_calendar_conversion()
lookup |
desc |
character | "" |
print() |
year_start |
list | list(month=1L, day=1L) |
datetime_to_timeslice() (YDAY/YEAR anchor),
expand_calendar() (year window) |
utc_offset_minutes |
integer | 0L |
datetime_to_timeslice(), expand_calendar()
(local time = UTC + offset) |
year_fraction |
numeric | 1 |
validator (sum(share)) |
year_qualified |
logical | FALSE |
set by calendar("y_...")
|
tokens |
named chr | — | provenance: token per timeframe (set by
calendar_build()) |
alignment |
named list | — | alignment rule per timeframe, seeded by tokens |
Anything else you put in @meta is preserved
untouched.
names(cal@meta)
#> [1] "name" "desc" "year_start"
#> [4] "utc_offset_minutes" "year_fraction" "tokens"
#> [7] "weights" "default_weight" "coverage_class"
#> [10] "regularity"
cal@meta$year_start
#> $month
#> [1] 1
#>
#> $day
#> [1] 1The token registry
Tokens live in an internal environment, populated at load time with
the built-in set and extensible at run time with
register_calendar_token().
list_calendar_tokens()
#> [1] "d360" "d364" "d365" "d366" "h168" "h24" "hp3" "m12" "m12a"
#> [10] "min60" "q4" "s4" "w52" "w53" "wd7" "wk2"A token is a list with two required fields and one optional:
-
timeframe— one ofCORE_TIMEFRAMES, -
expand— a zero-argument function returningdata.frame(label, share)withsharesumming to 1, -
alignment(optional) — one ofALIGNMENT_RULES, inherited by calendars built from the token.
m12 <- get_calendar_token("m12")
m12$timeframe
#> [1] "MONTH"
head(m12$expand())
#> label share
#> 1 m01 0.08493151
#> 2 m02 0.07671233
#> 3 m03 0.08493151
#> 4 m04 0.08219178
#> 5 m05 0.08493151
#> 6 m06 0.08219178Custom tokens behave identically to built-ins:
register_calendar_token("d4q", "YDAY", function() {
data.frame(label = c("Q1d", "Q2d", "Q3d", "Q4d"),
share = c(90, 91, 92, 92) / 365)
})
calendar_build("d4q", "h24")
#> Calendar: d4q_h24
#> Timeframes (2):
#> - YDAY (4) [token: d4q]
#> - HOUR (24) [token: h24]
#> Leaf timeslices: 96
#> year_fraction: 1
#> year_start: month=1, day=1
#> utc_offset_minutes: 0Construction validity
The validator on Calendar enforces:
- required columns present in
@leaftable, -
timesliceunique and non-empty, -
share > 0andsum(share) == meta$year_fraction, -
weight >= 0, -
year_startcarries validmonth(1..12) andday(1..31), -
utc_offset_minutesis an integer scalar, - each
levels[[tf]]is set-equal tounique(leaves[[tf]]).
Any violation throws at construction time, so a Calendar
you hold is always internally consistent.
Conversion outputs
recast_calendar() returns a data.frame with
the same column layout as its input — timeslice plus the
value columns — but with the target calendar’s timeslice IDs.
src <- data.frame(timeslice = sprintf("m%02d", 1:12),
load = seq(100, 210, length.out = 12))
recast_calendar(src, from = calendar("m12"), to = calendar("q4"),
year = 2025, rule = "weighted_mean", by = "day")
#> timeslice load
#> 1 Q1 110.0000
#> 2 Q2 140.0000
#> 3 Q3 169.8913
#> 4 Q4 200.0000datetime_to_timeslice() is the inverse direction
(datetime → timeslice ID) and returns a character vector of the same
length as the input, with NA for instants outside the
calendar’s coverage.
