## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)

## ----setup, message = FALSE---------------------------------------------------
library(mixtime)
library(S7)

## -----------------------------------------------------------------------------
.onLoad <- function(...) {
  methods_register()
}

## ----time-units---------------------------------------------------------------
# Timezone-aware Symmetry454 year and month units
new_time_unit("tu_symmetry454_year",  parent = mt_tz_unit)
new_time_unit("tu_symmetry454_month", parent = mt_tz_unit)

## ----new-calendar-------------------------------------------------------------
cal_symmetry454 <- new_calendar(
  year  = new_time_unit("tu_symmetry454_year",  parent = mt_tz_unit),
  month = new_time_unit("tu_symmetry454_month", parent = mt_tz_unit),
  week  = cal_isoweek$week,
  # Inherit civil-time units (day, hour, minute, second, ...)
  inherit = cal_time_civil,
  class = "cal_symmetry454"
)

cal_symmetry454

## ----calendar-access----------------------------------------------------------
cal_symmetry454$year(1L)
cal_symmetry454$month(1L, tz = "UTC")

## -----------------------------------------------------------------------------
chronon_cardinality(cal_isoweek$day(1L), cal_isoweek$week(1L))

## ----cardinality-fixed--------------------------------------------------------
# Each Symmetry454 year has 12 months
method(chronon_cardinality_fixed, list(cal_symmetry454$month, cal_symmetry454$year)) <-
  function(x, y) {
    12L
  }
chronon_cardinality(cal_symmetry454$month(1L), cal_symmetry454$year(1L))

## -----------------------------------------------------------------------------
chronon_cardinality(cal_gregorian$day(1L), cal_gregorian$month(1L), at = 0L) # Jan 1970
chronon_cardinality(cal_gregorian$day(1L), cal_gregorian$month(1L), at = 1L) # Feb 1970
chronon_cardinality(cal_gregorian$day(1L), cal_gregorian$month(1L), at = 25L) # Feb 1972 (leap year)

## ----cardinality-variable-----------------------------------------------------
method(chronon_cardinality, list(cal_symmetry454$week, cal_symmetry454$month)) <-
  function(x, y, at = NULL) {
    # The number of weeks in each n-month period
    month_size <- y@n
    nweeks_cyc <- circsum(c(4L, 5L, 4L), month_size)

    # Find which n-month period we're in based on the "at" position (months since epoch)
    period <- at %% length(nweeks_cyc) + 1L

    nweeks <- nweeks_cyc[period]

    # Add the extra week to December for Symmetry454 leap years.
    # A year is a leap year when (52*year + 146) %% 293 < 52.
    m1 <- at * month_size
    contains_dec <- which((m1 %% 12L) >= (12L - month_size))
    year <- 1970L + m1[contains_dec] %/% 12L
    is_leap_year <- ((52 * year + 146L) %% 293L) < 52L

    nweeks[contains_dec[is_leap_year]] <- nweeks[contains_dec[is_leap_year]] + 1L

    # Scale by the number of weeks in the n-week time granule
    nweeks / x@n
  }

# The 4-5-4 cycle across a full Symmetry454 year
chronon_cardinality(cal_symmetry454$week(1L), cal_symmetry454$month(1L), at = 0:11)

# 1970 is a leap year (53 weeks), so December has 5 weeks instead of 4
chronon_cardinality(cal_symmetry454$week(1L), cal_symmetry454$month(1L), at = 11L)

# Non-leap year: December has the usual 4 weeks
chronon_cardinality(cal_symmetry454$week(1L), cal_symmetry454$month(1L), at = 12:23) # Dec 1971

# The number of weeks in a multi-month period is the sum of the weeks in each month
chronon_cardinality(cal_symmetry454$week(1L), cal_symmetry454$month(2L), at = 0:5)

## -----------------------------------------------------------------------------
# The number of weeks in a Symmetry454 year (derived from week → month → year)
# Note that the leap year in 1970 (at = 0) produces 53 weeks via week → month.
chronon_cardinality(cal_symmetry454$week(1L), cal_symmetry454$year(1L), at = 0:4)

# The number of days in a Symmetry454 year (derived from day → week → month → year)
chronon_cardinality(cal_symmetry454$day(1L), cal_symmetry454$year(1L), at = 0:4)

## -----------------------------------------------------------------------------
# The number of Symmetry454 years in a month is the inverse of months → years (1/12)
chronon_cardinality(cal_symmetry454$year(1L), cal_symmetry454$month(1L))

# The number of Symmetry454 months in 2 weeks (requires `at` since weeks → months is irregular)
chronon_cardinality(cal_symmetry454$month(1L), cal_symmetry454$week(2L), at = 0:4)

## -----------------------------------------------------------------------------
chronon_divmod(cal_gregorian$day(1L), cal_gregorian$month(1L), 45L)

## ----divmod-------------------------------------------------------------------
method(chronon_divmod, list(cal_symmetry454$week, cal_symmetry454$month)) <-
  function(from, to, x) {
    # Most of this code works on 1-week granules
    week_size <- from@n
    x <- x * week_size  # convert n-weeks to 1-weeks

    # 1. Account for leap weeks by regularising x to have a fixed 52 weeks per year

    # The symmetrical sub-cycles of the 293-year leap week cycle are:
    #   17+11+17 + 17+17+11+17+17 + 17+11+17 + 17+17+11+17+17 + 17+11+17
    #   = 45 + 79 + 45 + 79 + 45 = 293
    # Primary (length 17) sub-cycles have 3 leap years: 00100000100000100
    # Secondary (length 11) sub-cycles have 2 leap years: 00100000100
    leaps_cycle_17 <- function(w) (w >= 157L) + (w >= 470L) + (w >= 783L)
    leaps_cycle_11 <- function(w) (w >= 157L) + (w >= 470L)
    leaps_cycle_45 <- function(w) {
      ifelse(w < 887L,  leaps_cycle_17(w),
      ifelse(w < 1461L, 3L + leaps_cycle_11(w - 887L),
                        5L + leaps_cycle_17(w - 1461L)))
    }
    leaps_cycle_79 <- function(w) {
      ifelse(w < 887L,  leaps_cycle_17(w),
      ifelse(w < 1774L, 3L  + leaps_cycle_17(w - 887L),
      ifelse(w < 2348L, 6L  + leaps_cycle_11(w - 1774L),
      ifelse(w < 3235L, 8L  + leaps_cycle_17(w - 2348L),
                        11L + leaps_cycle_17(w - 3235L)))))
    }
    leaps_symmetry454 <- function(x) {
      # There are 15288 weeks in a full 293-year cycle (293*52 + 52 leap weeks)
      w <- x %% 15288L
      x %/% 15288L * 52L +
      ifelse(w < 2348L,  leaps_cycle_45(w),
      ifelse(w < 6470L,  8L  + leaps_cycle_79(w - 2348L),
      ifelse(w < 8818L,  22L + leaps_cycle_45(w - 6470L),
      ifelse(w < 12940L, 30L + leaps_cycle_79(w - 8818L),
                         44L + leaps_cycle_45(w - 12940L)))))
    }

    # Offset x to align with the nearest 293-year cycle boundary before the epoch.
    # There are 349 leap years between year 1-W1 and the 1970-W1 epoch, and the
    # nearest cycle start is (1969*52 + 349) %% (293*52 + 52) = 11009 weeks before epoch.
    x_cyc <- x + 11009L + week_size  # right align multi-week granules
    n_leaps <- leaps_symmetry454(x_cyc)

    # Regularise x to have exactly 52 weeks per year by subtracting leap weeks.
    # (37 leap years occur in the cycle before the epoch, so we add 37 back.)
    x_reg <- x - n_leaps + 37L

    # 2. Use the 4-5-4 pattern to find the month (div) and week remainder (mod)
    ## The number of weeks in each n-month period
    month_size <- to@n
    weeks_len <- circsum(c(4L, 5L, 4L), month_size)

    ## The total weeks in a full n-month cycle
    weeks_tot <- sum(weeks_len)

    ## Find which n-month cycle we're in based on the regularised week count
    period_full <- x_reg %/% weeks_tot

    ## Find which part within the n-month cycle we're in
    weeks_seq  <- cumsum(weeks_len[-length(weeks_len)])
    period_part <- rowSums(outer(x_reg %% weeks_tot, weeks_seq, ">="))

    # div: total complete n-month cycles + complete n-months within the current cycle
    div <- period_full * length(weeks_len) + period_part
    # mod: remaining (regularised) weeks within the current n-month period
    mod <- x_reg %% weeks_tot - c(0L, weeks_seq)[period_part + 1L]


    # 3. Adjust the remainder to account for leap weeks that were removed during regularisation.

    # Identify leap weeks re-using cumulative in-cycle leap week counts: leaps_symmetry454()
    # If the week added a leap week from the previous, then it itself must be a leap week.
    # Applied only to regularised 52/53rd weeks of the year (only these weeks can be leap weeks)
    last_weeks  <- which(x_reg %% 52L >= 51L)
    leap_weeks  <- last_weeks[n_leaps[last_weeks] - leaps_symmetry454(x_cyc[last_weeks] - week_size) > 0L]
    mod[leap_weeks] <- mod[leap_weeks] + 1L  # restore leap week to remainder

    # Scale mod back to the original n-unit week size
    mod <- mod %/% week_size

    # Return the divmod result
    list(div = div, mod = mod)
  }

## -----------------------------------------------------------------------------
# Week 19 (0-indexed) of 1970 is the 2nd week (div=1) of May 1970 (mod=4)
with(cal_symmetry454, chronon_divmod(week(1L), month(1L), 18L))

# Week 52 (0-indexed) is the leap week of 1970; it is the 5th week (div=4) of Dec 1970 (mod=11)
with(cal_symmetry454, chronon_divmod(week(1L), month(1L), 52L))

## -----------------------------------------------------------------------------
# The 5th fortnight of 1970 is the 3rd fortnight (div=2) of Feb 1970 (mod=1)
with(cal_symmetry454, chronon_divmod(week(2L), month(1L), 4L))

## -----------------------------------------------------------------------------
# Divmod for converting days → years is derived from days → weeks → months → years
# Gregorian day 839 since unix epoch (1972-04-19) is symmetry454 day 101 (mod=100) of year 1972 (div=2)
with(cal_symmetry454, chronon_divmod(day(1L), year(1L), 839L))

## -----------------------------------------------------------------------------
method(chronon_epoch, cal_symmetry454$year) <- function(x) 1970L

## -----------------------------------------------------------------------------
method(time_unit_full, cal_symmetry454$year)  <- function(x) "Symmetry454 year{?/s}"
method(time_unit_abbr, cal_symmetry454$year)  <- function(x) "Y"
method(time_unit_full, cal_symmetry454$month) <- function(x) "Symmetry454 month{?/s}"
method(time_unit_abbr, cal_symmetry454$month) <- function(x) "M"

## -----------------------------------------------------------------------------
linear_time(as.Date("1955-11-12"), chronon = cal_symmetry454$year(1L))

## -----------------------------------------------------------------------------
year(as.Date("1955-11-12"), calendar = cal_symmetry454)

## -----------------------------------------------------------------------------
# Week of the month
cyclical_time(as.Date("1955-11-12"), chronon = week(1L), cycle = month(1L), calendar = cal_symmetry454)

# Month of the year
month_of_year(as.Date("1955-11-12"), calendar = cal_symmetry454)

## -----------------------------------------------------------------------------
method(linear_labels_format, cal_symmetry454$year) <- function(granule, i, ...) {
  ifelse(i <= 0L, paste0(-i + 1L, "BC"), i)
}
method(linear_labels_parse, cal_symmetry454$year) <- function(granule, ...) {
  list(
    pattern = "\\d+(?:BC)?",
    decode = function(text, at = NULL) {
      bc <- grepl("BC$", text)
      n <- as.integer(sub("BC$", "", text))
      ifelse(bc, 1L - n, n) - chronon_epoch(granule)
    }
  )
}

## -----------------------------------------------------------------------------
year(-1:2, calendar = cal_symmetry454)

## -----------------------------------------------------------------------------
# Labels for months of the year, essentially the same as Gregorian months in years.
method(cyclical_labels, list(cal_symmetry454$month, cal_symmetry454$year)) <-
  label_scheme(
    start = 1L,
    width = 2L,
    vocab = vocab_table(
      `en-GB` = list(wide = month.name, abbreviated = month.abb)
    )
  )

# Labels for weeks of the month are simply 1-indexed (e.g. "1", "2", ...)
method(cyclical_labels, list(cal_symmetry454$week, cal_symmetry454$month)) <-
  label_scheme(start = 1L)

## -----------------------------------------------------------------------------
# Month of year
month_of_year(as.Date("1955-11-12"), calendar = cal_symmetry454)
# Week of month
cyclical_time(as.Date("1955-11-12"), chronon = cal_symmetry454$week(1L), cycle = cal_symmetry454$month(1L))

# Day of week (inherited from cal_isoweek)
day_of_week(as.Date("1955-11-12"), calendar = cal_symmetry454)
# Day of year (inherited from cal_time_civil)
day_of_year(as.Date("1955-11-12"), calendar = cal_symmetry454)

## -----------------------------------------------------------------------------
date(as.Date("1955-11-12"), calendar = cal_symmetry454)
yearweek(as.Date("1955-11-12"), calendar = cal_symmetry454)
yearmonth(as.Date("1955-11-12"), calendar = cal_symmetry454)

## -----------------------------------------------------------------------------
# Simply display years as numbers (e.g. "1970", "1971", ...)
method(
  chronon_format_linear,
  list(cal_symmetry454$year, S7::class_any)
) <- function(x, cal) "{lin(year(1L))}"

# Display labelled months as month within year (e.g. "1970 Jan", "1970 Feb", ...)
method(
  chronon_format_linear,
  list(cal_symmetry454$month, S7::class_any)
) <- function(x, cal) "{lin(year(1L))}-{cyc(month(1L), year(1L), label=TRUE, abbreviate=TRUE)}"

# Display weeks as week in month in year (e.g. "1970-01-W1", "1970-01-W2", ...)
method(
  chronon_format_linear,
  list(cal_symmetry454$week, S7::new_S3_class("cal_symmetry454"))
) <- function(x, cal) "{lin(year(1L))}-{cyc(month(1L), year(1L), label=TRUE, abbreviate=TRUE)}-W{cyc(week(1L), month(1L))}"

# Format days as day in week in month in year (e.g. "1970-Jan-W1-Mon", "1970-Jan-W1-Tue", ...)
method(
  chronon_format_linear,
  list(cal_symmetry454$day, S7::new_S3_class("cal_symmetry454"))
) <- function(x, cal) "{lin(year(1L))}-{cyc(month(1L), year(1L), label=TRUE, abbreviate=TRUE)}-W{cyc(week(1L), month(1L))}-{cyc(day(1L), week(1L), label=TRUE, abbreviate=TRUE)}"

## -----------------------------------------------------------------------------
# Years are formatted as YYYY
year(as.Date("1955-11-12"), calendar = cal_symmetry454)

# Months are formatted as YYYY Mon
yearmonth(as.Date("1955-11-12"), calendar = cal_symmetry454)

# Weeks are formatted as YYYY-MM-WW
yearweek(as.Date("1955-11-12"), calendar = cal_symmetry454)

# Days are formatted as YYYY-MM-WW-DD
linear_time(as.Date(c("1985-10-26", "1955-11-05", "1955-11-12")), chronon = cal_symmetry454$day(1L))

## -----------------------------------------------------------------------------
# Format months in years as abbreviated month labels (e.g. "Jan", "Feb", ...)
method(
  chronon_format_cyclical,
  list(cal_symmetry454$month, cal_symmetry454$year)
) <- function(x, y) "{cyc(month,year,label=TRUE,abbreviate=TRUE)}"

## -----------------------------------------------------------------------------
month_of_year(as.Date("1955-11-12"), calendar = cal_symmetry454)

## -----------------------------------------------------------------------------
duration(1L, cal_symmetry454$year(1L))
duration(3L, cal_symmetry454$month(1L))

## -----------------------------------------------------------------------------
method(chronon_parse_linear, list(cal_symmetry454$month, S7::class_any)) <- function(x, cal) {
  parse_format(
    "{lin(year(1L))}-{cyc(month(1L), year(1L), label=TRUE, abbreviate=TRUE)}",
    "{lin(year(1L))}-{cyc(month(1L), year(1L))}"
  )
}

## -----------------------------------------------------------------------------
time_parse(c("1970-Jan", "1970-03"), format = chronon_parse_linear(cal_symmetry454$month(1L)))

