-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy paths3-era.R
More file actions
394 lines (354 loc) · 10.7 KB
/
Copy paths3-era.R
File metadata and controls
394 lines (354 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
# era.R
# S3 record class `era`: era definitions
# Register formal class for S4 compatibility
# https://vctrs.r-lib.org/articles/s3-vector.html#implementing-a-vctrs-s3-class-in-a-package-1
#' @importFrom methods setOldClass
methods::setOldClass(c("era", "vctrs_rcrd"))
# Constructors ------------------------------------------------------------
#' Create an era object
#'
#' An `era` object defines the time scale associated with a vector of years
#' (see [yr()]). `era()` returns an `era` object, either by looking up
#' `label` in the standard eras defined in [eras()] or, if more than one
#' argument is given, constructing a new definition with the specified
#' parameters.
#'
#' @param label Character. If only one argument is given to `era()`, the
#' abbreviated label of a standard era defined in [eras()].
#' Otherwise, the label to give to the era constructed using the following
#' arguments.
#' @param epoch Numeric. Epoch year from which years are counted in Gregorian
#' astronomical years (i.e. there is a "year zero").
#' @param name Character. Full name of the era. Defaults to the value of
#' `label`.
#' @param unit An [era_year()] object describing the name of the year unit and
#' its average length in solar days. Defaults to a Gregorian year
#' (365.2425 days).
#' @param scale Integer. Number of years represented by one unit, e.g. `1000`
#' for ka. Default: 1.
#' @param direction Are years counted backwards (`-1`) (the default) or forwards (`1`)
#' from `epoch`?
#'
#' @return
#' An object of class `era`.
#'
#' @family era definition functions
#' @family era helper functions
#'
#' @export
#'
#' @examples
#' era("cal BP")
#'
#' era("T.A.", epoch = -9021, name = "Third Age", direction = 1)
era <- function(label = character(),
epoch = NULL,
name = label,
unit = era_year("Gregorian"),
scale = 1,
direction = -1) {
if (missing(epoch) &&
missing(name) &&
missing(unit) &&
missing(scale) &&
missing(direction)) {
if (vec_is_empty(label)) {
return(new_era())
}
if (any(is.na(label))) {
abort(
"`label` must not contain NAs.",
class = "era_invalid_era",
body = format_error_bullets(c(
i = "Use eras() to get a list of known era labels."
))
)
}
if (is_era(label)) {
parameters <- as.list(vec_data(label))
}
else {
label <- vec_cast(label, character())
parameters <- eras(label)
if (any(apply(is.na(parameters), 1, all))) {
unknown_label <- label[apply(is.na(parameters), 1, all)]
unknown_label <- paste0("\"", unknown_label, "\"")
names(unknown_label) <- rep("x", length(unknown_label))
abort(
"Some values of `label` do not match an era defined in eras():",
class = "era_invalid_era",
body = format_error_bullets(c(
unknown_label,
i = "Use eras() to get a list of known era labels."
))
)
}
parameters <- as.list(parameters)
}
}
else {
# Backwards compatibility with era v.<=0.2.0
if (any(direction %in% c("backwards", "forwards"))) {
direction[direction == "backwards"] <- -1
direction[direction == "forwards"] <- 1
direction <- as.integer(direction)
}
# Use data.frame() to get vector recycling
parameters <- data.frame(
label = vec_cast(label, character()),
epoch = vec_cast(epoch, numeric()),
name = vec_cast(name, character()),
unit = vec_assert(unit, new_era_year()),
scale = vec_cast(scale, integer()),
direction = vec_cast(direction, integer()),
stringsAsFactors = FALSE
)
parameters <- as.list(parameters)
}
era <- do.call(new_era, parameters)
validate_era(era)
return(era)
}
new_era <- function(label = character(),
epoch = numeric(),
name = character(),
unit = era_year(),
scale = integer(),
direction = integer()) {
new_rcrd(
list(label = label,
epoch = epoch,
name = name,
unit = unit,
scale = scale,
direction = direction),
class = "era"
)
}
#' Standard era definitions
#'
#' @description
#' Definitions of common eras and time scales.
#'
#' `eras()` lists all available era definitions. `eras(label)` looks up a
#' specific era by its unique, abbreviated name (e.g. "cal BP").
#'
#' @param label (Optional) Abbreviated names(s) of eras to look up.
#'
#' @details
#' Looking up eras by `label` uses partial matching.
#'
#' @return
#' A table of era definitions. This can be passed to [era()] to construct an
#' `era` object.
#'
#' @family era definition functions
#'
#' @export
#'
#' @examples
#' # List all available eras
#' eras()
#'
#' # Look up a specific era by label
#' eras("cal BP")
#'
#' # With partial matching
#' eras("cal")
eras <- function(label = NA) {
# era_table is an internal dataset generated in data-raw/era_table.R
era_table <- era_table
# Partial matching
if (!all(is.na(label))) {
era_table[pmatch(label, era_table[["label"]]),]
}
else {
era_table
}
}
# Validators --------------------------------------------------------------
#' Validation functions for `era` objects
#'
#' @description
#' Tests whether an object is an era definition (an `era` object).
#' `is_era()` tests whether the object inherits from the S3 class `era_yr`.
#' `is_valid_era()` performs additional checks to determine whether the object
#' is well-formed (see details).
#' `validate_era()` throws an informative error message for invalid `yr`s.
#'
#' @param x Object to test.
#'
#' @details
#' Valid `era` objects:
#'
#' * Must have all parameters set and not NA
#' * Must have a character `label` parameter
#' * Must have a numeric `epoch` parameter
#' * Must have a character `name` parameter
#' * Must have a character `unit` parameter that is one of the defined units
#' * Must have a positive, integer `scale` parameter
#' * Must have a direction parameter that is -1 (backwards) or 1 (forwards)
#'
#' @return
#' `is_era()` and `is_valid_era()` return `TRUE` or `FALSE`.
#' `validate_era()` returns `x` invisibly, and is used for its side-effect of
#' throwing an informative error for invalid objects.
#'
#' @family era helper functions
#'
#' @export
is_era <- function(x) {
inherits(x, "era")
}
#' @rdname is_era
#' @export
validate_era <- function(x) {
if (!is_era(x)) {
abort("Invalid era:",
class = "era_invalid_era",
body = format_error_bullets(c(
x = "Must inherit from class era.",
i = "See ?era for methods for defining eras."
)))
}
problems <- era_problems(x)
if (any(problems)) {
problems <- names(problems[problems])
names(problems) <- rep("x", length(problems))
abort("Invalid era:",
class = "era_invalid_era",
body = format_error_bullets(problems))
}
return(invisible(x))
}
#' @rdname is_era
#' @export
is_valid_era <- function(x) {
if (!is_era(x)) {
return(FALSE)
}
problems <- era_problems(x)
!any(problems)
}
#' List problems with an era object
#'
#' Internal function for testing validity of era objects.
#'
#' @param x Object to test
#'
#' @return
#' A named logical vector. The names describe invalid conditions (formatted as
#' error messages that can be passed to vctrs::abort()) and `TRUE` indicates
#' an invalid state.
#'
#' @noRd
#' @keywords internal
era_problems <- function(x) {
!c(
# TODO: Do we need this? Breaks now unit is a rcrd, and maybe some fields
# could be NA?
# "era parameters must not be NA" =
# apply(vec_proxy(x), 1, function(x) !any(is.na(x))),
"`label` must be a character" =
vec_is(era_label(x), character()),
"`epoch` must be a numeric" =
vec_is(era_epoch(x), numeric()),
"`name` must be a character" =
vec_is(era_name(x), character()),
"`unit` must be an `era_year` object" =
is_era_year(era_unit(x)),
"`scale` must be an integer" =
vec_is(era_scale(x), integer()),
"`scale` must be positive" =
all(era_scale(x) > 0) && !any(is.na(era_scale(x))),
"`direction` must be -1 (backwards) or 1 (forwards)" =
all(era_direction(x) %in% c(-1, 1))
)
}
# Format/print --------------------------------------------------------------
#' @export
format.era <- function(x, ...) {
nameout <- paste0(era_name(x), " (", era_label(x), ")")
nameout[era_name(x) == era_label(x)] <- era_name(x)[era_name(x) == era_label(x)]
scaleout <- paste0(era_scale(x), " ")
scaleout[era_scale(x) == 1] <- ""
dirout <- c("backwards", "forwards")[(era_direction(x) > 0) + 1]
out <- paste0(nameout, ": ", scaleout, format(era_unit(x)),
", counted ", dirout, " from ", era_epoch(x))
return(out)
}
#' @importFrom pillar pillar_shaft
#' @export
pillar_shaft.era <- function(x, ...) {
out <- format(era_label(x), justify = "right")
pillar::new_pillar_shaft_simple(out, align = "right")
}
# Casting/coercion --------------------------------------------------------
#' @export
vec_ptype2.era.era <- function(x, y, ...) new_era()
#' @export
vec_cast.era.era <- function(x, to, ...) x
# Equality/comparison -------------------------------------------------
#' @method vec_proxy_equal era
#' @export
vec_proxy_equal.era <- function(x, ...) {
vec_data(x)[!names(vec_data(x)) %in% c("label", "name")]
}
# Get/set attributes -----------------------------------------------------
#' Get parameters of an era
#'
#' Extracts a specific parameter from an era object.
#'
#' @name era_parameters
#'
#' @param x An `era` object.
#'
#' @details
#' The available parameters are:
#'
#' * **label** – unique, abbreviated label of the era, e.g. "cal BP"
#' * **epoch** – year of origin of the era, e.g. 1950 for Before Present
#' * **name** – full name of the era, e.g. "calendar years Before Present"
#' * **unit** – unit of years used, an [era_year()] object
#' * **scale** – multiple of years used, e.g. 1000 for ka/kiloannum
#' * **direction** – whether years are counted "backwards" or "forwards" from the epoch
#' #'
#' @return
#' Value of the parameter.
#'
#' @family era helper functions
#'
#' @examples
#' x <- era("cal BP")
#' era_name(x)
NULL
#' @rdname era_parameters
#' @export
era_label <- function(x) {
return(field(x, "label"))
}
#' @rdname era_parameters
#' @export
era_epoch <- function(x) {
return(field(x, "epoch"))
}
#' @rdname era_parameters
#' @export
era_name <- function(x) {
return(field(x, "name"))
}
#' @rdname era_parameters
#' @export
era_unit <- function(x) {
return(field(x, "unit"))
}
#' @rdname era_parameters
#' @export
era_scale <- function(x) {
return(field(x, "scale"))
}
#' @rdname era_parameters
#' @export
era_direction <- function(x) {
return(field(x, "direction"))
}