Skip to contents

The Calendar class

Calendar is an S7 class with four properties:

Property Type Purpose
@leaves data.frame One row per slice (the terminal cells).
@timeframes character Ordered timeframe names, coarsest first.
@levels 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     : <S7_object>
#> @ constructor: function(leaves, timeframes, levels, meta) {...}
#> @ validator  : function(self) {...}
#> @ properties :
#>  $ leaves    : S3<data.frame>
#>  $ timeframes: <character>   
#>  $ levels    : <list>        
#>  $ meta      : <list>

@leaves

A plain data.frame with three required columns plus one column per timeframe:

Column Type Required Notes
slice character yes Unique non-empty slice 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(cal@leaves)
#> '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 ...
#>  $ slice  : chr  "Q1_h00" "Q2_h00" "Q3_h00" "Q4_h00" ...

slice 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 @leaves and the join order during recasting.

cal@timeframes
#> [1] "QUARTER" "HOUR"

@levels

Per-timeframe label vocabulary in the order the calendar uses. This fixes deterministic ordering for downstream sorting and plotting.

cal@levels
#> $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()
desc character "" print()
year_start list list(month=1L, day=1L) instant_to_slice(), expand_calendar()
utc_offset_minutes integer 0L instant_to_slice() (planned)
year_fraction numeric 1 validator (sum(share))
year_qualified logical FALSE set by calendar("y_...")

Anything else you put in @meta is preserved untouched.

names(cal@meta)
#> [1] "name"               "desc"               "year_start"        
#> [4] "utc_offset_minutes" "year_fraction"
cal@meta$year_start
#> $month
#> [1] 1
#> 
#> $day
#> [1] 1

The token registry

Tokens live in an internal environment, populated at load time with the built-in set and extensible at run time with register_token().

list_tokens()
#>  [1] "d360"  "d364"  "d365"  "d366"  "h168"  "h24"   "m12"   "m12a"  "min60"
#> [10] "q4"    "w52"   "w53"   "wd7"

A token is a list with two fields:

  • timeframe — one of CORE_TIMEFRAMES,
  • expand — a zero-argument function returning data.frame(label, share) with share summing to 1.
m12 <- get_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.08219178

Custom tokens behave identically to built-ins:

register_token("d4q", "YDAY", function() {
  data.frame(label = c("Q1d", "Q2d", "Q3d", "Q4d"),
             share = c(90, 91, 92, 92) / 365)
})
calendar_build("d4q", "h24")
#> <timescales::Calendar>
#>  @ leaves    :'data.frame':  96 obs. of  5 variables:
#>  .. $ YDAY  : chr  "Q1d" "Q2d" "Q3d" "Q4d" ...
#>  .. $ 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 ...
#>  .. $ slice : chr  "Q1d_h00" "Q2d_h00" "Q3d_h00" "Q4d_h00" ...
#>  @ timeframes: chr [1:2] "YDAY" "HOUR"
#>  @ levels    :List of 2
#>  .. $ YDAY: chr [1:4] "Q1d" "Q2d" "Q3d" "Q4d"
#>  .. $ HOUR: chr [1:24] "h00" "h01" "h02" "h03" ...
#>  @ meta      :List of 5
#>  .. $ name              : chr "d4q_h24"
#>  .. $ desc              : chr ""
#>  .. $ year_start        :List of 2
#>  ..  ..$ month: int 1
#>  ..  ..$ day  : int 1
#>  .. $ utc_offset_minutes: int 0
#>  .. $ year_fraction     : num 1

Construction validity

The validator on Calendar enforces:

  • required columns present in @leaves,
  • slice unique and non-empty,
  • share > 0 and sum(share) == meta$year_fraction,
  • weight >= 0,
  • year_start carries valid month (1..12) and day (1..31),
  • utc_offset_minutes is an integer scalar,
  • each levels[[tf]] is set-equal to unique(leaves[[tf]]).

Any violation throws at construction time, so a Calendar you hold is always internally consistent.

Conversion outputs

recast() returns a data.frame with the same column layout as its input — slice plus the value columns — but with the target calendar’s slice IDs.

src <- data.frame(slice = sprintf("m%02d", 1:12),
                  load  = seq(100, 210, length.out = 12))
recast(src, from = calendar("m12"), to = calendar("q4"),
       year = 2025, rule = "weighted_mean", by = "day")
#>   slice     load
#> 1    Q1 110.0000
#> 2    Q2 140.0000
#> 3    Q3 169.8913
#> 4    Q4 200.0000

instant_to_slice() is the inverse direction (datetime → slice ID) and returns a character vector of the same length as the input, with NA for instants outside the calendar’s coverage.