diff --git a/.Rbuildignore b/.Rbuildignore
index 2a59cd98..7c2ab61d 100644
--- a/.Rbuildignore
+++ b/.Rbuildignore
@@ -14,3 +14,4 @@
^CRAN-SUBMISSION$
^article$
^.DS_Store$
+^next$
diff --git a/.gitignore b/.gitignore
index 31b7a9dc..30e81a4f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,5 @@ article/jsslogo.jpg
article/references.bib
article/article_files
vignettes-old
+next
diff --git a/DESCRIPTION b/DESCRIPTION
index 82a65a96..7450204b 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -9,8 +9,8 @@ Description: Contains a set of tools for constructing and coercing
date ranges, and sets of dates.
This is useful for describing and analysing temporal information,
whether historical or recent, where date precision may vary.
-Version: 0.5.1
-Date: 2025-02-25
+Version: 0.5.2
+Date: 2025-03-07
Authors@R:
c(person(given = "James",
family = "Hollway",
diff --git a/NAMESPACE b/NAMESPACE
index e3d1bdf2..0a168c08 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -22,8 +22,8 @@ S3method(as.Date,mdate)
S3method(as.POSIXct,mdate)
S3method(as.POSIXlt,mdate)
S3method(as.data.frame,mdate)
+S3method(as.double,mdate)
S3method(as.list,mdate)
-S3method(as.numeric,mdate)
S3method(as_messydate,Date)
S3method(as_messydate,POSIXct)
S3method(as_messydate,POSIXlt)
@@ -39,13 +39,18 @@ S3method(messyduration,character)
S3method(messyduration,mdate)
S3method(min,mdate)
S3method(modal,mdate)
-S3method(mreport,default)
S3method(print,mdate)
S3method(print,mdates_duration)
-S3method(print,mreport)
+S3method(random,character)
S3method(random,mdate)
S3method(rep,mdate)
S3method(seq,mdate)
+S3method(vmax,mdate)
+S3method(vmean,mdate)
+S3method(vmedian,mdate)
+S3method(vmin,mdate)
+S3method(vmodal,mdate)
+S3method(vrandom,mdate)
export("%><%")
export("%>=<%")
export("%g%")
@@ -77,7 +82,6 @@ export(mdate)
export(messyduration)
export(modal)
export(month)
-export(mreport)
export(new_messydate)
export(new_messyduration)
export(on_or_after)
@@ -86,8 +90,13 @@ export(precision)
export(random)
export(validate_messydate)
export(validate_messyduration)
+export(vmax)
+export(vmean)
+export(vmedian)
+export(vmin)
+export(vmodal)
+export(vrandom)
export(year)
-importFrom(dplyr,"%>%")
importFrom(dplyr,first)
importFrom(dplyr,last)
importFrom(dplyr,lead)
@@ -99,7 +108,6 @@ importFrom(lubridate,ymd)
importFrom(purrr,map)
importFrom(purrr,pmap_chr)
importFrom(stats,median)
-importFrom(stats,na.omit)
importFrom(stringi,stri_detect_regex)
importFrom(stringi,stri_extract_all_regex)
importFrom(stringi,stri_replace_all_fixed)
diff --git a/NEWS.md b/NEWS.md
index f44fb831..5c53f559 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,3 +1,30 @@
+# messydates 0.5.2
+
+## Package
+
+- Moved `mreport()` to `{manydata}`
+- Consolidated and renamed scripts internally
+
+## Coerce to
+
+- Fixed pkgdown#2855 by fixing how as_messydate methods interpret infinite dates
+- Fixed time zone defaults in `as.POSIXct.mdate()` and `as.POSIXlt.mdate()`
+- Fixed set bug in `validate_messydate()`
+
+## Coerce from
+
+- Renamed `as.numeric()` to `as.double()` to fix S3 dispatching
+- Separated extrema functions into `min.mdate()` and `max.mdate()` for summaries
+and `vmin.mdate()` and `vmax.mdate()` for vector coercion
+- Separated tendency functions into `mean.mdate()`, `median.mdate()`, and `modal.mdate()` for summaries
+and `vmean.mdate()`, `vmedian.mdate()`, and `vmodal.mdate()` for vector coercion
+- Vector coercion previously in `random.mdate()` now in `vrandom.mdate()`
+- Improved how coercion/resolution functions handle BCE dates
+
+## Manipulation
+
+- Fixed how `precision()` calculates precision
+
# messydates 0.5.1
## Package
diff --git a/R/class.R b/R/class.R
deleted file mode 100644
index e3b16c6c..00000000
--- a/R/class.R
+++ /dev/null
@@ -1,213 +0,0 @@
-#' A flexible date class for messy dates
-#'
-#' @description
-#' Recent extensions to standardised date notation in
-#' [ISO 8601-2_2019(E)](https://www.iso.org/standard/70908.html)
-#' create space for unspecified, uncertain, and approximate dates,
-#' as well as succinct representation of ranges of dates.
-#' These functions create and validate a new date class for R
-#' that can contain and parse these annotations,
-#' and are not typically user-facing.
-#' Please see `as_messydate()` for the user-facing coercion function.
-#'
-#' @details
-#' ## Date annotations
-#'
-#' _Unspecified date components_, such as when the day is unknown,
-#' can be represented by one or more `X`s in place of the digits.
-#' The modifier `*` is recommended to indicate that the entire
-#' time scale component value is unspecified, e.g. `X*-03-03`,
-#' however this is not implemented here.
-#' Please be explicit about the digits that are unspecified,
-#' e.g. `XXXX-03-03` expresses 3rd March in some unspecified year,
-#' whereas `2003-XX-03` expresses the 3rd of some month in 2003.
-#' If time components are not given, they are expanded to this.
-#'
-#' _Approximate date components_, modified by `~`,
-#' represent an estimate whose value is asserted
-#' to be possibly correct.
-#' For example, `2003~-03-03`
-#' The degree of confidence in approximation
-#' depends on the application.
-#'
-#' _Uncertain date components_, modified by `?`,
-#' represent a date component whose source is considered
-#' to be dubious and therefore not to be relied upon.
-#' An additional modifier, `%`, is used to indicate
-#' a value that is both uncertain and approximate.
-#'
-#' ## Date sets
-#'
-#' These functions also introduce standard notation
-#' for ranges of dates.
-#' Rather than the typical R notation for ranges,
-#' `:`, ISO 8601-2_2019(E) recommends `..`.
-#' This then can be applied between two time scale
-#' components to create a standard range between
-#' these dates (inclusive), e.g. `2009-01-01..2019-01-01`.
-#' But it can also be used as an affix,
-#' indicating "on or before" if used as a prefix,
-#' e.g. `..2019-01-01`,
-#' or indicating "on or after" if used as a suffix,
-#' e.g. `2009-01-01..`.
-#'
-#' And lastly, notation for sets of dates is also included.
-#' Here braces, `{}`, are used to mean "all members of the set",
-#' while brackets, `[]`, are used to mean "one member of the set".
-#' @param x A character scalar or vector in the expected `"yyyy-mm-dd"` format
-#' annotated, as necessary, according to ISO 8601-2_2019(E).
-#' @return Object of class `mdate`
-#' @name class
-#' @seealso messydate
-NULL
-
-#' @rdname class
-#' @export
-new_messydate <- function(x = character()) {
- stopifnot(is.character(x))
- structure(x, class = "mdate")
-}
-
-#' @rdname class
-#' @export
-validate_messydate <- function(x) {
- values <- unclass(x)
- if (any(grepl("[A-WYZa-z]", values) & !grepl("^NA$", values))) {
- stop("The only alpha character allowed in messy dates is 'X' for
- unspecified time components", call. = FALSE)
- }
- if (!any(grepl("[0-9]", values))) {
- stop("mdate object requires at least one specified date component.",
- call. = FALSE)
- }
- if (any(grepl("!|\\(|\\)|\\+|\\=|\\/|,|;|>|<|_|\\^|'|&|\\$|#", values))) {
- stop("mdate object can only consist of numbers and
- some special symbols: []{}..X%?~", call. = FALSE)
- }
- x
-}
-
-#' @importFrom utils str
-#' @export
-print.mdate <- function(x, ...) {
- str(x)
-}
-
-#' @export
-`[.mdate` <- function(x, ..., drop = TRUE) {
- as_messydate(NextMethod("[", unclass(x)))
-}
-
-#' @export
-`[<-.mdate` <- function(x, i, ..., value) {
- value <- as_messydate(value)
- validate_messydate(value)
- as_messydate(NextMethod("[<-", unclass(x)))
-}
-
-#' @export
-`[[.mdate` <- function(x, ...) {
- as_messydate(NextMethod("[[", unclass(x)))
-}
-
-#' @export
-`[[<-.mdate` <- function(x, i, ..., value) {
- value <- as_messydate(value)
- validate_messydate(value)
- as_messydate(NextMethod("[[<-", unclass(x)))
-}
-
-#' @export
-c.mdate <- function(...) {
- if(length(list(...)) == 1){
- unclass(list(...)[[1]])
- } else {
- vecs <- lapply(list(...), function(e) unclass(as_messydate(e)))
- x <- as_messydate(unlist(vecs))
- validate_messydate(x)
- }
-}
-
-#' @export
-as.data.frame.mdate <- function(x, ...) {
- as.data.frame.vector(x, ...)
-}
-
-#' @export
-rep.mdate <- function(x, ...) {
- as_messydate(NextMethod("rep", unclass(x)))
-}
-
-#' @export
-as.list.mdate <- function(x, ...) {
- lapply(unclass(x), as_messydate)
-}
-
-#' @export
-as.numeric.mdate <- function(x, ...) {
- as.numeric(as.Date(x))
-}
-
-#' Sequence method for messydates
-#' @description
-#' This function provides a sequence (`seq()`) method for messydates.
-#' This can be used with ranges or unspecified dates,
-#' and is particularly useful for defining a sequence of dates
-#' before the common era or between eras.
-#' @name messy-sequence
-#' @param from A messydate or range.
-#' If 'from' is a range and 'to' is not specified,
-#' 'from' will be the minimum of the range and 'to' will be maximum.
-#' @param to A messydate.
-#' @param by Increment of the sequence. By default "days".
-#' @param ... Arguments passed to or from methods.
-#' @examples
-#' seq(mdate("-0001-12-20"), mdate("0001-01-10"))
-#' @export
-seq.mdate <- function(from, to, by = "days", ...) {
-
- if(missing(to) & !is_precise(from)){
- to <- max(from)
- from <- min(from)
- }
-
- # straight forward sequence
- if(!any(is_bce(c(from, to)))){
- seq(as.Date(from), as.Date(to), by = by)
- } else {
-
- fromp <- as.Date(stringi::stri_replace_first_regex(from, "^-", ""))
- # sequence before common era
- if(is_bce(to)){
- top <- as.Date(stringi::stri_replace_first_regex(to, "^-", ""))
- .neg_seqs(fromp, top, by = by)
- } else {
- # sequence between eras
- zero_padding(c(.neg_seqs(fromp, as.Date("0001-12-31"), by = by),
- as.character(seq(as.Date("0001-01-01"), as.Date(to), by = by))))
- # zero_padding(c(rev(paste0("-", seq(as.Date("0001-01-01"), fromp, by = by))),
- # as.character(seq(as.Date("0001-01-01"), as.Date(to), by = by))))
- }
- }
-}
-
-.neg_seqs <- function(fromp, top, by = "days"){
- if(year(fromp) == year(top)){
- zero_padding(paste0("-", seq(min(c(fromp, top)),
- max(c(fromp,top)), by = by)))
- } else {
- strt <- max(c(fromp, top))
- ends <- min(c(fromp, top))
- strt_yr <- year(strt)
- strt_sq <- seq(as.Date(strt), as.Date(paste0(strt_yr,"-12-31")), by = by)
- ends_yr <- year(ends)
- ends_sq <- seq(as.Date(paste0(ends_yr, "-01-01")), as.Date(ends), by = by)
- if(strt_yr - ends_yr > 1){
- mids_sq <- seq(as.Date(paste0(ends_yr+1, "-01-01")),
- as.Date(paste0(strt_yr-1,"-12-31")), by = by)
- if(length(unique(year(mids_sq)))>1)
- mids_sq <- mids_sq[order(year(mids_sq), decreasing = TRUE)]
- zero_padding(paste0("-", c(strt_sq, mids_sq, ends_sq)))
- } else zero_padding(paste0("-", c(strt_sq, ends_sq)))
- }
-}
diff --git a/R/duration.R b/R/class_duration.R
similarity index 89%
rename from R/duration.R
rename to R/class_duration.R
index 06d4f561..37edc334 100644
--- a/R/duration.R
+++ b/R/class_duration.R
@@ -12,29 +12,23 @@
#' @param approx_range Range to expand approximate dates, in days.
#' If 3, for example, adds 3 days; if -3, removes 3 days from both sides.
#' @return Object of class `description`
-#' @name duration_class
+#' @name class_duration
#' @examples
#' messyduration(as_messydate(c("2010-01-01..2010-12-31", "2010-01..2010-12")))
NULL
-#' @rdname duration_class
+#' @rdname class_duration
#' @export
new_messyduration <- function(x = character()) {
stopifnot(is.character(x))
structure(x, class = "mdates_duration")
}
-#' @importFrom utils str
-#' @export
-print.mdates_duration <- function(x, ...) {
- str(x)
-}
-
-#' @rdname duration_class
+#' @rdname class_duration
#' @export
messyduration <- function(x, approx_range = 0) UseMethod("messyduration")
-#' @rdname duration_class
+#' @rdname class_duration
#' @export
validate_messyduration <- function(x, approx_range = 0) {
if (any(!grepl("\\.\\.", x))) {
@@ -43,7 +37,7 @@ validate_messyduration <- function(x, approx_range = 0) {
}
}
-#' @rdname duration_class
+#' @rdname class_duration
#' @export
messyduration.character <- function(x, approx_range = 0) {
message("Converting to mdate class.")
@@ -53,7 +47,7 @@ messyduration.character <- function(x, approx_range = 0) {
new_messyduration(x)
}
-#' @rdname duration_class
+#' @rdname class_duration
#' @export
messyduration.mdate <- function(x, approx_range = 0) {
validate_messyduration(x)
diff --git a/R/class_mdate.R b/R/class_mdate.R
new file mode 100644
index 00000000..bb98b073
--- /dev/null
+++ b/R/class_mdate.R
@@ -0,0 +1,118 @@
+#' A flexible date class for messy dates
+#' @description
+#' Recent extensions to standardised date notation in
+#' [ISO 8601-2_2019(E)](https://www.iso.org/standard/70908.html)
+#' create space for unspecified, uncertain, and approximate dates,
+#' as well as succinct representation of ranges of dates.
+#' These functions create and validate a new date class for R
+#' that can contain and parse these annotations,
+#' and are not typically user-facing.
+#' Please see `as_messydate()` for the user-facing coercion function.
+#' @section Date annotations:
+#' _Unspecified date components_, such as when the day is unknown,
+#' can be represented by one or more `X`s in place of the digits.
+#' The modifier `*` is recommended to indicate that the entire
+#' time scale component value is unspecified, e.g. `X*-03-03`,
+#' however this is not implemented here.
+#' Please be explicit about the digits that are unspecified,
+#' e.g. `XXXX-03-03` expresses 3rd March in some unspecified year,
+#' whereas `2003-XX-03` expresses the 3rd of some month in 2003.
+#' If time components are not given, they are expanded to this.
+#'
+#' _Approximate date components_, modified by `~`,
+#' represent an estimate whose value is asserted
+#' to be possibly correct.
+#' For example, `2003~-03-03`
+#' The degree of confidence in approximation
+#' depends on the application.
+#'
+#' _Uncertain date components_, modified by `?`,
+#' represent a date component whose source is considered
+#' to be dubious and therefore not to be relied upon.
+#' An additional modifier, `%`, is used to indicate
+#' a value that is both uncertain and approximate.
+#'
+#' @section Date sets:
+#' These functions also introduce standard notation
+#' for ranges of dates.
+#' Rather than the typical R notation for ranges,
+#' `:`, ISO 8601-2_2019(E) recommends `..`.
+#' This then can be applied between two time scale
+#' components to create a standard range between
+#' these dates (inclusive), e.g. `2009-01-01..2019-01-01`.
+#' But it can also be used as an affix,
+#' indicating "on or before" if used as a prefix,
+#' e.g. `..2019-01-01`,
+#' or indicating "on or after" if used as a suffix,
+#' e.g. `2009-01-01..`.
+#'
+#' And lastly, notation for sets of dates is also included.
+#' Here braces, `{}`, are used to mean "all members of the set",
+#' while brackets, `[]`, are used to mean "one member of the set".
+#' @param x A character scalar or vector in the expected `"yyyy-mm-dd"` format
+#' annotated, as necessary, according to ISO 8601-2_2019(E).
+#' @return Object of class `mdate`
+#' @name class_create
+#' @seealso messydate
+NULL
+
+#' @rdname class_create
+#' @export
+new_messydate <- function(x = character()) {
+ stopifnot(is.character(x))
+ structure(x, class = "mdate")
+}
+
+#' @rdname class_create
+#' @export
+validate_messydate <- function(x) {
+ values <- unclass(x)
+ if (any(grepl("[A-WYZa-z]", values) & !grepl("^NA$", values))) {
+ stop("The only alpha character allowed in messy dates is 'X' for
+ unspecified time components", call. = FALSE)
+ }
+ if (!any(grepl("[0-9]", values))) {
+ stop("mdate object requires at least one specified date component.",
+ call. = FALSE)
+ }
+ if (any(grepl("!|\\(|\\)|\\+|\\=|\\/|;|>|<|_|\\^|'|&|\\$|#", values))) {
+ stop("mdate object can only consist of numbers and
+ some special symbols: []{}..X%?~", call. = FALSE)
+ }
+ x
+}
+
+# Make ####
+
+#' Composes `mdate` from multiple variables
+#' @param ... One (yyyy-mm-dd), two (yyyy-mm-dd, yyyy-mm-dd),
+#' or three (yyyy, mm, dd) variables.
+#' @inheritParams coerce_to
+#' @details If three date variables are passed to `make_messydate()`,
+#' function will create a single date (yyyy-mm-dd) from it.
+#' If two date variables are passed to `make_messydate()`,
+#' function will create a range of dates from it (yyyy-mm-dd..yyyy-mm-dd).
+#' If one date variable is passed to `make_messydate()`,
+#' function defaults to `as_messydate()`.
+#' @importFrom purrr map pmap_chr
+#' @name class_make
+#' @examples
+#' make_messydate("2010", "10", "10")
+#' @export
+make_messydate <- function(..., resequence = FALSE) {
+ dots <- list(...)
+ if (length(dots) == 1) {
+ dots <- do.call(as.character, dots)
+ dates <- unlist(dots)
+ } else if (length(dots) == 2) {
+ dots <- purrr::map(dots, as.character)
+ dates <- unlist(purrr::pmap_chr(dots, paste, sep = ".."))
+ dates <- gsub("NA..NA", "NA", dates)
+ } else if (length(dots) == 3) {
+ dots <- purrr::map(dots, as.character)
+ dates <- unlist(purrr::pmap_chr(dots, paste, sep = "-"))
+ dates <- gsub("NA-NA-NA", "NA", dates)
+ } else stop("make_messydate() takes one variable (yyyy-mm-dd),
+ two variables (yyyy-mm-dd, yyyy-mm-dd), or three variables (yyyy, mm, dd).")
+ as_messydate(dates, resequence)
+}
diff --git a/R/class_methods.R b/R/class_methods.R
new file mode 100644
index 00000000..35a2debe
--- /dev/null
+++ b/R/class_methods.R
@@ -0,0 +1,54 @@
+#' @export
+`[.mdate` <- function(x, ..., drop = TRUE) {
+ as_messydate(NextMethod("[", unclass(x)))
+}
+
+#' @export
+`[<-.mdate` <- function(x, i, ..., value) {
+ value <- as_messydate(value)
+ validate_messydate(value)
+ as_messydate(NextMethod("[<-", unclass(x)))
+}
+
+#' @export
+`[[.mdate` <- function(x, ...) {
+ as_messydate(NextMethod("[[", unclass(x)))
+}
+
+#' @export
+`[[<-.mdate` <- function(x, i, ..., value) {
+ value <- as_messydate(value)
+ validate_messydate(value)
+ as_messydate(NextMethod("[[<-", unclass(x)))
+}
+
+#' @export
+c.mdate <- function(...) {
+ if(length(list(...)) == 1){
+ unclass(list(...)[[1]])
+ } else {
+ vecs <- lapply(list(...), function(e) unclass(as_messydate(e)))
+ x <- as_messydate(unlist(vecs))
+ validate_messydate(x)
+ }
+}
+
+#' @export
+rep.mdate <- function(x, ...) {
+ as_messydate(NextMethod("rep", unclass(x)))
+}
+
+# Printing ####
+
+#' @importFrom utils str
+#' @export
+print.mdate <- function(x, ...) {
+ str(x)
+}
+#' @importFrom utils str
+#' @export
+print.mdates_duration <- function(x, ...) {
+ str(x)
+}
+
+
diff --git a/R/coerce_extrema.R b/R/coerce_extrema.R
new file mode 100644
index 00000000..b34fe3d1
--- /dev/null
+++ b/R/coerce_extrema.R
@@ -0,0 +1,130 @@
+#' Resolves messy dates into an extrema
+#' @description
+#' This collection of S3 methods 'resolve' messy dates into a single date
+#' according to some explicit bias,
+#' such as returning the minimum or maximum date,
+#' the mean, median, or modal date,
+#' or a random date from among the possible resolutions for each messy date.
+#' If the date is not 'messy' (i.e. has no annotations)
+#' then just that precise date is returned.
+#' This can be useful for various descriptive or inferential projects.
+#' @param ... a mdate object
+#' @param na.rm Should NAs be removed? True by default.
+#' @importFrom stringi stri_detect_regex stri_replace_all_regex
+#' @return A single scalar or vector of dates
+#' @examples
+#' d <- as_messydate(c("2008-03-25", "?2012-02-27", "2001-01?", "2001~",
+#' "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}",
+#' "{2001-01,2001-02-02}", "2008-XX-31", "-0050-01-01"))
+#' d
+#' @name coerce_extrema
+NULL
+
+#' @rdname coerce_extrema
+#' @export
+vmin <- function(..., na.rm = FALSE) UseMethod("vmin")
+
+#' @rdname coerce_extrema
+#' @examples
+#' vmin(d)
+#' @export
+vmin.mdate <- function(..., na.rm = TRUE){
+ d <- list(...)[[1]]
+ dates <- d
+ if(na.rm) dates <- stats::na.omit(d)
+ dates <- stringi::stri_replace_all_regex(dates, "~|\\?", "")
+ dates <- .remove_post(dates)
+ dates <- .replace_earliest(dates)
+ mdate(dates)
+}
+
+#' @rdname coerce_extrema
+#' @examples
+#' min(d)
+#' @export
+min.mdate <- function(..., na.rm = TRUE){
+ d <- list(...)[[1]]
+ dates <- d
+ if(na.rm) dates <- stats::na.omit(d)
+ dates <- stringi::stri_replace_all_regex(dates, "~|\\?", "")
+ dates <- .remove_post(dates)
+ dates <- .replace_earliest(dates)
+ dates <- mdate(dates)
+ if(any(is_bce(dates)))
+ dates[is_bce(dates)][order(as.character(dates[is_bce(dates)]),
+ decreasing = TRUE)][1] else
+ dates[order(as.character(dates))==1]
+}
+
+.remove_post <- function(dates){
+ dates <- stringi::stri_replace_all_regex(dates, "\\.\\.$|,.*$|\\{", "")
+ dates <- stringi::stri_replace_all_regex(dates, "^(.+)\\.\\..*$", "$1")
+ dates <- stringi::stri_replace_all_regex(dates, "\\.\\.", "")
+ dates
+}
+
+.replace_earliest <- function(dates){
+ dates <- stringi::stri_replace_last_regex(dates,
+ "XX", "01")
+ dates <- stringi::stri_replace_last_regex(dates,
+ "^(.*[:digit:]{4})$", "$1-01-01")
+ dates <- stringi::stri_replace_last_regex(dates,
+ "^(.*[:digit:]{4})-([:digit:]{2})$", "$1-$2-01")
+ # dates <- stringi::stri_replace_last_regex(dates,
+ # "^-([:digit:]{4})-([:digit:]{2})$", "-$1-$2-01")
+ dates
+}
+
+#' @rdname coerce_extrema
+#' @export
+vmax <- function(..., na.rm = FALSE) UseMethod("vmax")
+
+#' @rdname coerce_extrema
+#' @examples
+#' vmax(d)
+#' @export
+vmax.mdate <- function(..., na.rm = TRUE){
+ d <- list(...)[[1]]
+ dates <- d
+ if(na.rm) dates <- stats::na.omit(d)
+ dates <- stringi::stri_replace_all_regex(dates, "~|\\?", "")
+ dates <- unspecified_months(dates)
+ dates <- .remove_pre(dates)
+ dates <- .replace_latest(dates)
+ mdate(dates)
+}
+
+#' @rdname coerce_extrema
+#' @examples
+#' max(d)
+#' @export
+max.mdate <- function(..., na.rm = TRUE) {
+
+ d <- list(...)[[1]]
+ dates <- stringi::stri_replace_all_regex(d, "~|\\?", "")
+ dates <- unspecified_months(dates)
+ dates <- .remove_pre(dates)
+ dates <- .replace_latest(dates)
+ dates <- mdate(dates)
+ if(all(is_bce(dates), na.rm = TRUE))
+ dates[order(dates, decreasing = TRUE)][1] else
+ dates[!is_bce(dates)][order(as.character(dates[!is_bce(dates)]),
+ decreasing = TRUE)][1]
+
+}
+
+.remove_pre <- function(dates){
+ dates <- stringi::stri_replace_all_regex(dates, "^\\.\\.|^.*,|\\}", "")
+ dates <- stringi::stri_replace_all_regex(dates, "^.*\\.\\.(.+)$", "$1")
+ dates <- stringi::stri_replace_all_regex(dates, "\\.\\.", "")
+ dates
+}
+
+.replace_latest <- function(dates){
+ dates <- stringi::stri_replace_last_regex(dates,
+ "^(.*[:digit:]{4})$", "$1-12-31")
+ dates <- stringi::stri_replace_last_regex(dates,
+ "-XX-", "-12-")
+ dates
+}
+
diff --git a/R/coerce_from_messydate.R b/R/coerce_from_messydate.R
index 3839751e..e4511c0b 100644
--- a/R/coerce_from_messydate.R
+++ b/R/coerce_from_messydate.R
@@ -1,53 +1,50 @@
#' Coercion from messy dates
-#'
#' @description
-#' These functions coerce objects of `mdate` class to
-#' common date classes such as `Date`, `POSIXct`, and `POSIXlt`.
-#' Since `mdate` objects can hold multiple individual dates,
-#' however, an additional function must be passed as an argument
-#' so that these functions know how to coerce resolve multiple dates
-#' into a single date.
+#' These functions coerce objects of `mdate` class to
+#' common date classes such as `Date`, `POSIXct`, and `POSIXlt`.
+#' Since `mdate` objects can hold multiple individual dates,
+#' however, an additional function must be passed as an argument
+#' so that these functions know how to coerce resolve multiple dates
+#' into a single date.
#'
-#' For example, one might wish to use the earliest possible date
-#' in any ranges of dates (`min`), the latest possible date (`max`),
-#' some notion of a central tendency (`mean`, `median`, or `modal`),
-#' or even a `random` selection from among the candidate dates.
+#' For example, one might wish to use the earliest possible date
+#' in any ranges of dates (`min`), the latest possible date (`max`),
+#' some notion of a central tendency (`mean`, `median`, or `modal`),
+#' or even a `random` selection from among the candidate dates.
#'
-#' These functions then, building on `expand()` and the resolve functions,
-#' are particularly useful in converting back out of the `mdate` class
-#' for use with existing methods and models,
-#' especially for checking the robustness of results.
+#' These functions then, building on `expand()` and the resolve functions,
+#' are particularly useful in converting back out of the `mdate` class
+#' for use with existing methods and models,
+#' especially for checking the robustness of results.
#' @param x A `mdate` object
#' @param ... Arguments passed on to the S3 generics.
#' @param FUN A function that can be used to resolve expanded messy dates
-#' into a single date.
-#' For example, `min()`, `max()`, `mean()`, `median()`,
-#' `modal()`, and `random()`.
+#' into a single date.
+#' For example, `min()`, `max()`, `mean()`, `median()`,
+#' `modal()`, and `random()`.
#' @return A date object of `Date`, `POSIXct`, or `POSIXlt` class
-#' @name from_messydate
+#' @name coerce_from
NULL
-#> NULL
-#' @rdname from_messydate
+#' @rdname coerce_from
#' @examples
-#' as.Date(as_messydate("2012-01"), min)
-#' as.Date(as_messydate("2012-01-01"), mean)
-#' as.Date(as_messydate("2012-01"), max)
-#' as.Date(as_messydate("2012-01"), median)
-#' as.Date(as_messydate("2012-01"), modal)
-#' as.Date(as_messydate("2012-01"), random)
-#' as.Date(as_messydate("1000 BC"), max)
-#' as.Date(as_messydate("1000 BC"), mean)
-#' as.Date(as_messydate("1000 BC"), median)
-#' as.Date(as_messydate(c("-1000", "2020")), min)
+#' as.Date(as_messydate("2012-01"), FUN = vmin)
+#' as.Date(as_messydate("2012-01-01"), FUN = vmean)
+#' as.Date(as_messydate("2012-01"), FUN = vmax)
+#' as.Date(as_messydate("2012-01"), FUN = vmedian)
+#' as.Date(as_messydate("2012-01"), FUN = vmodal)
+#' as.Date(as_messydate("2012-01"), FUN = vrandom)
+#' as.Date(as_messydate("1000 BC"), FUN = vmax)
+#' as.Date(as_messydate("1000 BC"), FUN = vmedian)
+#' as.Date(as_messydate(c("-1000", "2020")), FUN = vmin)
#' @export
-as.Date.mdate <- function(x, ..., FUN) {
+as.Date.mdate <- function(x, FUN = vmin, ...) {
# # fix argument ordering issues
# if (missing(FUN)){
# if(length(list(...)) > 0) FUN <- list(...)[[1]] else
# FUN <- messydates::min.mdate
# }
- if(missing(FUN)) FUN <- min
+ # if(missing(FUN)) FUN <- min
x <- FUN(x)
x <- suppressWarnings(ifelse(stringi::stri_detect_regex(x, "^-"),
lubridate::as_date(negative_dates(x)),
@@ -55,26 +52,30 @@ as.Date.mdate <- function(x, ..., FUN) {
as.Date(x, origin = "1970-01-01")
}
-#' @rdname from_messydate
+#' @rdname coerce_from
+#' @param tz Character string specifying the time zone for the conversion,
+#' if required.
+#' By default "UTC" (Universal Time Coordinated), equivalent to GMT.
+#' If "" then the current time zone is used.
#' @export
-as.POSIXct.mdate <- function(x, ..., FUN) {
- if (missing(FUN) & length(list(...)) > 0) FUN <- list(...)[[1]]
+as.POSIXct.mdate <- function(x, tz = "UTC", FUN = vmin, ...) {
+ # if (missing(FUN) & length(list(...)) > 0) FUN <- list(...)[[1]]
x <- FUN(x)
if (stringi::stri_detect_regex(x, "^-")) {
stop("For conversion of negative dates from mdate class use as.Date()")
}
- as.POSIXct(x)
+ as.POSIXct(as.character(x), tz = tz)
}
-#' @rdname from_messydate
+#' @rdname coerce_from
#' @export
-as.POSIXlt.mdate <- function(x, ..., FUN) {
- if (missing(FUN) & length(list(...)) > 0) FUN <- list(...)[[1]]
+as.POSIXlt.mdate <- function(x, tz = "UTC", FUN = vmin, ...) {
+ # if (missing(FUN) & length(list(...)) > 0) FUN <- list(...)[[1]]
x <- FUN(x)
if (stringi::stri_detect_regex(x, "^-")) {
stop("For conversion of negative dates from mdate class use as.Date()")
}
- as.POSIXlt(x)
+ as.POSIXlt(as.character(x), tz = tz)
}
# Helper function for returning negative dates in date formats
@@ -88,3 +89,20 @@ negative_dates <- function(x) {
x <- lubridate::as_date(x)
x
}
+
+#' @export
+as.data.frame.mdate <- function(x, ...) {
+ as.data.frame.vector(x, ...)
+}
+
+#' @export
+as.list.mdate <- function(x, ...) {
+ lapply(unclass(x), as_messydate)
+}
+
+#' @export
+as.double.mdate <- function(x, ...) {
+ if(any(is_bce(x))) x[is_bce(x)] <- negative_dates(x)[is_bce(x)]
+ as.double(lubridate::as_date(x))
+}
+
diff --git a/R/coerce_resolve.R b/R/coerce_resolve.R
deleted file mode 100644
index bc17a6ae..00000000
--- a/R/coerce_resolve.R
+++ /dev/null
@@ -1,220 +0,0 @@
-#' Resolves messy dates into a single value
-#'
-#' This collection of S3 methods 'resolve' messy dates into a single date
-#' according to some explicit bias,
-#' such as returning the minimum or maximum date,
-#' the mean, median, or modal date,
-#' or a random date from among the possible resolutions for each messy date.
-#' If the date is not 'messy' (i.e. has no annotations)
-#' then just that precise date is returned.
-#' This can be useful for various descriptive or inferential projects.
-#' @param ... a mdate object
-#' @param na.rm Should NAs be removed? True by default.
-#' @importFrom stringi stri_detect_regex stri_replace_all_regex
-#' @return A single scalar or vector of dates
-#' @examples
-#' d <- as_messydate(c("2008-03-25", "?2012-02-27", "2001-01?", "2001~",
-#' "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}",
-#' "{2001-01,2001-02-02}", "2008-XX-31", "-0050-01-01"))
-#' d
-#' @name coerce_resolve
-NULL
-#> NULL
-
-#' @rdname coerce_resolve
-#' @param recursive If recursive = TRUE, then the dates will be resolved
-#' to a single date. If recursive = FALSE, then the dates will be resolved
-#' to a vector the length of the original vector.
-#' By default FALSE.
-#' @examples
-#' min(d)
-#' @export
-min.mdate <- function(..., na.rm = TRUE, recursive = FALSE){
- d <- list(...)[[1]]
- dates <- stringi::stri_replace_all_regex(d, "~|\\?", "")
- dates <- .remove_post(dates)
- dates <- .replace_earliest(dates)
- # if(any(stringi::stri_detect_regex(dates, "^~")))
- # dates <- expand_approximate_years(dates, approx_range = approx_range)
- # if(any(stringi::stri_detect_regex(dates, "[:digit:]~"))){
- # dates <- expand_approximate_months(dates, approx_range = approx_range)
- # dates <- expand_approximate_days(dates, approx_range = approx_range)
- # }
- if(recursive){
- if(any(is_bce(dates))) max(dates[is_bce(dates)]) else
- min(dates)
- } else dates
-}
-
-.remove_post <- function(dates){
- dates <- stringi::stri_replace_all_regex(dates, "\\.\\.$|,.*$|\\{", "")
- dates <- stringi::stri_replace_all_regex(dates, "^(.+)\\.\\..*$", "$1")
- dates <- stringi::stri_replace_all_regex(dates, "\\.\\.", "")
- dates
-}
-
-.replace_earliest <- function(dates){
- dates <- stringi::stri_replace_last_regex(dates,
- "XX", "01")
- dates <- stringi::stri_replace_last_regex(dates,
- "^(.*[:digit:]{4})$", "$1-01-01")
- dates <- stringi::stri_replace_last_regex(dates,
- "^(.*[:digit:]{4})-([:digit:]{2})$", "$1-$2-01")
- # dates <- stringi::stri_replace_last_regex(dates,
- # "^-([:digit:]{4})-([:digit:]{2})$", "-$1-$2-01")
- dates
-}
-
-#' @rdname coerce_resolve
-#' @examples
-#' max(d)
-#' @export
-max.mdate <- function(..., na.rm = TRUE, recursive = FALSE) {
-
- d <- list(...)[[1]]
- dates <- stringi::stri_replace_all_regex(d, "~|\\?", "")
- dates <- unspecified_months(dates)
- dates <- .remove_pre(dates)
- dates <- .replace_latest(dates)
-
- if(recursive){
- if(any(is_bce(dates))) max(dates[!is_bce(dates)]) else
- max(dates)
- } else dates
-
-}
-
-.remove_pre <- function(dates){
- dates <- stringi::stri_replace_all_regex(dates, "^\\.\\.|^.*,|\\}", "")
- dates <- stringi::stri_replace_all_regex(dates, "^.*\\.\\.(.+)$", "$1")
- dates <- stringi::stri_replace_all_regex(dates, "\\.\\.", "")
- dates
-}
-
-.replace_latest <- function(dates){
- dates <- stringi::stri_replace_last_regex(dates,
- "^(.*[:digit:]{4})$", "$1-12-31")
- dates <- stringi::stri_replace_last_regex(dates,
- "-XX-", "-12-")
- dates
-}
-
-#' @rdname coerce_resolve
-#' @importFrom stats median
-#' @examples
-#' median(d)
-#' @export
-median.mdate <- function(..., na.rm = TRUE, recursive = FALSE) {
-
- x <- as.list(...)
- y <- unlist(lapply(x, function(y) ifelse(!is_precise(y), expand(y), y)),
- recursive = recursive)
- if(recursive){
- if (length(y) %% 2 == 0) {
- as.character(median(unlist(y[-1]), na.rm = na.rm))
- }
- else{
- as.character(median(y, na.rm = na.rm))
- }
- } else {
- unlist(lapply(y, function(z) {
- if (length(z) %% 2 == 0) {
- z <- unlist(z[-1])
- z <- as.character(median(z, na.rm = na.rm))
- z
- }
- else{
- z <- as.character(median(z, na.rm = na.rm))
- z
- }
- }), recursive = FALSE)
- }
-}
-
-#' @rdname coerce_resolve
-#' @param trim the fraction (0 to 0.5) of observations to be trimmed
-#' from each end of x before the mean is computed.
-#' Values of trim outside that range are taken as the nearest endpoint.
-#' @importFrom lubridate as_date
-#' @examples
-#' mean(d)
-#' @export
-mean.mdate <- function(..., trim = 0, na.rm = TRUE, recursive = FALSE) {
- x <- as.list(...)
- y <- unlist(lapply(x, function(y) ifelse(!is_precise(y), expand(y), y)),
- recursive = recursive)
- if(recursive){
- if (length(y) > 1 & stringi::stri_detect_regex(y[1], "^-", negate = TRUE)) {
- y <- as.character(mean(as.Date(y), trim = 0, na.rm = TRUE))
- }
- if (length(y) > 1 & stringi::stri_detect_regex(y[1], "^-")) {
- y <- paste0("-", as.character(mean(lubridate::as_date(y),
- trim = 0, na.rm = TRUE)))
- y <- zero_padding(y)
- }
- y
- } else {
- unlist(lapply(y, function(x) {
- if (length(x) > 1 & stringi::stri_detect_regex(x[1], "^-", negate = TRUE)) {
- x <- as.character(mean(as.Date(x), trim = 0, na.rm = TRUE))
- }
- if (length(x) > 1 & stringi::stri_detect_regex(x[1], "^-")) {
- x <- paste0("-", as.character(mean(lubridate::as_date(x),
- trim = 0, na.rm = TRUE)))
- x <- zero_padding(x)
- }
- x
- }), recursive = FALSE)
- }
-}
-
-#' @rdname coerce_resolve
-#' @export
-modal <- function(..., na.rm = FALSE, recursive = FALSE) UseMethod("modal")
-
-#' @rdname coerce_resolve
-#' @examples
-#' modal(d)
-#' @export
-modal.mdate <- function(..., na.rm = TRUE, recursive = FALSE) {
-
- d <- list(...)[[1]]
- getmode <- function(v) {
- uniqv <- unique(v)
- uniqv[which.max(tabulate(match(v, uniqv)))]
- }
- d <- purrr::map_chr(expand(d), function(y) getmode(y))
- if(recursive) d <- as.character(getmode(d))
- d
-}
-
-#' @rdname coerce_resolve
-#' @param size a non-negative integer giving the number of items to choose.
-#' @param replace should sampling be with replacement?
-#' @param prob a vector of probability weights
-#' for obtaining the elements of the vector being sampled.
-#' @export
-random <- function(..., size,
- replace = FALSE,
- prob = NULL, recursive = FALSE) UseMethod("random")
-
-#' @rdname coerce_resolve
-#' @examples
-#' random(d)
-#' @export
-random.mdate <- function(...,
- size,
- replace = FALSE,
- prob = NULL, recursive = FALSE) {
- x <- as.list(...)
- y <- unlist(lapply(x, function(y) ifelse(!is_precise(y), expand(y), y)),
- recursive = recursive)
- if(recursive){
- as.character(sample(y, size = 1))
- } else {
- unlist(lapply(y, function(x) {
- if (length(x) > 1) x <- as.character(sample(x, size = 1))
- x
- }), recursive = FALSE)
- }
-}
diff --git a/R/coerce_tendency.R b/R/coerce_tendency.R
new file mode 100644
index 00000000..26ebb66b
--- /dev/null
+++ b/R/coerce_tendency.R
@@ -0,0 +1,163 @@
+#' Resolves messy dates into a central tendency
+#' @description
+#' These functions resolve messydates by their central tendency.
+#' While the functions `mean()`, `median()`, and `modal()` summarise the
+#' vector to a single value, `v*()` versions return a vector of the same length.
+#' @name coerce_tendency
+#' @inheritParams coerce_extrema
+#' @examples
+#' d <- as_messydate(c("2008-03-25", "?2012-02-27", "2001-01?", "2001~",
+#' "2001-01-01..2001-02-02", "{2001-01-01,2001-02-02}",
+#' "{2001-01,2001-02-02}", "2008-XX-31", "-0050-01-01"))
+#' d
+NULL
+
+#' @rdname coerce_tendency
+#' @importFrom stats median
+#' @examples
+#' median(d)
+#' @export
+median.mdate <- function(..., na.rm = TRUE) {
+
+ x <- list(...)[[1]]
+ y <- unlist(expand(x))
+ y <- .order_messy(y)
+ median(y, na.rm = na.rm)
+}
+
+.order_messy <- function(y){
+ if(any(is_bce(y))){
+ bcey <- y[is_bce(y)]
+ cey <- y[!is_bce(y)]
+ c(bcey[order(bcey, decreasing = TRUE)],
+ cey[order(cey)])
+ } else {
+ y[order(y)]
+ }
+}
+
+#' @rdname coerce_tendency
+#' @export
+vmedian <- function(..., na.rm = TRUE) UseMethod("vmedian")
+
+#' @rdname coerce_tendency
+#' @importFrom stats median
+#' @examples
+#' vmedian(d)
+#' @export
+vmedian.mdate <- function(..., na.rm = TRUE) {
+
+ x <- as.list(...)
+ vapply(x, function(y){
+ z <- suppressWarnings(median(y, na.rm = na.rm))
+ if(is.na(z)){
+ if(length(expand(y)[[1]]) %% 2 == 0)
+ z <- median(.order_messy(expand(y)[[1]])[-1])
+ }
+ z
+ }, FUN.VALUE = character(1))
+}
+
+#' @rdname coerce_tendency
+#' @param trim the fraction (0 to 0.5) of observations to be trimmed
+#' from each end of x before the mean is computed.
+#' Values of trim outside that range are taken as the nearest endpoint.
+#' @importFrom lubridate as_date
+#' @examples
+#' mean(d)
+#' @export
+mean.mdate <- function(..., trim = 0, na.rm = TRUE) {
+
+ x <- list(...)[[1]]
+ y <- unlist(expand(x))
+ as.character(lubridate::as_date(mean(as.double(lubridate::as_date(y)))))
+}
+
+
+#' @rdname coerce_tendency
+#' @export
+vmean <- function(..., na.rm = TRUE) UseMethod("vmean")
+
+#' @rdname coerce_tendency
+#' @examples
+#' vmean(d)
+#' @export
+vmean.mdate <- function(..., trim = 0, na.rm = TRUE) {
+ x <- list(...)[[1]]
+ vapply(expand(x), function(y)
+ as.character(lubridate::as_date(mean(as.double(lubridate::as_date(y))))),
+ FUN.VALUE = character(1))
+}
+
+#' @rdname coerce_tendency
+#' @export
+modal <- function(..., na.rm = TRUE) UseMethod("modal")
+
+#' @rdname coerce_tendency
+#' @examples
+#' modal(d)
+#' @export
+modal.mdate <- function(..., na.rm = TRUE) {
+ d <- list(...)[[1]]
+ .getmode(unlist(expand(d)))
+}
+
+.getmode <- function(v) {
+ uniqv <- unique(v)
+ uniqv[which.max(tabulate(match(v, uniqv)))]
+}
+
+#' @rdname coerce_tendency
+#' @export
+vmodal <- function(..., na.rm = TRUE) UseMethod("vmodal")
+
+#' @rdname coerce_tendency
+#' @examples
+#' vmodal(d)
+#' @export
+vmodal.mdate <- function(..., na.rm = TRUE) {
+
+ d <- list(...)[[1]]
+ d <- purrr::map_chr(expand(d), function(y) .getmode(y))
+ d
+}
+
+#' @rdname coerce_tendency
+#' @export
+random <- function(..., na.rm = TRUE) UseMethod("random")
+
+#' @rdname coerce_tendency
+#' @examples
+#' random(d)
+#' @export
+random.mdate <- function(..., na.rm = TRUE) {
+ x <- list(...)[[1]]
+ y <- unlist(expand(x))
+ if(na.rm) y <- y[!is.na(y)]
+ sample(y, 1)
+}
+
+#' @export
+random.character <- function(..., na.rm = TRUE) {
+ y <- list(...)[[1]]
+ # y <- suppressMessages(unlist(expand(x)))
+ if(na.rm) y <- y[!is.na(y)]
+ sample(y, 1)
+}
+
+#' @rdname coerce_tendency
+#' @export
+vrandom <- function(..., na.rm = TRUE) UseMethod("vrandom")
+
+#' @rdname coerce_tendency
+#' @examples
+#' vrandom(d)
+#' @export
+vrandom.mdate <- function(..., na.rm = TRUE) {
+
+ x <- as.list(...)
+ vapply(x, function(y){
+ random(expand(y)[[1]], na.rm = na.rm)
+ }, FUN.VALUE = character(1))
+
+}
diff --git a/R/coerce_to_messydate.R b/R/coerce_to_messydate.R
index 62bcd228..3003b80d 100644
--- a/R/coerce_to_messydate.R
+++ b/R/coerce_to_messydate.R
@@ -28,10 +28,10 @@
#' based on which the date is reordered into YYYY-MM-DD format
#' and further completed to YYYY-MM-DD format if they choose to do so.
#' @return A `mdate` class object
-#' @name messydate
+#' @name coerce_to
NULL
-#' @describeIn messydate Core `mdate` class coercion function
+#' @describeIn coerce_to Core `mdate` class coercion function
#' @examples
#' as_messydate("2021")
#' as_messydate("2021-02")
@@ -53,24 +53,26 @@ NULL
as_messydate <- function(x, resequence = FALSE)
UseMethod("as_messydate")
-#' @describeIn messydate Coerce from `Date` to `mdate` class
+#' @describeIn coerce_to Coerce from `Date` to `mdate` class
#' @export
as_messydate.Date <- function(x, resequence = FALSE) {
x <- as.character(x)
new_messydate(x)
}
-#' @describeIn messydate Coerce from `POSIXct` to `mdate` class
+#' @describeIn coerce_to Coerce from `POSIXct` to `mdate` class
#' @export
as_messydate.POSIXct <- function(x, resequence = FALSE) {
- x <- as.character(x)
+ if(any(is.infinite(x))) x[is.infinite(x)] <- "9999-12-31"
+ x <- as.character(as.Date(x))
new_messydate(x)
}
-#' @describeIn messydate Coerce from `POSIXlt` to `mdate` class
+#' @describeIn coerce_to Coerce from `POSIXlt` to `mdate` class
#' @export
as_messydate.POSIXlt <- function(x, resequence = FALSE) {
- x <- as.character(x)
+ if(any(is.infinite(x))) x[is.infinite(x)] <- "9999-12-31"
+ x <- as.character(as.Date(x))
new_messydate(x)
}
@@ -80,9 +82,10 @@ as_messydate.mdate <- function(x, resequence = FALSE) {
new_messydate(x)
}
-#' @describeIn messydate Coerce character date objects to `mdate` class
+#' @describeIn coerce_to Coerce character date objects to `mdate` class
#' @export
as_messydate.character <- function(x, resequence = NULL) {
+ if(any(is.infinite(x))) x[is.infinite(x)] <- "9999-12-31"
d <- standardise_text(x)
d <- standardise_date_separators(d)
if (!is.null(resequence)) {
@@ -107,14 +110,15 @@ as_messydate.character <- function(x, resequence = NULL) {
new_messydate(d)
}
-#' @describeIn messydate Coerce numeric objects to `mdate` class
+#' @describeIn coerce_to Coerce numeric objects to `mdate` class
#' @export
as_messydate.numeric <- function(x, resequence = NULL) {
+ if(any(is.infinite(x))) x[is.infinite(x)] <- "9999-12-31"
d <- as.character(x)
new_messydate(d)
}
-#' @describeIn messydate Coerce list date objects to the most concise
+#' @describeIn coerce_to Coerce list date objects to the most concise
#' representation of `mdate` class
#' @examples
#' as_messydate(list(c("2012-06-01", "2012-06-02", "2012-06-03")))
@@ -128,7 +132,7 @@ as_messydate.list <- function(x, resequence = FALSE) {
})
}
-#' @rdname messydate
+#' @rdname coerce_to
#' @export
mdate <- as_messydate
@@ -578,39 +582,6 @@ complete_ambiguous_19 <- function(d) {
out
}
-# Make ####
-
-#' @describeIn messydate Composes `mdate` from multiple variables
-#' @param ... One (yyyy-mm-dd), two (yyyy-mm-dd, yyyy-mm-dd),
-#' or three (yyyy, mm, dd) variables.
-#' @details If three date variables are passed to `make_messydate()`,
-#' function will create a single date (yyyy-mm-dd) from it.
-#' If two date variables are passed to `make_messydate()`,
-#' function will create a range of dates from it (yyyy-mm-dd..yyyy-mm-dd).
-#' If one date variable is passed to `make_messydate()`,
-#' function defaults to `as_messydate()`.
-#' @importFrom purrr map pmap_chr
-#' @examples
-#' make_messydate("2010", "10", "10")
-#' @export
-make_messydate <- function(..., resequence = FALSE) {
- dots <- list(...)
- if (length(dots) == 1) {
- dots <- do.call(as.character, dots)
- dates <- unlist(dots)
- } else if (length(dots) == 2) {
- dots <- purrr::map(dots, as.character)
- dates <- unlist(purrr::pmap_chr(dots, paste, sep = ".."))
- dates <- gsub("NA..NA", "NA", dates)
- } else if (length(dots) == 3) {
- dots <- purrr::map(dots, as.character)
- dates <- unlist(purrr::pmap_chr(dots, paste, sep = "-"))
- dates <- gsub("NA-NA-NA", "NA", dates)
- } else stop("make_messydate() takes one variable (yyyy-mm-dd),
- two variables (yyyy-mm-dd, yyyy-mm-dd), or three variables (yyyy, mm, dd).")
- as_messydate(dates, resequence)
-}
-
stri_squish <- function(charvec){
stringi::stri_trim_both(stringi::stri_replace_all_regex(charvec, "\\s+", " "))
}
diff --git a/R/component_extract.R b/R/component_extract.R
index 5c71e578..aa06a327 100644
--- a/R/component_extract.R
+++ b/R/component_extract.R
@@ -50,7 +50,6 @@ day <- function(x) {
#' precision(as_messydate(c("2012-02-03","2012","2012-02")))
#' @export
precision <- function(x) {
- x <- expand(x)
- out <- sum(lengths(x))
- out
+ out <- expand(x)
+ lengths(out)
}
diff --git a/R/convert_sequence.R b/R/convert_sequence.R
new file mode 100644
index 00000000..54cc6501
--- /dev/null
+++ b/R/convert_sequence.R
@@ -0,0 +1,63 @@
+#' Sequence method for messydates
+#' @description
+#' This function provides a sequence (`seq()`) method for messydates.
+#' This can be used with ranges or unspecified dates,
+#' and is particularly useful for defining a sequence of dates
+#' before the common era or between eras.
+#' @name convert_sequence
+#' @param from A messydate or range.
+#' If 'from' is a range and 'to' is not specified,
+#' 'from' will be the minimum of the range and 'to' will be maximum.
+#' @param to A messydate.
+#' @param by Increment of the sequence. By default "days".
+#' @param ... Arguments passed to or from methods.
+#' @examples
+#' seq(mdate("-0001-12-20"), mdate("0001-01-10"))
+#' @export
+seq.mdate <- function(from, to, by = "days", ...) {
+
+ if(missing(to) & !is_precise(from)){
+ to <- max(from)
+ from <- min(from)
+ }
+
+ # straight forward sequence
+ if(!any(is_bce(c(from, to)))){
+ seq(as.Date(from), as.Date(to), by = by)
+ } else {
+
+ fromp <- as.Date(stringi::stri_replace_first_regex(from, "^-", ""))
+ # sequence before common era
+ if(is_bce(to)){
+ top <- as.Date(stringi::stri_replace_first_regex(to, "^-", ""))
+ .neg_seqs(fromp, top, by = by)
+ } else {
+ # sequence between eras
+ zero_padding(c(.neg_seqs(fromp, as.Date("0001-12-31"), by = by),
+ as.character(seq(as.Date("0001-01-01"), as.Date(to), by = by))))
+ # zero_padding(c(rev(paste0("-", seq(as.Date("0001-01-01"), fromp, by = by))),
+ # as.character(seq(as.Date("0001-01-01"), as.Date(to), by = by))))
+ }
+ }
+}
+
+.neg_seqs <- function(fromp, top, by = "days"){
+ if(year(fromp) == year(top)){
+ zero_padding(paste0("-", seq(min(c(fromp, top)),
+ max(c(fromp,top)), by = by)))
+ } else {
+ strt <- max(c(fromp, top))
+ ends <- min(c(fromp, top))
+ strt_yr <- year(strt)
+ strt_sq <- seq(as.Date(strt), as.Date(paste0(strt_yr,"-12-31")), by = by)
+ ends_yr <- year(ends)
+ ends_sq <- seq(as.Date(paste0(ends_yr, "-01-01")), as.Date(ends), by = by)
+ if(strt_yr - ends_yr > 1){
+ mids_sq <- seq(as.Date(paste0(ends_yr+1, "-01-01")),
+ as.Date(paste0(strt_yr-1,"-12-31")), by = by)
+ if(length(unique(year(mids_sq)))>1)
+ mids_sq <- mids_sq[order(year(mids_sq), decreasing = TRUE)]
+ zero_padding(paste0("-", c(strt_sq, mids_sq, ends_sq)))
+ } else zero_padding(paste0("-", c(strt_sq, ends_sq)))
+ }
+}
diff --git a/R/data_battles.R b/R/data_battles.R
index faf0f7ce..48bf4417 100644
--- a/R/data_battles.R
+++ b/R/data_battles.R
@@ -11,8 +11,4 @@
#' \item{US_party}{is the US a party to the battle, numeric}
#' \item{N_actors}{number of actors to conflict, numeric}
#' }
-#' @details
-#' ``` {r, echo = FALSE, warning = FALSE}
-#' mreport(battles)
-#' ```
"battles"
diff --git a/R/operate_arithmetic.R b/R/operate_arithmetic.R
index cd36d12a..80de0853 100644
--- a/R/operate_arithmetic.R
+++ b/R/operate_arithmetic.R
@@ -18,17 +18,17 @@
#' as_messydate("2001-01-01..2001-01-04") - as_messydate("2001-01-02")
#' #as_messydate("2001-01-01") - as_messydate("2001-01-03")
#' }
-#' @name operate
+#' @name operate_arithmetic
NULL
-#' @rdname operate
+#' @rdname operate_arithmetic
#' @export
`+.mdate` <- function(e1, e2) {
e2 <- parse_date_strings(e2)
add(e1, e2)
}
-#' @rdname operate
+#' @rdname operate_arithmetic
#' @export
`-.mdate` <- function(e1, e2) {
e2 <- parse_date_strings(e2)
diff --git a/R/operate_inequalities.R b/R/operate_inequalities.R
new file mode 100644
index 00000000..cd680e06
--- /dev/null
+++ b/R/operate_inequalities.R
@@ -0,0 +1,127 @@
+# Inequalities ####
+
+#' Logical operations on messy dates
+#' @param e1,e2 `mdate` or other class objects
+#' @name operate_inequalities
+NULL
+
+#' @describeIn operate_inequalities tests whether the dates in the first vector precede
+#' the dates in the second vector.
+#' Returns `NA` when the date order can't be determined.
+#' @examples
+#' as_messydate("2012-06-02") > as.Date("2012-06-01") # TRUE
+#' # 2012-06-XX could mean 2012-06-03, so unknown if it comes before 2012-06-02
+#' as_messydate("2012-06-XX") < as.Date("2012-06-02") # NA
+#' # But 2012-06-XX cannot be before 2012-06-01
+#' as_messydate("2012-06-XX") >= as.Date("2012-06-01") # TRUE
+#' @export
+`<.mdate` <- function(e1, e2) {
+ if (!is_messydate(e1)) e1 <- as_messydate(e1)
+ if (!is_messydate(e2)) e2 <- as_messydate(e2)
+ ranges <- numeric_time_ranges(e1, e2)
+ x <- rep(NA, max(length(e1), length(e2)))
+ x[ranges[["max1"]] < ranges[["min2"]]] <- TRUE
+ x[ranges[["min1"]] > ranges[["max2"]]] <- FALSE
+ x[ranges[["max1"]] == ranges[["min2"]]] <- FALSE
+ x[ranges[["min1"]] == ranges[["max2"]]] <- FALSE
+ x
+}
+
+# Quoth the {lubridate} team:
+# Nothing else seems to work, only this sneaky trick.
+evalqOnLoad({
+ registerS3method("<", "Date", `<.mdate`)
+ registerS3method("<", "POSIXt", `<.mdate`)
+})
+
+numeric_time_ranges <- function(e1, e2) {
+ if (is_messydate(e1)) {
+ min1 <- as.Date(e1, FUN = vmin)
+ max1 <- as.Date(e1, FUN = vmax)
+ if (lubridate::is.POSIXt(e2)) {
+ ptz <- lubridate::tz(e2)
+ min1 <- lubridate::force_tz(min1, ptz)
+ min1 <- as.POSIXct(min1)
+ max1 <- lubridate::force_tz(max1, ptz)
+ max1 <- as.POSIXct(max1)
+ }
+ } else {
+ min1 <- max1 <- e1
+ }
+ if (is_messydate(e2)) {
+ min2 <- as.Date(e2, FUN = vmin)
+ max2 <- as.Date(e2, FUN = vmax)
+ if (lubridate::is.POSIXt(e1)) {
+ ptz <- lubridate::tz(e1)
+ min2 <- lubridate::force_tz(min2, ptz)
+ min2 <- as.POSIXct(min2)
+ max2 <- lubridate::force_tz(max2, ptz)
+ max2 <- as.POSIXct(max2)
+ }
+ } else {
+ min2 <- max2 <- e2
+ }
+ list(
+ min1 = as.numeric(min1), max1 = as.numeric(max1),
+ min2 = as.numeric(min2), max2 = as.numeric(max2)
+ )
+}
+
+#' @describeIn operate_inequalities tests whether the dates in the first vector
+#' succeed the dates in the second vector.
+#' Returns `NA` when the date order can't be determined.
+#' @export
+`>.mdate` <- function(e1, e2) {
+ if (!is_messydate(e1)) e1 <- as_messydate(e1)
+ if (!is_messydate(e2)) e2 <- as_messydate(e2)
+ ranges <- numeric_time_ranges(e1, e2)
+ x <- rep(NA, max(length(e1), length(e2)))
+ x[ranges[["min1"]] > ranges[["max2"]]] <- TRUE
+ x[ranges[["max1"]] < ranges[["min2"]]] <- FALSE
+ x[ranges[["min1"]] == ranges[["max2"]]] <- FALSE
+ x[ranges[["max1"]] == ranges[["min2"]]] <- FALSE
+ x
+}
+
+evalqOnLoad({
+ registerS3method(">", "Date", `>.mdate`)
+ registerS3method(">", "POSIXt", `>.mdate`)
+})
+
+#' @describeIn operate_inequalities tests whether the dates in the first vector are
+#' equal to or precede the dates in the second vector.
+#' Returns `NA` when the date order can't be determined.
+#' @export
+`<=.mdate` <- function(e1, e2) {
+ if (!is_messydate(e1)) e1 <- as_messydate(e1)
+ if (!is_messydate(e2)) e2 <- as_messydate(e2)
+ ranges <- numeric_time_ranges(e1, e2)
+ x <- rep(NA, max(length(e1), length(e2)))
+ x[ranges[["max1"]] <= ranges[["min2"]]] <- TRUE
+ x[ranges[["min1"]] > ranges[["max2"]]] <- FALSE
+ x
+}
+
+evalqOnLoad({
+ registerS3method("<=", "Date", `<=.mdate`)
+ registerS3method("<=", "POSIXt", `<=.mdate`)
+})
+
+#' @describeIn operate_inequalities tests whether the dates in the first vector are equal to
+#' or succeed the dates in the second vector.
+#' Returns `NA` when the date order can't be determined.
+#' @export
+`>=.mdate` <- function(e1, e2) {
+ if (!is_messydate(e1)) e1 <- as_messydate(e1)
+ if (!is_messydate(e2)) e2 <- as_messydate(e2)
+ ranges <- numeric_time_ranges(e1, e2)
+ x <- rep(NA, max(length(e1), length(e2)))
+ x[ranges[["min1"]] >= ranges[["max2"]]] <- TRUE
+ x[ranges[["max1"]] < ranges[["min2"]]] <- FALSE
+ x
+}
+
+evalqOnLoad({
+ registerS3method(">=", "Date", `>=.mdate`)
+ registerS3method(">=", "POSIXt", `>=.mdate`)
+})
diff --git a/R/operate_logical.R b/R/operate_logical.R
deleted file mode 100644
index ae10ab03..00000000
--- a/R/operate_logical.R
+++ /dev/null
@@ -1,210 +0,0 @@
-#' Logical tests on messy dates
-#'
-#' These functions provide various logical tests for messy date objects.
-#' @name logical_tests
-#' @param x,y,e1,e2 `mdate` or other class objects
-#' @return A logical vector the same length as the `mdate` passed.
-NULL
-
-#' @describeIn logical_tests tests whether the object inherits the `mdate` class.
-#' If more rigorous validation is required, see `validate_messydate()`.
-#' @examples
-#' is_messydate(as_messydate("2012-01-01"))
-#' is_messydate(as.Date("2012-01-01"))
-#' @export
-is_messydate <- function(x) {
- inherits(x, "mdate")
-}
-
-#' @describeIn logical_tests tests whether there is any intersection between
-#' two messy dates, leveraging `intersect()`.
-#' @examples
-#' is_intersecting(as_messydate("2012-01"),
-#' as_messydate("2012-01-01..2012-02-22"))
-#' is_intersecting(as_messydate("2012-01"),
-#' as_messydate("2012-02-01..2012-02-22"))
-#' @export
-is_intersecting <- function(x, y) {
- length(intersect(unlist(expand(x)), unlist(expand(y)))) > 0
-}
-
-#' @describeIn logical_tests tests whether one or more messy date can be found
-#' within a messy date range or set.
-#' @examples
-#' is_subset(as_messydate("2012-01-01"), as_messydate("2012-01"))
-#' is_subset(as_messydate("2012-01-01..2012-01-03"), as_messydate("2012-01"))
-#' is_subset(as_messydate("2012-01-01"), as_messydate("2012-02"))
-#' @export
-is_subset <- function(x, y) {
- x <- as.character(expand(x)[[1]])
- y <- as.character(expand(y)[[1]])
- any(is.element(x, y))
-}
-
-#' @describeIn logical_tests tests whether two dates contain similar components.
-#' This can be useful for identifying dates that may be typos of one another.
-#' @examples
-#' is_similar(as_messydate("2012-06-02"), as_messydate("2012-02-06"))
-#' is_similar(as_messydate("2012-06-22"), as_messydate("2012-02-06"))
-#' @export
-is_similar <- function(x, y) {
- year(x) == year(y) & month(x) == day(y) & day(x) == month(y)
-}
-
-#' @describeIn logical_tests tests whether a date is precise (i.e. an 8 digit date).
-#' Non-precise dates contain markers that they are approximate (i.e. ~),
-#' unreliable (i.e. ?), are incomplete dates (i.e. year only),
-#' or date ranges and sets.
-#' @examples
-#' is_precise(as_messydate(c("2012-06-02", "2012-06")))
-#' @export
-is_precise <- function(x) {
- stringi::stri_detect_regex(x, "^[:digit:]{4}-[:digit:]{2}-[:digit:]{2}$|
- |^-[:digit:]{4}-[:digit:]{2}-[:digit:]{2}$")
-}
-
-#' @describeIn logical_tests tests whether a date is uncertain (i.e. contains ?).
-#' @examples
-#' is_uncertain(as_messydate(c("2012-06-02", "2012-06-02?")))
-#' @export
-is_uncertain <- function(x) {
- stringi::stri_detect_regex(x, "\\?|\\%")
-}
-
-#' @describeIn logical_tests tests whether a date is approximate (i.e. contains ~).
-#' @examples
-#' is_approximate(as_messydate(c("2012-06-02~", "2012-06-02")))
-#' @export
-is_approximate <- function(x) {
- stringi::stri_detect_regex(x, "\\~|\\%")
-}
-
-#' @describeIn logical_tests tests whether one or more messy dates are found
-#' before the common era.
-#' @examples
-#' is_bce(as_messydate(c("2012-06-02", "-2012-06-02")))
-#' @export
-is_bce <- function(x) {
- stringi::stri_detect_regex(x, "^-")
-}
-
-#' @describeIn logical_tests tests whether the dates in the first vector precede
-#' the dates in the second vector.
-#' Returns `NA` when the date order can't be determined.
-#' @examples
-#' as_messydate("2012-06-02") > as.Date("2012-06-01") # TRUE
-#' # 2012-06-XX could mean 2012-06-03, so unknown if it comes before 2012-06-02
-#' as_messydate("2012-06-XX") < as.Date("2012-06-02") # NA
-#' # But 2012-06-XX cannot be before 2012-06-01
-#' as_messydate("2012-06-XX") >= as.Date("2012-06-01") # TRUE
-#' @export
-`<.mdate` <- function(e1, e2) {
- if (!is_messydate(e1)) e1 <- as_messydate(e1)
- if (!is_messydate(e2)) e2 <- as_messydate(e2)
- ranges <- numeric_time_ranges(e1, e2)
- x <- rep(NA, max(length(e1), length(e2)))
- x[ranges[["max1"]] < ranges[["min2"]]] <- TRUE
- x[ranges[["min1"]] > ranges[["max2"]]] <- FALSE
- x[ranges[["max1"]] == ranges[["min2"]]] <- FALSE
- x[ranges[["min1"]] == ranges[["max2"]]] <- FALSE
- x
-}
-
-# Quoth the {lubridate} team:
-# Nothing else seems to work, only this sneaky trick.
-evalqOnLoad({
- registerS3method("<", "Date", `<.mdate`)
- registerS3method("<", "POSIXt", `<.mdate`)
-})
-
-numeric_time_ranges <- function(e1, e2) {
- if (is_messydate(e1)) {
- min1 <- as.Date(e1, FUN = min)
- max1 <- as.Date(e1, FUN = max)
- if (lubridate::is.POSIXt(e2)) {
- ptz <- lubridate::tz(e2)
- min1 <- lubridate::force_tz(min1, ptz)
- min1 <- as.POSIXct(min1)
- max1 <- lubridate::force_tz(max1, ptz)
- max1 <- as.POSIXct(max1)
- }
- } else {
- min1 <- max1 <- e1
- }
- if (is_messydate(e2)) {
- min2 <- as.Date(e2, FUN = min)
- max2 <- as.Date(e2, FUN = max)
- if (lubridate::is.POSIXt(e1)) {
- ptz <- lubridate::tz(e1)
- min2 <- lubridate::force_tz(min2, ptz)
- min2 <- as.POSIXct(min2)
- max2 <- lubridate::force_tz(max2, ptz)
- max2 <- as.POSIXct(max2)
- }
- } else {
- min2 <- max2 <- e2
- }
- list(
- min1 = as.numeric(min1), max1 = as.numeric(max1),
- min2 = as.numeric(min2), max2 = as.numeric(max2)
- )
-}
-
-#' @describeIn logical_tests tests whether the dates in the first vector
-#' succeed the dates in the second vector.
-#' Returns `NA` when the date order can't be determined.
-#' @export
-`>.mdate` <- function(e1, e2) {
- if (!is_messydate(e1)) e1 <- as_messydate(e1)
- if (!is_messydate(e2)) e2 <- as_messydate(e2)
- ranges <- numeric_time_ranges(e1, e2)
- x <- rep(NA, max(length(e1), length(e2)))
- x[ranges[["min1"]] > ranges[["max2"]]] <- TRUE
- x[ranges[["max1"]] < ranges[["min2"]]] <- FALSE
- x[ranges[["min1"]] == ranges[["max2"]]] <- FALSE
- x[ranges[["max1"]] == ranges[["min2"]]] <- FALSE
- x
-}
-
-evalqOnLoad({
- registerS3method(">", "Date", `>.mdate`)
- registerS3method(">", "POSIXt", `>.mdate`)
-})
-
-#' @describeIn logical_tests tests whether the dates in the first vector are
-#' equal to or precede the dates in the second vector.
-#' Returns `NA` when the date order can't be determined.
-#' @export
-`<=.mdate` <- function(e1, e2) {
- if (!is_messydate(e1)) e1 <- as_messydate(e1)
- if (!is_messydate(e2)) e2 <- as_messydate(e2)
- ranges <- numeric_time_ranges(e1, e2)
- x <- rep(NA, max(length(e1), length(e2)))
- x[ranges[["max1"]] <= ranges[["min2"]]] <- TRUE
- x[ranges[["min1"]] > ranges[["max2"]]] <- FALSE
- x
-}
-
-evalqOnLoad({
- registerS3method("<=", "Date", `<=.mdate`)
- registerS3method("<=", "POSIXt", `<=.mdate`)
-})
-
-#' @describeIn logical_tests tests whether the dates in the first vector are equal to
-#' or succeed the dates in the second vector.
-#' Returns `NA` when the date order can't be determined.
-#' @export
-`>=.mdate` <- function(e1, e2) {
- if (!is_messydate(e1)) e1 <- as_messydate(e1)
- if (!is_messydate(e2)) e2 <- as_messydate(e2)
- ranges <- numeric_time_ranges(e1, e2)
- x <- rep(NA, max(length(e1), length(e2)))
- x[ranges[["min1"]] >= ranges[["max2"]]] <- TRUE
- x[ranges[["max1"]] < ranges[["min2"]]] <- FALSE
- x
-}
-
-evalqOnLoad({
- registerS3method(">=", "Date", `>=.mdate`)
- registerS3method(">=", "POSIXt", `>=.mdate`)
-})
diff --git a/R/operate_proportional.R b/R/operate_proportional.R
index 083b5eda..2f3eaaef 100644
--- a/R/operate_proportional.R
+++ b/R/operate_proportional.R
@@ -1,17 +1,17 @@
#' Proportion of messy dates meeting logical test
-#'
-#' These functions provide various proportional tests for messy date objects.
-#' @name proportional
+#' @description
+#' These functions provide various proportional tests for messy date objects.
+#' @name operate_proportional
#' @param e1,e2 `mdate` or other class objects
#' @return The proportion that the comparison is true.
#' @return A logical vector the same length as the `mdate` passed.
NULL
-#' @rdname proportional
+#' @rdname operate_proportional
#' @export
`%l%` <- function(e1, e2) UseMethod("%l%")
-#' @describeIn proportional Tests proportion of dates in the first vector
+#' @describeIn operate_proportional Tests proportion of dates in the first vector
#' that precede the minimum in the second vector.
#' @examples
#' as_messydate("2012-06") < as.Date("2012-06-02")
@@ -30,11 +30,11 @@ evalqOnLoad({
registerS3method("%l%", "POSIXt", `%l%.mdate`)
})
-#' @rdname proportional
+#' @rdname operate_proportional
#' @export
`%g%` <- function(e1, e2) UseMethod("%g%")
-#' @describeIn proportional Tests proportion of dates in the first vector
+#' @describeIn operate_proportional Tests proportion of dates in the first vector
#' that follow the maximum in the second vector.
#' @export
#' @examples
@@ -53,11 +53,11 @@ evalqOnLoad({
registerS3method("%g%", "POSIXt", `%g%.mdate`)
})
-#' @rdname proportional
+#' @rdname operate_proportional
#' @export
`%ge%` <- function(e1, e2) UseMethod("%ge%")
-#' @describeIn proportional Tests proportion of dates in the first vector
+#' @describeIn operate_proportional Tests proportion of dates in the first vector
#' that follow or are equal to the maximum in the second vector.
#' @export
#' @examples
@@ -76,11 +76,11 @@ evalqOnLoad({
registerS3method("%ge%", "POSIXt", `%ge%.mdate`)
})
-#' @rdname proportional
+#' @rdname operate_proportional
#' @export
`%le%` <- function(e1, e2) UseMethod("%le%")
-#' @describeIn proportional Tests proportion of dates in the first vector
+#' @describeIn operate_proportional Tests proportion of dates in the first vector
#' that precede or are equal to the minimum in the second vector.
#' @export
#' @examples
@@ -99,11 +99,11 @@ evalqOnLoad({
registerS3method("%le%", "POSIXt", `%le%.mdate`)
})
-#' @rdname proportional
+#' @rdname operate_proportional
#' @export
`%><%` <- function(e1, e2) UseMethod("%><%")
-#' @describeIn proportional Tests proportion of dates in the first vector
+#' @describeIn operate_proportional Tests proportion of dates in the first vector
#' that are between the minimum and maximum dates in the second vector.
#' @export
#' @examples
@@ -123,11 +123,11 @@ evalqOnLoad({
registerS3method("%><%", "POSIXt", `%><%.mdate`)
})
-#' @rdname proportional
+#' @rdname operate_proportional
#' @export
`%>=<%` <- function(e1, e2) UseMethod("%>=<%")
-#' @describeIn proportional Tests proportion of dates in the first vector that
+#' @describeIn operate_proportional Tests proportion of dates in the first vector that
#' are between the minimum and maximum dates in the second vector, inclusive.
#' @export
#' @examples
diff --git a/R/set.R b/R/operate_set.R
similarity index 68%
rename from R/set.R
rename to R/operate_set.R
index c3de04e6..0e87f27d 100644
--- a/R/set.R
+++ b/R/operate_set.R
@@ -1,20 +1,20 @@
#' Set operations for messy dates
-#'
-#' Performs intersection (`md_intersect()`) and union (`md_union()`) on,
-#' inter alia, messy date class objects.
-#' For a more typical 'join' that retains all elements, even if duplicated,
-#' please use `md_multiset`.
-#' @name set
+#' @description
+#' Performs intersection (`md_intersect()`) and union (`md_union()`) on,
+#' inter alia, messy date class objects.
+#' For a more typical 'join' that retains all elements, even if duplicated,
+#' please use `md_multiset`.
+#' @name operate_set
#' @param e1,e2 Messy date or other class objects
#' @return A vector of the same mode for `intersect`,
-#' or a common mode for union.
+#' or a common mode for union.
NULL
-#' @rdname set
+#' @rdname operate_set
#' @export
`%intersect%` <- function(e1, e2) UseMethod("%intersect%")
-#' @describeIn set Find intersection of sets of messy dates
+#' @describeIn operate_set Find intersection of sets of messy dates
#' @examples
#' as_messydate("2012-01-01..2012-01-20") %intersect% as_messydate("2012-01")
#' @export
@@ -29,11 +29,11 @@ evalqOnLoad({
registerS3method("%intersect%", "POSIXt", `%intersect%.mdate`)
})
-#' @rdname set
+#' @rdname operate_set
#' @export
`%union%` <- function(e1, e2) UseMethod("%union%")
-#' @describeIn set Find intersection of sets of messy dates
+#' @describeIn operate_set Find intersection of sets of messy dates
#' @examples
#' as_messydate("2012-01-01..2012-01-20") %union% as_messydate("2012-01")
#' @export
diff --git a/R/operate_statements.R b/R/operate_statements.R
new file mode 100644
index 00000000..9b29e58c
--- /dev/null
+++ b/R/operate_statements.R
@@ -0,0 +1,92 @@
+# Statements ####
+
+#' Logical statements on messy dates
+#' @description
+#' These functions provide various logical statements about messy date objects.
+#' @name operate_statements
+#' @param x,y `mdate` or other class objects
+#' @return A logical vector the same length as the `mdate` passed.
+NULL
+
+#' @describeIn operate_statements tests whether the object inherits the `mdate` class.
+#' If more rigorous validation is required, see `validate_messydate()`.
+#' @examples
+#' is_messydate(as_messydate("2012-01-01"))
+#' is_messydate(as.Date("2012-01-01"))
+#' @export
+is_messydate <- function(x) {
+ inherits(x, "mdate")
+}
+
+#' @describeIn operate_statements tests whether there is any intersection between
+#' two messy dates, leveraging `intersect()`.
+#' @examples
+#' is_intersecting(as_messydate("2012-01"),
+#' as_messydate("2012-01-01..2012-02-22"))
+#' is_intersecting(as_messydate("2012-01"),
+#' as_messydate("2012-02-01..2012-02-22"))
+#' @export
+is_intersecting <- function(x, y) {
+ length(intersect(unlist(expand(x)), unlist(expand(y)))) > 0
+}
+
+#' @describeIn operate_statements tests whether one or more messy date can be found
+#' within a messy date range or set.
+#' @examples
+#' is_subset(as_messydate("2012-01-01"), as_messydate("2012-01"))
+#' is_subset(as_messydate("2012-01-01..2012-01-03"), as_messydate("2012-01"))
+#' is_subset(as_messydate("2012-01-01"), as_messydate("2012-02"))
+#' @export
+is_subset <- function(x, y) {
+ x <- as.character(expand(x)[[1]])
+ y <- as.character(expand(y)[[1]])
+ any(is.element(x, y))
+}
+
+#' @describeIn operate_statements tests whether two dates contain similar components.
+#' This can be useful for identifying dates that may be typos of one another.
+#' @examples
+#' is_similar(as_messydate("2012-06-02"), as_messydate("2012-02-06"))
+#' is_similar(as_messydate("2012-06-22"), as_messydate("2012-02-06"))
+#' @export
+is_similar <- function(x, y) {
+ year(x) == year(y) & month(x) == day(y) & day(x) == month(y)
+}
+
+#' @describeIn operate_statements tests whether a date is precise (i.e. an 8 digit date).
+#' Non-precise dates contain markers that they are approximate (i.e. ~),
+#' unreliable (i.e. ?), are incomplete dates (i.e. year only),
+#' or date ranges and sets.
+#' @examples
+#' is_precise(as_messydate(c("2012-06-02", "2012-06")))
+#' @export
+is_precise <- function(x) {
+ stringi::stri_detect_regex(x, "^[:digit:]{4}-[:digit:]{2}-[:digit:]{2}$|
+ |^-[:digit:]{4}-[:digit:]{2}-[:digit:]{2}$")
+}
+
+#' @describeIn operate_statements tests whether a date is uncertain (i.e. contains ?).
+#' @examples
+#' is_uncertain(as_messydate(c("2012-06-02", "2012-06-02?")))
+#' @export
+is_uncertain <- function(x) {
+ stringi::stri_detect_regex(x, "\\?|\\%")
+}
+
+#' @describeIn operate_statements tests whether a date is approximate (i.e. contains ~).
+#' @examples
+#' is_approximate(as_messydate(c("2012-06-02~", "2012-06-02")))
+#' @export
+is_approximate <- function(x) {
+ stringi::stri_detect_regex(x, "\\~|\\%")
+}
+
+#' @describeIn operate_statements tests whether one or more messy dates are found
+#' before the common era.
+#' @examples
+#' is_bce(as_messydate(c("2012-06-02", "-2012-06-02")))
+#' @export
+is_bce <- function(x) {
+ stringi::stri_detect_regex(x, "^-")
+}
+
diff --git a/R/report.R b/R/report.R
deleted file mode 100644
index bd83a8ef..00000000
--- a/R/report.R
+++ /dev/null
@@ -1,73 +0,0 @@
-#' Data report for datasets with 'mdate' variables
-#'
-#' Create a properly formatted data report for datasets which contain 'mdate'
-#' class objects, alongside other object classes.
-#' @param data A `{tibble}` or a `{data.frame}`.
-#' @return A data report of class 'mreport'.
-#' @importFrom dplyr %>%
-#' @importFrom stats na.omit
-#' @details 'mreport' displays the variable's name,
-#' the variable type, the number of observations per variable,
-#' the number of missing observations for variable,
-#' and the percentage of missing observations in variable.
-#' @examples
-#' mreport(battles)
-#' @export
-mreport <- function(data) UseMethod("mreport")
-
-#' @export
-mreport.default <- function(data) {
- if (!is.data.frame(data)) {
- stop("Data must be a `data.frame` or `tibble`.")
- }
- rows <- nrow(data)
- cols <- ncol(data)
- varnames <- names(data)
- datatype <- unlist(lapply(data, class))
- counts <- unlist(lapply(data, length))
- mvalues <- unlist(lapply(data, function(z) sum(is.na(z))))
- mvaluesper <- round((mvalues / counts) * 100, 2)
- result <- list(Rows = rows,
- Columns = cols,
- Variables = varnames,
- Types = datatype,
- Count = counts,
- Missing = mvalues,
- MissingPer = mvaluesper)
- class(result) <- "mreport"
- return(result)
-}
-
-#' @export
-print.mreport <- function(x, ...) {
- columns <- c(" Column Name ", " Data Type ", " Observations ",
- " Missing ", " Missing (%) ")
- len_col <- as.vector(unlist(lapply(columns, nchar)))
- x$Types <- lapply(x$Types, paste, collapse = ", ")
- lengths <- list(x$Variables, x$Types, x$Count, x$Missing, x$MissingPer)
- n <- length(columns)
- nlist <- list()
- for (i in seq_len(n)) {
- nlist[[i]] <- max(len_col[i], max(unlist(lapply(lengths[[i]], nchar))))
- }
- clengths <- unlist(nlist)
- dash <- sum(clengths) + 6
- cat(rep("-", dash), sep = "")
- cat("\n|")
- for (i in seq_len(n)) {
- cat(format(columns[i], width = clengths[i], justify = "centre"),
- "|", sep = "")
- }
- cat("\n", rep("-", dash), sep = "")
- cat("\n")
- for (i in seq_len(x$Columns)) {
- cat("|", format(x$Variables[i], width = clengths[1], justify = "centre"), "|",
- format(x$Types[i], width = clengths[2], justify = "centre"), "|",
- format(x$Count[i], width = clengths[3], justify = "centre"), "|",
- format(as.character(x$Missing[i]), width = clengths[4], justify = "centre"), "|",
- format(as.character(x$MissingPer[i]), width = clengths[5], justify = "centre"),
- "|\n", sep = "")
- }
- cat(rep("-", dash), sep = "")
- cat("\n\n")
-}
diff --git a/README.Rmd b/README.Rmd
index 96b15820..6553642b 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -3,7 +3,7 @@ output: github_document
always_allow_html: true
---
-# messydates
+# messydates
```{r, include = FALSE}
knitr::opts_chunk$set(
@@ -179,7 +179,7 @@ allowing researchers to use messy dates in an analytic strategy that uses any ot
Please see the cheat sheet and [the messydates website](https://globalgov.github.io/messydates/) for more information about
how to use `{messydates}`.
-
+
## Installation
diff --git a/README.md b/README.md
index b95c1500..55729e4c 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-# messydates
+# messydates
@@ -413,13 +413,13 @@ max
2012-01-01
+
## Installation
diff --git a/cran-comments.md b/cran-comments.md
index 295e9141..8e77fc07 100644
--- a/cran-comments.md
+++ b/cran-comments.md
@@ -8,3 +8,6 @@
## R CMD check results
0 errors | 0 warnings | 0 notes
+
+- Fixed redirected URL issue from previous submission
+- Fixed most reverse dependency issues, though some changes in manydata will follow
diff --git a/man/battles.Rd b/man/battles.Rd
index e733f534..b8c39a11 100644
--- a/man/battles.Rd
+++ b/man/battles.Rd
@@ -21,16 +21,4 @@ battles
A dataset containing the names and dates of battles in 2001,
according to Wikipedia (https://en.wikipedia.org/wiki/List_of_battles_in_the_21st_century).
}
-\details{
-\if{html}{\out{