Skip to contents

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

Custom 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: 0

Construction validity

The validator on Calendar enforces:

  • required columns present in @leaftable,
  • timeslice 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_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.0000

datetime_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.