aweek/0000755000176200001440000000000014317444553011360 5ustar liggesusersaweek/NAMESPACE0000644000176200001440000000126114053270040012560 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method("[",aweek) S3method("[<-",aweek) S3method("[[",aweek) S3method(as.Date,aweek) S3method(as.POSIXlt,aweek) S3method(as.aweek,"NULL") S3method(as.aweek,Date) S3method(as.aweek,POSIXt) S3method(as.aweek,aweek) S3method(as.aweek,character) S3method(as.aweek,default) S3method(as.aweek,factor) S3method(as.character,aweek) S3method(as.data.frame,aweek) S3method(as.list,aweek) S3method(c,aweek) S3method(print,aweek) S3method(rep,aweek) S3method(trunc,aweek) export(as.aweek) export(change_week_start) export(date2week) export(factor_aweek) export(get_aweek) export(get_date) export(get_week_start) export(set_week_start) export(week2date) aweek/LICENSE0000644000176200001440000000005514053270040012346 0ustar liggesusersYEAR: 2019 COPYRIGHT HOLDER: Zhian N. Kamvar aweek/man/0000755000176200001440000000000014053270040012114 5ustar liggesusersaweek/man/factor_aweek.Rd0000644000176200001440000000136614053270040015043 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/factor_aweek.R \name{factor_aweek} \alias{factor_aweek} \title{Coerce an aweek object to factor to include missing weeks} \usage{ factor_aweek(x) } \arguments{ \item{x}{an aweek object} } \value{ an aweek object that inherits from \code{\link[=factor]{factor()}} with levels that span the range of the weeks in the object. } \description{ Coerce an aweek object to factor to include missing weeks } \note{ when factored aweek objects are combined with other aweek objects, they are converted back to characters. } \examples{ w <- get_aweek(week = (1:2) * 5, year = 2019, day = c(7, 1)) w wf <- factor_aweek(w) wf # factors are destroyed if combined with aweek objects c(w, wf) } aweek/man/as.data.frame.aweek.Rd0000644000176200001440000000116014053270040016100 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/as.data.frame.aweek.R \name{as.data.frame.aweek} \alias{as.data.frame.aweek} \title{Convert aweek objects to a data frame} \usage{ \method{as.data.frame}{aweek}(x, ...) } \arguments{ \item{x}{an aweek object} \item{...}{unused} } \value{ a data frame with an aweek column } \description{ Convert aweek objects to a data frame } \examples{ d <- as.Date("2019-03-25") + 0:6 w <- date2week(d, "Sunday") dw <- data.frame(date = d, week = w) dw dw$week } \seealso{ \code{\link[=date2week]{date2week()}} \code{\link[=print.aweek]{print.aweek()}} } aweek/man/week_start.Rd0000644000176200001440000000303414053270040014553 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/week_start.R \name{set_week_start} \alias{set_week_start} \alias{get_week_start} \title{Get and set the global week_start variable} \usage{ set_week_start(x = 1L) get_week_start(w = NULL) } \arguments{ \item{x}{a character or integer specifying the day of the week for conversion between dates and weeks.} \item{w}{if \code{NULL}, the global option "aweek.week_start" is returned. If \code{w} is an aweek object, then the "week_start" attribute is returned.} } \value{ for \code{set_week_start}, the old value of \code{week_start} is returned, invisibly. For \code{get_week_start}, the current value of \code{week_start} is returned. } \description{ This is a convenience wrapper around \code{\link[=options]{options()}} and \code{\link[=getOption]{getOption()}}, which allows users to input both numeric and character week start values } \examples{ # get the current definition of the week start get_week_start() # defaults to Monday (1) getOption("aweek.week_start", 1L) # identical to above # set the week start mon <- set_week_start("Sunday") # set week start to Sunday (7) get_week_start() print(set_week_start(mon)) # reset the default get_week_start() # Get the week_start of a specific aweek object. w <- date2week("2019-05-04", week_start = "Sunday") get_week_start(w) } \seealso{ \code{\link[=change_week_start]{change_week_start()}} for changing the week_start attribute of an aweek object, \code{\link[=date2week]{date2week()}}, \code{\link[=week2date]{week2date()}} } aweek/man/as.aweek.Rd0000644000176200001440000001014314053270040014100 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/as.aweek.R \name{as.aweek} \alias{as.aweek} \alias{as.aweek.default} \alias{as.aweek.NULL} \alias{as.aweek.character} \alias{as.aweek.factor} \alias{as.aweek.Date} \alias{as.aweek.POSIXt} \alias{as.aweek.aweek} \title{Convert characters or dates to aweek objects} \usage{ as.aweek(x, week_start = get_week_start(), ...) \method{as.aweek}{default}(x, week_start = NULL, ...) \method{as.aweek}{`NULL`}(x, week_start = NULL, ...) \method{as.aweek}{character}(x, week_start = get_week_start(), start = week_start, ...) \method{as.aweek}{factor}(x, week_start = get_week_start(), ...) \method{as.aweek}{Date}(x, week_start = get_week_start(), ...) \method{as.aweek}{POSIXt}(x, week_start = get_week_start(), ...) \method{as.aweek}{aweek}(x, week_start = NULL, ...) } \arguments{ \item{x}{a \link{Date}, \link{POSIXct}, \link{POSIXlt}, or a correctly formatted (YYYY-Www-d) character string that represents the year, week, and weekday.} \item{week_start}{a number indicating the start of the week based on the ISO 8601 standard from 1 to 7 where 1 = Monday OR an abbreviation of the weekdate in an English or current locale. \emph{Note: using a non-English locale may render your code non-portable.} Defaults to the value of \code{\link[=get_week_start]{get_week_start()}}} \item{...}{arguments passed on to \code{\link[=date2week]{date2week()}} and \code{\link[=as.POSIXlt]{as.POSIXlt()}}} \item{start}{an integer (or character) vector of days that the weeks start on for each corresponding week. Defaults to the value of \code{\link[=get_week_start]{get_week_start()}}. Note that these will not determine the final week.} } \value{ an \link[=aweek-class]{aweek} object } \description{ Convert characters or dates to aweek objects } \details{ The \code{as.aweek()} will coerce character, dates, and datetime objects to aweek objects. Dates are trivial to convert to weeks because there is only one correct way to convert them with any given \code{week_start}. There is a bit of nuance to be aware of when converting characters to aweek objects: \itemize{ \item The characters must be correctly formatted as \code{YYYY-Www-d}, where YYYY is the year relative to the week, Www is the week number (ww) prepended by a W, and d (optional) is the day of the week from 1 to 7 where 1 represents the week_start. This means that characters formatted as dates will be rejected. \item By default, the \code{week_start} and \code{start} parameters are identical. If your data contains heterogeneous weeks (e.g. some dates will have the week start on Monday and some will have the week start on Sunday), then you should use the \code{start} parameter to reflect this. Internally, the weeks will first be converted to dates with their respective starts and then converted back to weeks, unified under the \code{week_start} parameter. } } \note{ factors are first converted to characters before they are converted to aweek objects. } \examples{ # aweek objects can only be created from valid weeks: as.aweek("2018-W10-5", week_start = 7) # works! try(as.aweek("2018-10-5", week_start = 7)) # doesn't work :( # you can also convert dates or datetimes as.aweek(Sys.Date()) as.aweek(Sys.time()) # all functions get passed to date2week, so you can use any of its arguments: as.aweek("2018-W10-5", week_start = 7, floor_day = TRUE, factor = TRUE) as.aweek(as.Date("2018-03-09"), floor_day = TRUE, factor = TRUE) # If you have a character vector where different elements begin on different # days of the week, you can use the "start" argument to ensure they are # correctly converted. as.aweek(c(mon = "2018-W10-1", tue = "2018-W10-1"), week_start = "Monday", start = c("Monday", "Tuesday")) # you can convert aweek objects to aweek objects: x <- get_aweek() as.aweek(x) as.aweek(x, week_start = 7) } \seealso{ \link[=aweek-class]{"aweek-class"} for details on the aweek object, \code{\link[=get_aweek]{get_aweek()}} for converting numeric weeks to weeks or dates, \code{\link[=date2week]{date2week()}} for converting dates to weeks, \code{\link[=week2date]{week2date()}} for converting weeks to dates. } aweek/man/date2week.Rd0000644000176200001440000001311614317406465014277 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/date2week.R, R/week2date.R \name{date2week} \alias{date2week} \alias{week2date} \title{Convert date to a an arbitrary week definition} \usage{ date2week( x, week_start = get_week_start(), floor_day = factor, numeric = FALSE, factor = FALSE, ... ) week2date(x, week_start = get_week_start(), floor_day = FALSE) } \arguments{ \item{x}{a \link{Date}, \link{POSIXt}, \link{character}, or any data that can be easily converted to a date with \code{\link[=as.POSIXlt]{as.POSIXlt()}}.} \item{week_start}{a number indicating the start of the week based on the ISO 8601 standard from 1 to 7 where 1 = Monday OR an abbreviation of the weekdate in an English or current locale. \emph{Note: using a non-English locale may render your code non-portable.} Defaults to the value of \code{\link[=get_week_start]{get_week_start()}}} \item{floor_day}{when \code{TRUE}, the days will be set to the start of the week.} \item{numeric}{if \code{TRUE}, only the numeric week be returned. If \code{FALSE} (default), the date in the format "YYYY-Www-d" will be returned.} \item{factor}{if \code{TRUE}, a factor will be returned with levels spanning the range of dates. This should only be used with \code{floor_day = TRUE} to produce the sequence of weeks between the first and last date as the factor levels. Currently, \code{floor_date = FALSE} will still work, but will produce a message indicating that it is deprecated. \emph{Take caution when using this with a large date range as the resulting factor can contain all days between dates}.} \item{...}{arguments passed to \code{\link[=as.POSIXlt]{as.POSIXlt()}}, unused in all other cases.} } \value{ \itemize{ \item \code{date2week()} an \link[=aweek-class]{aweek} object which represents dates in \code{YYYY-Www-d} format where \code{YYYY} is the year (associated with the week, not necessarily the day), \code{Www} is the week number prepended by a "W" that ranges from 01-53 and \code{d} is the day of the week from 1 to 7 where 1 represents the first day of the week (as defined by the \code{week_start} attribute). \item \code{week2date()} a \link{Date} object. } } \description{ Convert date to a an arbitrary week definition } \details{ Weeks differ in their start dates depending on context. The ISO 8601 standard specifies that Monday starts the week (\url{https://en.wikipedia.org/wiki/ISO_week_date}) while the US CDC uses Sunday as the start of the week (\url{https://stacks.cdc.gov/view/cdc/22305}). For example, MSF has varying start dates depending on country in order to better coordinate response. While there are packages that provide conversion for ISOweeks and epiweeks, these do not provide seamless conversion from dates to epiweeks with non-standard start dates. This package provides a lightweight utility to be able to convert each day. } \note{ \code{date2week()} will initially convert the input with \code{\link[=as.POSIXlt]{as.POSIXlt()}} and use that to calculate the week. If the user supplies character input, it is expected that the input will be of the format yyyy-mm-dd \emph{unless} the user explicitly passes the "format" parameter to \code{\link[=as.POSIXlt]{as.POSIXlt()}}. If the input is not in yyyy-mm-dd and the format parameter is not passed, \code{date2week()} will result in an error. } \examples{ ## Dates to weeks ----------------------------------------------------------- # The same set of days will occur in different weeks depending on the start # date. Here we can define a week before and after today print(dat <- as.Date("2018-12-31") + -6:7) # By default, the weeks are defined as ISO weeks, which start on Monday print(iso_dat <- date2week(dat)) # This can be changed by setting the global default with set_week_start() set_week_start("Sunday") date2week(dat) # If you want lubridate-style numeric-only weeks, you need look no further # than the "numeric" argument date2week(dat, numeric = TRUE) # To aggregate weeks, you can use `floor_day = TRUE` date2week(dat, floor_day = TRUE) # If you want aggregations into factors that include missing weeks, use # `floor_day = TRUE, factor = TRUE`: date2week(dat[c(1, 14)], floor_day = TRUE, factor = TRUE) ## Weeks to dates ----------------------------------------------------------- # The aweek class can be converted back to a date with `as.Date()` as.Date(iso_dat) # If you don't have an aweek class, you can use week2date(). Note that the # week_start variable is set by the "aweek.week_start" option, which we will # set to Monday: set_week_start("Monday") week2date("2019-W01-1") # 2018-12-31 # This can be overidden by the week_start argument; week2date("2019-W01-1", week_start = "Sunday") # 2018-12-30 # If you want to convert to the first day of the week, you can use the # `floor_day` argument as.Date(iso_dat, floor_day = TRUE) ## The same two week timespan starting on different days -------------------- # ISO week definition: Monday -- 1 date2week(dat, 1) date2week(dat, "Monday") # Tuesday -- 2 date2week(dat, 2) date2week(dat, "Tuesday") # Wednesday -- 3 date2week(dat, 3) date2week(dat, "W") # you can use valid abbreviations # Thursday -- 4 date2week(dat, 4) date2week(dat, "Thursday") # Friday -- 5 date2week(dat, 5) date2week(dat, "Friday") # Saturday -- 6 date2week(dat, 6) date2week(dat, "Saturday") # Epiweek definition: Sunday -- 7 date2week(dat, 7) date2week(dat, "Sunday") } \seealso{ \code{\link[=set_week_start]{set_week_start()}}, \code{\link[=as.Date.aweek]{as.Date.aweek()}}, \code{\link[=print.aweek]{print.aweek()}}, \code{\link[=as.aweek]{as.aweek()}}, \code{\link[=get_aweek]{get_aweek()}} } \author{ Zhian N. Kamvar } aweek/man/change_week_start.Rd0000644000176200001440000000332214053270040016060 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/change_week_start.R \name{change_week_start} \alias{change_week_start} \title{Change the week start of an aweek object} \usage{ change_week_start(x, week_start = NULL, ...) } \arguments{ \item{x}{a \link{Date}, \link{POSIXt}, \link{character}, or any data that can be easily converted to a date with \code{\link[=as.POSIXlt]{as.POSIXlt()}}.} \item{week_start}{a number indicating the start of the week based on the ISO 8601 standard from 1 to 7 where 1 = Monday OR an abbreviation of the weekdate in an English or current locale. \emph{Note: using a non-English locale may render your code non-portable.} Unlike \code{\link[=date2week]{date2week()}}, this defaults to NULL, which will throw an error unless you supply a value.} \item{...}{arguments passed to \code{\link[=as.POSIXlt]{as.POSIXlt()}}, unused in all other cases.} } \description{ This will change the week_start attribute of an aweek object and adjust the observations accordingly. } \examples{ # New Year's 2019 is the third day of the week starting on a Sunday s <- date2week(as.Date("2019-01-01"), week_start = "Sunday") s # It's the second day of the week starting on a Monday m <- change_week_start(s, "Monday") m # When you compare the underlying dates, they are exactly the same identical(as.Date(s), as.Date(m)) # Since this will pass arguments to `date2week()`, you can modify other # aspects of the aweek object this way, but this is not advised. change_week_start(s, "Monday", floor_day = TRUE) } \seealso{ \code{\link[=get_week_start]{get_week_start()}} for accessing the global and local \code{week_start} attribute, \code{\link[=as.aweek]{as.aweek()}}, which wraps this function. } aweek/man/aweek-package.Rd0000644000176200001440000001047714053270040015101 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/aweek-package.R \docType{package} \name{aweek-package} \alias{aweek} \alias{aweek-package} \title{Convert dates to weeks and back again} \description{ The aweek package is a lightweight solution for converting dates to weeks that can start on any weekday. It implements the \link[=aweek-class]{aweek class}, which can easily be converted to date and weeks that start on different days. } \section{Before you begin}{ When you work with aweek, you will want to make sure that you set the default \code{week_start} variable to indicate which day of the week your weeks should begin. This can be done with \code{\link[=set_week_start]{set_week_start()}}. It will ensure that all of your weeks will begin on the same day. \itemize{ \item \code{\link[=get_week_start]{get_week_start()}} returns the global week_start option \item \code{\link[=set_week_start]{set_week_start()}} sets the global week_start option } } \section{Conversions}{ \subsection{Dates to weeks}{ This conversion is the simplest because dates are unambiguous. \itemize{ \item \code{\link[=date2week]{date2week()}} converts dates, datetimes, and characters that look like dates to weeks \item \code{\link[=as.aweek]{as.aweek()}} is a wrapper around \code{\link[=date2week]{date2week()}} that converts dates and datetimes } } \subsection{Week numbers to weeks or dates}{ If you have separate columns for week numbers and years, then this is the option for you. This allows you to specify a different start for each week element using the \code{start} argument. \itemize{ \item \code{\link[=get_aweek]{get_aweek()}} converts week numbers (with years and days) to \link[=aweek-class]{aweek objects}. \item \code{\link[=get_date]{get_date()}} converts week numbers (with years and days) to \link[=Date]{Dates}. } } \subsection{ISO week strings (YYYY-Www-d or YYYY-Www) to weeks or dates}{ \itemize{ \item \code{\link[=as.aweek]{as.aweek()}} converts ISO-week formatted strings to \link[=aweek-class]{aweek objects}. \item \code{\link[=week2date]{week2date()}} converts ISO-week formatted strings to \link{Date}. } } \subsection{aweek objects to dates or datetimes}{ This conversion is simple for \link[=aweek-class]{aweek} objects since their week_start is unambiguous \itemize{ \item \link[=as.Date.aweek]{as.Date()} converts to \link{Date}. \item \link[=as.POSIXlt.aweek]{as.POSIXlt()} converts to \link{POSIXlt}. } } \subsection{aweek objects to characters}{ You can strip the week_start attribute of the aweek object by converting to a character with \code{\link[=as.character]{as.character()}} } } \section{Manipulating aweek objects}{ \itemize{ \item \code{\link[=trunc]{trunc()}} removes the weekday element of the ISO week string. \item \code{\link[=factor_aweek]{factor_aweek()}} does the same thing as trunc(), but will create a factor with levels spanning all the weeks from the first week to the last week. Useful for creating tables with zero counts for unobserved weeks. \item \code{\link[=change_week_start]{change_week_start()}} will change the week_start attribute and adjust the weeks accordingly so that the dates will always be consistent. } When you combine aweek objects, they must have the same week_start attribute. Characters can be added to aweek objects as long as they are in ISO week format and you can safely assume that they start on the same weekday. Dates are trivial to add to aweek objects. See the \linkS4class{aweek} documentation for details. } \examples{ # At the beginning of your analysis, set the week start to the weeks you want # to use for reporting ow <- set_week_start("Sunday") # convert dates to weeks d <- as.Date(c("2014-02-11", "2014-03-04")) w <- as.aweek(d) w # get the week numbers date2week(d, numeric = TRUE) # convert back to date as.Date(w) # convert to factor factor_aweek(w) # append a week w[3] <- as.Date("2014-10-31") w # change week start variable (if needed) change_week_start(w, "Monday") # note that the date remains the same as.Date(change_week_start(w, "Monday")) # Don't forget to reset the week_start at the end set_week_start(ow) } \seealso{ Useful links: \itemize{ \item \url{https://www.repidemicsconsortium.org/aweek/} \item Report bugs at \url{https://github.com/reconhub/aweek/issues/} } } \author{ \strong{Maintainer}: Zhian N. Kamvar \email{zkamvar@gmail.com} } aweek/man/aweek-class.Rd0000644000176200001440000001213314317406465014621 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/aweek-methods.R \name{print.aweek} \alias{print.aweek} \alias{aweek-class} \alias{[.aweek} \alias{[[.aweek} \alias{[<-.aweek} \alias{as.list.aweek} \alias{trunc.aweek} \alias{rep.aweek} \alias{c.aweek} \title{The aweek class} \usage{ \method{print}{aweek}(x, ...) \method{[}{aweek}(x, i) \method{[[}{aweek}(x, i) \method{[}{aweek}(x, i) <- value \method{as.list}{aweek}(x, ...) \method{trunc}{aweek}(x, ...) \method{rep}{aweek}(x, ...) \method{c}{aweek}(..., recursive = FALSE, use.names = TRUE) } \arguments{ \item{x}{an object of class \code{aweek}} \item{...}{a series of \code{aweek} objects, characters, or Dates, (unused in \code{print.aweek()})} \item{i}{index for subsetting an aweek object.} \item{value}{a value to add or replace in an aweek object} \item{recursive, use.names}{parameters passed on to \code{\link[=unlist]{unlist()}}} } \value{ an object of class \code{aweek} } \description{ The aweek class is a character or factor in the format YYYY-Www(-d) with a "week_start" attribute containing an integer specifying which day of the ISO 8601 week each week should begin. } \details{ Weeks differ in their start dates depending on context. The ISO 8601 standard specifies that Monday starts the week (\url{https://en.wikipedia.org/wiki/ISO_week_date}) while the US CDC uses Sunday as the start of the week (\url{https://stacks.cdc.gov/view/cdc/22305}). For example, MSF has varying start dates depending on country in order to better coordinate response. While there are packages that provide conversion for ISOweeks and epiweeks, these do not provide seamless conversion from dates to epiweeks with non-standard start dates. This package provides a lightweight utility to be able to convert each day. \subsection{Calculation of week numbers}{ Week numbers are calculated in three steps: \enumerate{ \item Find the day of the week, relative to the week_start (d). The day of the week (d) relative to the week start (s) is calculated using the ISO week day (i) via \code{d = 1L + ((i + (7L - s)) \%\% 7L)}. \item Find the date that represents midweek (m). The date that represents midweek is found by subtracting the day of the week (d) from 4 and adding that number of days to the current date: \code{m = date + (4 - d)}. \item Find the week number (w) by counting the number of days since 1 January to (m), and use integer division by 7: \code{w = 1L + ((m - yyyy-01-01) \%/\% 7)} } For the weeks around 1 January, the year is determined by the week number. If the month is January, but the week number is 52 or 53, then the year for the week (YYYY) is the calendar year (yyyy) minus 1. However, if the month is December, but the week number is 1, then the year for the week (YYYY) is the calendar year (yyyy) plus 1. } \subsection{Structure of the aweek object}{ The aweek object is a character vector in either the precise ISO week format (YYYY-Www-d) or imprecise ISO week format (YYYY-Www) with a \code{week_start} attribute indicating which ISO week day the week begins. The precise ISO week format can be broken down like this: \itemize{ \item \strong{YYYY} is an ISO week-numbering year, which is the year relative to the week, not the day. For example, the date 2016-01-01 would be represented as 2015-W53-5 (ISO week), because while the date is in the year 2016, the week is still part of the final week of 2015. \item W\strong{ww} is the week number, prefixed by the character "W". This ranges from 01 to 52 or 53, depending on whether or not the year has 52 or 53 weeks. \item \strong{d} is a digit representing the weekday where 1 represents the first day of the week and 7 represents the last day of the week. #' The attribute \code{week_start} represents the first day of the week as an ISO week day. This defaults to 1, which is Monday. If, for example, an aweek object represented weeks starting on Friday, then the \code{week_start} attribute would be 5, which is Friday of the ISO week. } Imprecise formats (YYYY-Www) are equivalent to the first day of the week. For example, 2015-W53 and 2015-W53-1 will be identical when converted to date. } } \note{ when combining aweek objects together, you must ensure that they have the same week_start attribute. You can use \code{\link[=change_week_start]{change_week_start()}} to adjust it. } \examples{ d <- as.Date("2018-12-20") + 1:40 w <- date2week(d, week_start = "Sunday") print(w) # subsetting acts as normal w[1:10] # Combining multiple aweek objects will only work if they have the same # week_start day c(w[1], w[3], w[5], as.aweek(as.Date("2018-12-01"), week_start = "Sunday")) # differing week_start days will throw an error mon <- date2week(as.Date("2018-12-01"), week_start = "Monday") mon try(c(w, mon)) # combining Dates will be coerced to aweek objects under the same rules c(w, Sys.Date()) # truncated aweek objects will be un-truncated w2 <- date2week(d[1:5], week_start = "Sunday", floor_day = TRUE) w2 c(w[1:5], w2) } \seealso{ \code{\link[=date2week]{date2week()}}, \code{\link[=get_aweek]{get_aweek()}}, \code{\link[=as.Date.aweek]{as.Date.aweek()}}, \code{\link[=change_week_start]{change_week_start()}} } aweek/man/get_aweek.Rd0000644000176200001440000000663014053270040014343 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/get_aweek.R \name{get_aweek} \alias{get_aweek} \alias{get_date} \title{Convert week numbers to dates or aweek objects} \usage{ get_aweek( week = 1L, year = format(Sys.Date(), "\%Y"), day = 1L, start = week_start, week_start = get_week_start(), ... ) get_date( week = 1L, year = format(Sys.Date(), "\%Y"), day = 1L, start = get_week_start() ) } \arguments{ \item{week}{an integer vector, defaults to 1, representing the first week of the year.} \item{year}{an integer vector, defaults to the current year} \item{day}{an integer vector, defaults to 1, representing the first day of the first week of the year.} \item{start}{an integer (or character) vector of days that the weeks start on for each corresponding week. Defaults to the value of \code{\link[=get_week_start]{get_week_start()}}. Note that these will not determine the final week.} \item{week_start}{a number indicating the start of the week based on the ISO 8601 standard from 1 to 7 where 1 = Monday OR an abbreviation of the weekdate in an English or current locale. \emph{Note: using a non-English locale may render your code non-portable.} Defaults to the value of \code{\link[=get_week_start]{get_week_start()}}} \item{...}{parameters passed on to \code{\link[=date2week]{date2week()}}} } \value{ \itemize{ \item get_aweek(): an aweek object \item get_date(): a Date object } } \description{ These are vectorized functions that take integer vectors and return Date or an aweek objects, making it easier to convert bare weeks to dates. } \note{ Any missing weeks, years, or start elements will result in a missing element in the resulting vector. Any missing days will revert to the first day of the week. } \examples{ # The default results in the first week of the year using the default # default week_start (from get_week_start()) get_aweek() get_date() # this is equivalent to as.Date(get_week()), but faster # Some years, like 2015, have 53 weeks get_aweek(53, 2015) # If you specify 53 weeks for a year that doesn't have 53 weeks, aweek will # happily correct it for you get_aweek(53, 2014) # this will be 2015-W01-1 # you can use this to quickly make a week without worrying about formatting # here, you can define an observation interval of 20 weeks obs_start <- get_date(week = 10, year = 2018) obs_end <- get_date(week = 29, year = 2018, day = 7) c(obs_start, obs_end) # If you have a data frame of weeks, you can use it to convert easily mat <- matrix(c( 2019, 11, 1, 7, # 2019-03-10 2019, 11, 2, 7, 2019, 11, 3, 7, 2019, 11, 4, 7, 2019, 11, 5, 7, 2019, 11, 6, 7, 2019, 11, 7, 7 ), ncol = 4, byrow = TRUE) colnames(mat) <- c("year", "week", "day", "start") m <- as.data.frame(mat) m sun <- with(m, get_date(week, year, day, start)) sun as.aweek(sun) # convert to aweek starting on the global week_start as.aweek(sun, week_start = "Sunday") # convert to aweek starting on Sunday # You can also change starts mon <- with(m, get_aweek(week, year, day, "Monday", week_start = "Monday")) mon as.Date(mon) # If you use multiple week starts, it will convert to date and then to # the correct week, so it won't appear to match up with the original # data frame. sft <- with(m, get_aweek(week, year, day, 7:1, week_start = "Sunday")) sft as.Date(sft) } \seealso{ \code{\link[=as.aweek]{as.aweek()}} \code{\link[=date2week]{date2week()}} \code{\link[=week2date]{week2date()}} } aweek/man/aweek-conversions.Rd0000644000176200001440000000216614053270040016052 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/as.Date.aweek.R, R/conversions.R \name{as.Date.aweek} \alias{as.Date.aweek} \alias{as.POSIXlt.aweek} \alias{as.character.aweek} \title{Convert aweek objects to characters or dates} \usage{ \method{as.Date}{aweek}(x, floor_day = FALSE, ...) \method{as.POSIXlt}{aweek}(x, tz = "", floor_day = FALSE, ...) \method{as.character}{aweek}(x, ...) } \arguments{ \item{x}{an object of class \link[=print.aweek]{aweek}.} \item{floor_day}{when \code{TRUE}, the days will be set to the start of the week.} \item{...}{parameters passed to \code{as.POSIXlt()}.} \item{tz}{passed on to \code{\link[=as.POSIXlt]{as.POSIXlt()}}} } \description{ Convert aweek objects to characters or dates } \examples{ w <- date2week(Sys.Date(), week_start = "Sunday") w # convert to POSIX as.POSIXlt(w) as.POSIXlt(w, floor_day = TRUE) as.POSIXlt(w, floor_day = TRUE, tz = "KST") # convert to date as.Date(w) as.Date(w, floor_day = TRUE) # convert to character (strip attributes) as.character(w) } \seealso{ \code{\link[=date2week]{date2week()}} \code{\link[=print.aweek]{print.aweek()}} } aweek/DESCRIPTION0000644000176200001440000000157614317444553013077 0ustar liggesusersPackage: aweek Title: Convert Dates to Arbitrary Week Definitions Version: 1.0.3 Authors@R: person(c("Zhian N."), "Kamvar", email = "zkamvar@gmail.com", role = c("aut", "cre")) Description: Which day a week starts depends heavily on the either the local or professional context. This package is designed to be a lightweight solution to easily switching between week-based date definitions. Depends: R (>= 3.0) License: MIT + file LICENSE Encoding: UTF-8 Suggests: testthat, stats, roxygen2, knitr, rmarkdown, covr, spelling RoxygenNote: 7.2.1 URL: https://www.repidemicsconsortium.org/aweek/ BugReports: https://github.com/reconhub/aweek/issues/ VignetteBuilder: knitr Language: en-US NeedsCompilation: no Packaged: 2022-10-06 02:40:19 UTC; zhian Author: Zhian N. Kamvar [aut, cre] Maintainer: Zhian N. Kamvar Repository: CRAN Date/Publication: 2022-10-06 03:20:11 UTC aweek/build/0000755000176200001440000000000014317440023012443 5ustar liggesusersaweek/build/vignette.rds0000644000176200001440000000033114317440023014777 0ustar liggesusersmQ0 QH4ܸC#:g\IQJO[QzJx' 1US[tE9_' m^)?e}'10rViO|Lqako醥;>IkVD$u?laweek/tests/0000755000176200001440000000000014053270040012503 5ustar liggesusersaweek/tests/spelling.R0000644000176200001440000000024214053270040014441 0ustar liggesusersif (requireNamespace('spelling', quietly = TRUE)) spelling::spell_check_test(vignettes = TRUE, error = FALSE, skip_on_cran = TRUE) aweek/tests/testthat/0000755000176200001440000000000014317444553014362 5ustar liggesusersaweek/tests/testthat/test-conversion.R0000644000176200001440000000314614053270040017634 0ustar liggesuserscontext("conversion tests") dat <- c(as.Date("2019-03-07"), NA) names(dat) <- c("one", "two") datw <- date2week(dat, 5) test_that("aweek prints as expected", { expect_output(wtad <- print(datw), "aweek start: Friday") expect_output(print(date2week(dat, 5, factor = TRUE)), "Levels:") expect_identical(wtad, datw) }) test_that("a character can be converted to a date", { w <- week2date("2018-W13", 1) expect_is(w, "Date") expect_identical(format(w, "%Y-%m-%d"), "2018-03-26") }) test_that("character can be converted to aweek if in YYYYMMDD format", { expect_identical(date2week(c("2019/03/07", NA), 5), unname(datw)) expect_identical(date2week(c("2019:03:07", NA), 5, format = "%Y:%m:%d"), unname(datw)) expect_identical(date2week(c("3/7/2019", NA), 5, format = "%m/%d/%Y"), unname(datw)) expect_identical(date2week(c("7/3/2019", NA), 5, format = "%d/%m/%Y"), unname(datw)) expect_error(date2week(c("2019-07-03", "7/3/2019"), 5), "The first incorrect date is 7/3/2019") }) test_that("aweek can be converted to character", { expect_failure(expect_output(print(as.character(datw)), "aweek start: Friday")) expect_is(as.character(datw), "character") expect_named(as.character(datw), names(datw)) }) test_that("aweek can be converted to POSIXlt", { p <- as.POSIXlt(datw, tz = "UTC") expect_identical(as.POSIXlt(dat, tz = "UTC"), p) }) test_that("aweek can be converted to POSIXlt", { p <- as.POSIXlt(datw, tz = "UTC") expect_identical(as.POSIXlt(dat), p) p2 <- as.POSIXlt(datw, tz = "UTC", floor_day = TRUE) expect_failure(expect_identical(p, p2)) expect_lt(p2[1], p[1]) }) aweek/tests/testthat/test-examples.R0000644000176200001440000000132014053270040017255 0ustar liggesuserscontext("real world example tests") dats <-"date week 2005-01-01 2004-W53-6 2005-01-02 2004-W53-7 2005-12-31 2005-W52-6 2006-01-01 2005-W52-7 2006-01-02 2006-W01-1 2006-12-31 2006-W52-7 2007-01-01 2007-W01-1 2007-12-30 2007-W52-7 2007-12-31 2008-W01-1 2008-01-01 2008-W01-2 2008-12-28 2008-W52-7 2008-12-29 2009-W01-1 2008-12-30 2009-W01-2 2008-12-31 2009-W01-3 2009-01-01 2009-W01-4 2009-12-31 2009-W53-4 2010-01-01 2009-W53-5 2010-01-02 2009-W53-6 2010-01-03 2009-W53-7 " dats <- read.table(text = dats, stringsAsFactors = FALSE, header = TRUE) test_that("Wikipedia ISO week definitions hold up", { w <- as.character(date2week(dats$date, week_start = 1)) expect_identical(dats$week, w) }) aweek/tests/testthat/test-weekday-conversion.R0000644000176200001440000000156714053270040021270 0ustar liggesuserscontext("weekday conversion tests") test_that("English weekdays can be matched", { expect_identical(date2week(Sys.Date(), week_start = 1.0), date2week(Sys.Date(), week_start = "Monday")) }) test_that("Nonsense days will fail", { expect_error(date2week(Sys.Date(), week_start = "Zhian"), "Zhian") expect_error(date2week(Sys.Date(), week_start = "Zhian"), Sys.getlocale("LC_TIME")) }) lct <- Sys.getlocale("LC_TIME") suppressWarnings({ res <- Sys.setlocale("LC_TIME", "de_DE.utf8") }) test_that("Different locales works", { skip_if_not(res == "de_DE.utf8") expect_identical(date2week(Sys.Date(), week_start = 7), date2week(Sys.Date(), week_start = "Sonntag")) expect_identical(date2week(Sys.Date(), week_start = 7), date2week(Sys.Date(), week_start = "Sunday")) }) Sys.setlocale("LC_TIME", lct) aweek/tests/testthat/test-as.data.frame.R0000644000176200001440000000274314053270040020055 0ustar liggesuserscontext("Data frame conversion tests") d <- as.Date("2019-05-15") + (-5:5) w <- date2week(d, week_start = "Sunday") f <- date2week(d, week_start = "Sunday", factor = TRUE, floor_day = TRUE) names(f) <- as.character(d) wd <- data.frame(w, d) fd <- data.frame(f, d) test_that("the resulting data frame contain the right classes", { expect_is(wd, "data.frame") expect_identical(wd$w, w) expect_identical(wd$d, d) expect_identical(as.Date(wd$w), wd$d) expect_is(fd, "data.frame") expect_identical(fd$f, unname(f)) expect_identical(fd$d, d) expect_identical(as.Date(fd$f), as.Date(wd$w, floor_day = TRUE)) }) test_that("the data can be merged", { fwd <- merge(wd, fd) expect_is(fwd, "data.frame") expect_identical(fwd$d, d) expect_identical(fwd$f, unname(f)) expect_identical(fwd$w, w) }) test_that("data frames can be combined", { # straightforward appending wdwd <- rbind(wd, wd) # appending week on top of date and vice-versa wddw <- rbind(wd, setNames(rev(wd), names(wd))) # appending aweek on date objects. ddwd <- rbind(setNames(wd[c(2, 2)],names(wd)), wd) # appending aweek object on characters cd <- wd cd$w <- as.character(cd$w) cdwd <- rbind(cd, wd) # wdcd <- rbind(wd, cd) expect_identical(wdwd, wddw) expect_identical(ddwd$d, c(d, d)) expect_identical(ddwd$w, c(d, d)) expect_is(wdwd$w, "aweek") expect_identical(wdwd$w, c(w, w)) expect_identical(wdwd$d, c(d, d)) # wdfd <- rbind(wd, setNames(fd, names(wd))) }) aweek/tests/testthat/test-make_aweek.R0000644000176200001440000001131314053270040017533 0ustar liggesuserscontext("get_aweek") mat <- matrix(c( 2019, 11, 1, 7, # 2019-03-10 2019, 11, 2, 7, 2019, 11, 3, 7, 2019, 11, 4, 7, 2019, 11, 5, 7, 2019, 11, 6, 7, 2019, 11, 7, 7 ), ncol = 4, byrow = TRUE) colnames(mat) <- c("year", "week", "day", "week_start") m <- as.data.frame(mat) ex_dat <- as.Date("2019-03-10") + 0:6 test_that("get_aweek() will always default to the first weekday of the year", { w1d <- get_date(week = 1, year = format(Sys.Date(), "%Y"), day = 1) d1 <- as.Date(get_aweek()) d2 <- as.Date(as.aweek(w1d, floor_day = TRUE)) expect_identical(d1, d2) }) test_that("get_aweek() will always default to the first weekday of the year for any given year", { # Span of forty-one consecutive years years <- as.integer(format(Sys.Date(), "%Y")) + -20:20 expect_equal(diff(sort(years)), rep(1L, 40)) w1d <- get_date(week = 1, year = years, day = 1) d1 <- as.Date(get_aweek(year = years)) d2 <- as.Date(as.aweek(w1d, floor_day = TRUE)) expect_identical(d1, d2) }) test_that("get_aweek() can use vectors", { w <- get_aweek(week = m$week, year = m$year, day = m$day, start = m$week_start, week_start = 7L) expect_is(w, "aweek") expect_equal(get_week_start(w), 7L) expect_identical(sprintf("%04d-W%02d-%d", m$year, m$week, m$day), as.character(w)) expect_identical(w, get_aweek(week = m$week, year = m$year, day = m$day, start = "Sunday", week_start = "Sunday")) expect_identical(w, get_aweek(week = m$week, year = m$year, day = m$day, start = rep("Sunday", 7), week_start = 7L)) expect_identical(as.Date(w), ex_dat) }) test_that("get_aweek() can handle missing data", { ver <- package_version(paste(R.version$major, R.version$minor, sep = ".")) min_ver <- package_version("3.5") skip_if(ver < min_ver) for (i in c("year", "week", "week_start")) { mm <- m mm[1, i] <- NA lab <- sprintf("get_aweek() couldn't handle missing [%s].", i) expect_is({ w <- try(with(mm, get_aweek(week = week, year = year, day = day, start = week_start, week_start = "Sunday"))) }, "aweek", label = paste("(gen)", lab)) expect_identical(as.Date(w), c(as.Date(NA_character_), ex_dat[-1]), label = paste("(acc)", lab)) } }) test_that("get_aweek() can handle several missing random data", { ver <- package_version(paste(R.version$major, R.version$minor, sep = ".")) min_ver <- package_version("3.5") skip_if(ver < min_ver) skip_if_not_installed("stats") mm <- mat mm[sample.int(7, 1), "year"] <- NA mm[sample.int(7, 1), "week"] <- NA mm[sample.int(7, 1), "week_start"] <- NA mm <- as.data.frame(mm) cc <- stats::complete.cases(mm) expect_is({ w <- try(with(mm, get_aweek(week = week, year = year, day = day, start = week_start, week_start = 7L))) }, "aweek") expect_identical(!cc, is.na(w)) expect_identical(as.Date(w[cc]), ex_dat[cc]) }) test_that("missing days revert to day 1", { mm <- m mm[1, "day"] <- NA w <- try(with(mm, get_aweek(week = week, year = year, day = day, start = week_start, week_start = 7L))) expect_identical(as.Date(w), ex_dat) mm[, "day"] <- NA w <- try(with(mm, get_aweek(week = week, year = year, day = day, start = week_start, week_start = 7L))) expect_identical(as.Date(w), rep(ex_dat[1], 7)) }) test_that("get_aweek() needs a scalar for week_start", { expect_error(get_aweek(week_start = 1:2), "week_start must be length 1") expect_error(get_aweek(week_start = NA), "week_start must not be missing") expect_error(get_aweek(week_start = 8), "Weekdays must be between 1 and 7") }) test_that("invalid weeks will throw an error", { expect_error(get_aweek(week = 69), "Weeks must be between 1 and 53") expect_error(get_aweek(week = 0), "Weeks must be between 1 and 53") expect_error(get_aweek(week = -9), "Weeks must be between 1 and 53") }) test_that("invalid starts will throw an error", { expect_error(get_aweek(start = 69), "Weekdays must be between 1 and 7") expect_error(get_aweek(start = 0), "Weekdays must be between 1 and 7") expect_error(get_aweek(start = -9), "Weekdays must be between 1 and 7") }) test_that("invalid days will throw an error", { expect_error(get_aweek(day = 69), "Weekdays must be between 1 and 7") expect_error(get_aweek(day = 0), "Weekdays must be between 1 and 7") expect_error(get_aweek(day = -9), "Weekdays must be between 1 and 7") }) test_that("null arguments throw an error", { expect_error(get_aweek(week = NULL), "all arguments must not be NULL") expect_error(get_aweek(year = NULL), "all arguments must not be NULL") expect_error(get_aweek(day = NULL), "all arguments must not be NULL") expect_error(get_aweek(start = NULL), "all arguments must not be NULL") expect_error(get_aweek(week_start = NULL), "please provide a week_start") }) aweek/tests/testthat/test-change_week_start.R0000644000176200001440000000135414053270040021123 0ustar liggesuserscontext("week_start changing") x <- get_aweek(week = 10, year = 2019, day = 1, week_start = get_week_start()) test_that("change_week_start will throw an error if week_start is missing", { expect_error(change_week_start(x), "please provide a week_start") }) test_that("change_week_start will not change the object if week_start is the same", { expect_identical(x, change_week_start(x, get_week_start(x))) }) test_that("change_week_start will de-factorize", { xf <- change_week_start(factor_aweek(x), get_week_start(x)) expect_is(xf, "aweek") expect_identical(xf, x) }) test_that("change_week_start will change the week_start at will", { w <- change_week_start(x, "Wednesday") expect_identical(get_week_start(w), 3L) }) aweek/tests/testthat/test-date2week.R0000644000176200001440000000565514053270040017331 0ustar liggesuserscontext("conversion to aweek tests") dats <- sprintf("%d-01-01", 2001:2019) dats <- as.Date(c(dats, NA_character_)) test_that("an error is thrown if something can't be converted to a date", { expect_error(date2week(iris), "iris could not be converted to a date") }) # test_that("an error will be thrown if a date is NULL", { # expect_error(date2week(NULL), "NULL could not be converted to a date.") # }) test_that("an error will be thrown if the user tries to use factor without floor_day", { expect_error(date2week(dats[1], factor = TRUE, floor_day = FALSE), "as of aweek 1.0, using factor without floor_day is not allowed.") }) test_that("January first dates can be properly converted", { # ISO week datw <- date2week(dats, 1) # Epi week datew <- date2week(dats, 7, numeric = TRUE) # Floored datf <- date2week(dats, 1, floor_day = TRUE) # Factors datffac <- date2week(dats, 1, floor_day = TRUE, factor = TRUE) datn <- date2week(dats, 1, numeric = TRUE) datback <- as.Date(datw) # isoweeks weeknums <- c(1, 1, 1, 1, 53, 52, 1, 1, 1, 53, 52, 52, 1, 1, 1, 53, 52, 1, 1, NA) # epiweeks epiweeks <- c(1, 1, 1, 53, 52, 1, 1, 1, 53, 52, 52, 1, 1, 1, 53, 52, 1, 1, 1, NA) iw <- c("2001-W01-1", "2002-W01-2", "2003-W01-3", "2004-W01-4", "2004-W53-6", "2005-W52-7", "2007-W01-1", "2008-W01-2", "2009-W01-4", "2009-W53-5", "2010-W52-6", "2011-W52-7", "2013-W01-2", "2014-W01-3", "2015-W01-4", "2015-W53-5", "2016-W52-7", "2018-W01-1", "2019-W01-2", NA) class(iw) <- "aweek" attr(iw, "week_start") <- 1L floored <- gsub("-\\d$", "", iw) # conversions are reversible expect_identical(as.character(dats), as.character(datback)) # weeks print as expected expect_identical(datw, iw) # wee numbers display as expected expect_identical(datn, weeknums) expect_identical(datew, epiweeks) # floored weeks are handled as expected expect_identical(datf, floored) expect_identical(trunc(datw), floored) # Factors are handled as expected expect_identical(as.character(datffac), as.character(datf)) # Factor levels are the sequence of dates expect_identical(seq.Date(min(dats, na.rm = TRUE), max(dats, na.rm = TRUE), by = 7), week2date(levels(datffac), 1)) }) test_that("dates can be co back and forth no matter the start day", { for (i in 1:7) { datw <- date2week(dats, i) datback <- week2date(datw) expect_identical(as.character(dats), as.character(datback), info = sprintf("day: %d", i)) } }) test_that("invalid weekdays throw an error", { expect_error(week2date("2019-W20-8"), "Weekdays must be between 1 and 7") expect_error(date2week(Sys.Date(), week_start = 1:7), "week_start must be length 1") expect_error(date2week(Sys.Date(), week_start = 8), "Weekdays must be between 1 and 7") }) aweek/tests/testthat/test-factor_aweek.R0000644000176200001440000000165514053270040020104 0ustar liggesuserscontext("factorisation tests") test_that("factor_aweek will reject non-aweek objects", { expect_error(factor_aweek("2018-W10-1"), "x must be an 'aweek' object") }) test_that("factor_aweek accounts for edge weeks", { w1 <- get_aweek(c(8, 11), year = 2019, day = c(7, 1)) w2 <- get_aweek(c(8, 11), year = 2019, day = c(1, 7)) f1 <- factor_aweek(w1) f2 <- factor_aweek(w2) expect_identical(levels(f1), c("2019-W08", "2019-W09", "2019-W10", "2019-W11")) expect_identical(levels(f2), c("2019-W08", "2019-W09", "2019-W10", "2019-W11")) }) test_that("factor_aweek accounts for edge days across years", { w3 <- get_aweek(c(53, 02), year = 2015:2016, day = c(7, 1)) w4 <- get_aweek(c(53, 02), year = 2015:2016, day = c(1, 7)) f3 <- factor_aweek(w3) f4 <- factor_aweek(w4) expect_identical(levels(f3), c("2015-W53", "2016-W01", "2016-W02")) expect_identical(levels(f4), c("2015-W53", "2016-W01", "2016-W02")) }) aweek/tests/testthat/test-as.aweek.R0000644000176200001440000000470414053270040017146 0ustar liggesuserscontext("as.aweek tests") ver <- package_version(paste(R.version$major, R.version$minor, sep = ".")) min_ver <- package_version("3.5") # Setup for a default d <- strptime("2019-05-23 03:11", format = "%Y-%m-%d %H:%M", tz = "UTC") e <- "2019-W21-4" attr(e, "week_start") <- get_week_start() class(e) <- "aweek" test_that("aweek rejects invalid classes", { expect_error(as.aweek(iris), "There is no method to convert an object of class 'data.frame' to an aweek object") }) test_that("aweek rejects NULL", { expect_error(as.aweek(NULL), "aweek objects can not be NULL") }) test_that("as.aweek rejects invalid weeks", { base <- "aweek strings must match the pattern 'YYYY-Www-d'. The first incorrect string was: '%s'" expect_error(as.aweek("2018-01-01"), sprintf(base, "2018-01-01")) expect_error(as.aweek("2018-W61-1"), sprintf(base, "2018-W61-1")) }) test_that("as.aweek takes into account the length of week_start", { x <- as.aweek("2018-W10-1", start = 1:2) y <- as.aweek("2018-W10-1", start = c("Mon", "Tue")) z <- as.aweek(c("2018-W09-7", "2018-W10-1"), week_start = get_week_start() + runif(1), start = "Tuesday") expect_identical(x, y) expect_identical(y, z) }) test_that("as.aweek correctly converts characters", { x <- c(NA, "2018-W10-1") skip_if(ver < min_ver) expect_is(as.aweek(x), "aweek") expect_identical(as.aweek(x), as.aweek(x, start = "Monday")) expect_is(as.aweek(x, factor = TRUE, floor_day = TRUE), "aweek") expect_is(as.aweek(x, factor = TRUE, floor_day = TRUE), "factor") }) test_that("as.aweek correctly converts factors", { skip_if(ver < min_ver) f <- factor(c(NA, "2018-W10-1")) expect_is(as.aweek(f), "aweek") expect_identical(as.aweek(f), as.aweek(f, start = "Monday")) expect_is(as.aweek(f, factor = TRUE, floor_day = TRUE), "aweek") expect_is(as.aweek(f, factor = TRUE, floor_day = TRUE), "factor") }) test_that("as.aweek correctly converts dates", { expect_is(as.Date(d), "Date") expect_is(as.aweek(as.Date(d)), "aweek") expect_identical(as.aweek(as.Date(d)), e) }) test_that("as.aweek correctly converts POSIXt", { expect_is(d, "POSIXt") expect_is(as.aweek(d), "aweek") expect_identical(as.aweek(d), e) }) test_that("as.aweek will act like change_week_start", { x <- as.aweek("2019-W10-1", week_start = 5) y <- as.aweek(as.Date(x), week_Start = 1) expect_is(x, "aweek") expect_is(y, "aweek") expect_identical(x, as.aweek(x)) expect_identical(x, as.aweek(y, 5L)) }) aweek/tests/testthat/test-week_start.R0000644000176200001440000000266614053270040017625 0ustar liggesuserscontext("global week_start tests") oa <- getOption("aweek.week_start", 1L) test_that("global week_start variable can be set and reset", { expect_identical(oa, get_week_start()) # converting to Sunday expect_identical(oa, set_week_start("Sunday")) expect_identical(7L, get_week_start()) # Converting to Tuesday expect_identical(7L, set_week_start("Tuesday")) expect_identical(2L, get_week_start()) # Converting back to Monday (or whatever day it was originally set to) expect_identical(2L, set_week_start(oa)) expect_identical(oa, get_week_start()) }) test_that("get_week_start will return the attibute from an aweek object", { # Converting to Wednesday ws <- set_week_start("Wednesday") w <- date2week(Sys.Date()) # Resetting global to Monday we <- set_week_start(oa) # week_start for wednesday is retained in the object expect_identical(we, 3L) expect_identical(we, get_week_start(w)) # global week start is different expect_identical(oa, get_week_start()) }) test_that("get_week_start will return an error if not null or aweek", { expect_error(get_week_start(Sys.Date()), "w must be an 'aweek' object or NULL") }) test_that("set_week_start will throw an error for invalid weekdays", { msg <- "week_start must be a whole number from 1 to 7, representing the days of the week." expect_error(set_week_start(8), msg) expect_error(set_week_start(pi), msg) expect_error(set_week_start(0), msg) }) aweek/tests/testthat/test-subset.R0000644000176200001440000000762514053270040016762 0ustar liggesuserscontext("subsetting tests") d <- as.Date("2018-12-31") + 0:14 x <- date2week(d, week_start = "Monday") f <- date2week(d, week_start = "Monday", factor = TRUE, floor_day = TRUE) y <- date2week(d, week_start = "Saturday") dd <- date2week(c(d, as.Date("2019-01-15")), week_start = "Monday") test_that("subsetting returns an aweek object", { expect_identical(x[], x) expect_is(x[1], "aweek") expect_is(y[1], "aweek") expect_is(f[1], "aweek") expect_identical(as.Date(x[1]), d[1]) expect_identical(as.Date(y[1]), d[1]) expect_identical(as.Date(f[1]), d[1]) expect_is(as.list(x)[[1]], "aweek") expect_is(as.list(y)[[1]], "aweek") expect_is(as.list(f)[[1]], "aweek") expect_is(as.list(f)[[1]], "factor") expect_identical(as.Date(as.list(x)[[1]]), d[1]) expect_identical(as.Date(as.list(y)[[1]]), d[1]) expect_identical(as.Date(as.list(f)[[1]]), d[1]) expect_is(x[[1]], "aweek") expect_is(y[[1]], "aweek") expect_is(f[[1]], "aweek") expect_identical(as.Date(x[[1]]), d[1]) expect_identical(as.Date(y[[1]]), d[1]) expect_identical(as.Date(f[[1]]), d[1]) }) test_that("aweek objects can be ammended", { xx <- rev(x) # ammended with missing xx[1] <- NA expect_identical(xx[1], as.aweek(NA_character_, week_start = get_week_start(xx))) # ammended with aweek objects xx[1] <- x[1] expect_identical(xx[1], x[1]) # ammended with dates xx[2] <- as.Date(x[2]) expect_identical(xx[2], x[2]) # ammended with ISO week character strings xx[3] <- as.character(x[3]) expect_identical(xx[3], x[3]) # entire object can be replaced xx[] <- x expect_identical(xx, x) # factors can be ammended xx[c(1, 8, 15)] <- factor_aweek(x[c(1, 8, 15)]) expect_identical(xx, x) expect_error(y[1] <- x[1], "aweek objects must have the same week_start attribute") expect_error(y[1] <- "1999-01-01", "The first incorrect string was: '1999-01-01'") base <- "Cannot add an object of class '%s' to an aweek object" expect_error(y[1] <- NULL, sprintf(base, "NULL")) }) test_that("change_week_start() only works on aweek objects", { expect_error(change_week_start("2018-W01-1"), "x must be an aweek object") expect_identical(change_week_start(x, get_week_start(x)), x) expect_identical(change_week_start(x, "Saturday"), y) }) test_that("concatenation returns aweek object with the correct week_start attribute", { expect_error(xy <- c(x, y), "All aweek objects must have the same week_start attribute.") expect_error(yx <- c(y, x), "All aweek objects must have the same week_start attribute.") xy <- c(x, change_week_start(y, get_week_start(x))) yx <- c(y, change_week_start(x, get_week_start(y))) expect_identical(attr(xy, "week_start"), 1L) expect_identical(attr(yx, "week_start"), 6L) }) test_that("truncation works", { expect_identical(trunc(x), date2week(d, week_start = 1, floor_day = TRUE)) expect_identical(trunc(y), date2week(d, week_start = 6, floor_day = TRUE)) expect_identical(trunc(f), date2week(d, week_start = 1, floor_day = TRUE, factor = TRUE)) }) test_that("rep works", { expect_identical(as.Date(rep(y, each = 2)), rep(d, each = 2)) expect_identical(as.Date(rep(x, each = 2)), rep(d, each = 2)) expect_true(all(as.Date(rep(f, each = 2)) <= rep(d, each = 2))) }) test_that("characters can be added", { xw <- c(x, "2019-W03-2") xd <- c(x, "2019-01-15") expect_identical(xw, dd) expect_identical(xw, xd) }) test_that("factors don't force factors", { xf <- c(date2week(d[1], week_start = "Monday", factor = TRUE), x[-1]) expect_is(xf, "aweek") expect_failure(expect_is(xf, "factor")) expect_true(all(as.Date(xf) <= as.Date(x))) }) test_that("dates can be added", { xd <- c(x, as.Date("2019-01-15")) expect_identical(xd, dd) }) test_that("POSIXt objects can be added", { xp <- c(x, as.POSIXlt("2019-01-15", tz = "UTC")) xc <- c(x, as.POSIXct("2019-01-15", tz = "UTC")) expect_identical(xp, xc) expect_identical(xp, dd) }) aweek/tests/testthat.R0000644000176200001440000000006614053270040014470 0ustar liggesuserslibrary(testthat) library(aweek) test_check("aweek") aweek/vignettes/0000755000176200001440000000000014317440023013354 5ustar liggesusersaweek/vignettes/introduction.Rmd0000644000176200001440000003100614317437750016556 0ustar liggesusers--- title: "aweek means 'any week'" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: toc: true toc_depth: 2 vignette: > %\VignetteIndexEntry{aweek means 'any week'} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- Introduction ============ The day in which a week starts differs depending on context. For countries like the UK, the first day of the week is the first working day, which is Monday. This definition conforms with the [ISO 8601 standard definition for the beginning of a week](https://en.wikipedia.org/wiki/ISO_week_date), but there are examples of situations where the first day of the week is different: - [The US CDC defines an "MMWR" week which starts on a Sunday](https://stacks.cdc.gov/view/cdc/22305). - In some regions, MSF will define a week that starts on Saturday This package provides tools to convert dates to weeks and back where a week can start on any day. You can use this package for any of the following: - convert date to week starting on any day - convert week numbers and years to dates - convert week to date - convert week to week - create a factor of weeks that contains ordered levels that includes missing weeks. Converting dates to weeks ========================= You can convert dates to weeks starting on any day by using `date2week()` with the `week_start` argument. This argument can be a number from 1 to 7 representing the ISO 8601 day of the week OR it can be a string representing the day of the week in either an English locale or the locale defined on your computer. The default of this argument is the value of `get_week_start()`, which is a thin wrapper around `options("aweek.week_start", 1L)`. **Unless you have specified a default `aweek.week_start` option with `set_week_start()`, this will always be set to 1 (Monday).** > It is **highly recommended** that you set the default `aweek.week_start` either > in the beginning of your Rscript, Rmarkdown document, or in your .Rprofile. ```{r date2week} library("aweek") set_week_start("Sunday") # setting the default week_start to Sunday set.seed(2019-03-03) dat <- as.Date("2019-03-03") + sample(-6:7, 10, replace = TRUE) dat print(w <- date2week(dat)) ``` If you need a different day on the fly, you can supply an integer or character day to the `week_start` argument. ```{r date2week_week_start} # Use character days date2week(dat, week_start = "Monday") # Use ISO 8601 days date2week(dat, week_start = 1) ``` If you want to save two extra keystrokes, you can also use the `as.aweek()` method for dates, which wraps `date2week()`: ```{r as.aweek.Date} as.aweek(dat, week_start = 1) ``` What you get back is an `aweek` class object. It can be converted back to a date with either `as.Date()` or `week2date()`: ```{r date2week2date} week2date(w) as.Date(w) ``` How does it work? ----------------- The calculation of weeks from dates requires knowledge of the current day of the week and the number of days past 1 January. Week numbers are calculated in three steps: 1. Find the day of the week, relative to the week_start (d). The day of the week (d) relative to the week start (s) is calculated using the ISO week day (i) via `d = 1L + ((i + (7L - s)) %% 7L)`. 2. Find the date that represents midweek (m). The date that represents midweek is found by subtracting the day of the week (d) from 4 and adding that number of days to the current date: `m = date + (4 - d)`. 3. Find the week number (w) by counting the number of days since 1 January to (m), and use integer division by 7: `w = 1L + ((m - yyyy-01-01) %/% 7)` For example, here's how to calculate the week for Tuesday, 6 December 2016, assuming the week start is a Sunday: ```{r example_day} the_date <- as.Date("2016-12-06") jan_1 <- as.Date("2016-01-01") i <- as.POSIXlt(the_date)$wday # 2, the ISO date for Tuesday s <- 7L # week_start for sunday # 1. Find the day of the week print(d <- 1L + ((i + (7L - s)) %% 7L)) # 2. Find the date that represents midweek print(m <- the_date + (4L - d)) # 3. Find the week print(w <- 1L + as.integer(m - jan_1) %/% 7L) # Format the week sprintf("2016-W%02d-%d", w, d) ``` For the weeks around 1 January, the year is determined by the week number. If the month is January, but the week number is 52 or 53, then the year for the week (YYYY) is the calendar year (yyyy) minus 1. However, if the month is December, but the week number is 1, then the year for the week (YYYY) is the calendar year (yyyy) plus 1. The `aweek` class --------------- The result you see above is an object of class "aweek". The `aweek` class is a character that contains the `week_start` attribute. This attribute allows it to be easily converted back to a date without the user needing to enter the start day every time. You can convert a character that matches the `YYYY-Www-d` pattern to an `aweek` class object with `as.aweek()`: ```{r as.aweek.character} x <- as.aweek("2019-W10-1") x ``` > Under the hood, it checks the validity of the week string and then add the > attribute and class: > > x <- "2019-W10-1" > attr(x, "week_start") <- 7 # Sunday > class(x) <- "aweek" > If you need to remove the class, you can just use `as.character()`: ```{r ascharacter} as.character(x) ``` Best practices -------------- The `date2week()` function only checks that dates are in ISO 8601 (yyyy-mm-dd) format before converting to weeks, *and otherwise assumes that the dates are accurate* so it's strongly recommended to make sure your dates are in either `Date` or `POISXt` format and accurate before converting to weeks. The [lubridate](https://cran.r-project.org/package=lubridate) can be used for this purpose. Use `set_week_start()` at the beginning of all your scripts to explicitly define the day on which your weeks start. This can be overridden if need be in specific parts of your scripts. Otherwise, the default will be dependent on the value of `getOption("aweek.week_start", 1L)`. Because the `week_start` arguments default to `get_week_start()`, it's recommended to specify `week_start` in `date2week()` and `week2date()` if you don't have an `aweek` object. Before you combine aweek objects, confirm that they are actually aweek objects with `inherits(myObject, "aweek")`. Weekly aggregation ------------------ There are times where you would want to aggregate your days into weeks, you can do this by specifying `floor_day = TRUE` in `date2week()`. For example, here we can show the individual weeks: ```{r date2week_floor} print(wf <- date2week(dat, week_start = "Saturday", floor_day = TRUE)) table(wf) ``` If you convert this to date, then all the dates will represent the beginning of the week: ```{r date2week_floor2date} print(dwf <- week2date(wf)) weekdays(dwf) ``` If you want to aggregate your `aweek` objects after you created them, you can always use the `trunc()` function: ```{r trunc} w <- date2week(dat) w trunc(w) ``` Factors ------- Weeks can be represented as factors, which is useful for tabulations across weeks. You can use `factor = TRUE` in `date2week()` and it will automatically fill in any missing weeks. ```{r factors} dat[1] + c(0, 15) date2week(dat[1] + c(0, 15), week_start = 1, factor = TRUE) ``` If you already have an aweek object and want to convert it to a factor, you can use `factor_aweek()`: ```{r factor_aweek} factor_aweek(w) ``` Be careful when combining factors with other dates or aweek objects as they will force the other objects to be truncated as well. Weeks to weeks -------------- You can use `change_week_start()` to convert between different week definitions if you have an `aweek` object: ```{r week2week_wednesday} w # week starting on Sunday ww <- change_week_start(w, week_start = "wednesday") # same dates, starting on Wednesday ww identical(as.Date(w), as.Date(ww)) ``` ```{r week2week, R.options=list(width = 100)} # create a table with all days in the week d <- as.Date("2019-03-03") + 0:6 res <- lapply(weekdays(d), function(i) date2week(d, week_start = i)) names(res) <- weekdays(d) data.frame(res) ``` All of these columns contain the same dates: ```{r week2week2date, R.options=list(width = 100)} data.frame(lapply(res, as.Date)) ``` Combining `aweek` objects ----------------------- You can add dates, aweek objects, or characters to aweek objects: ```{r caweekdate} c(as.aweek("2010-W10-1"), res$Sunday, "2010-W12-1", as.Date(res$Monday[1]) + 14) ``` However, you *can not* combine aweek objects with different `week_start` attributes. ```{r cweek2week_err, error = TRUE} c(res$Sunday[1], res$Wednesday[2], res$Friday[3]) ``` If you want to combine different aweek objects, you must first change their `week_start` attribute: ```{r cweekweek} wed <- change_week_start(res$Wednesday, get_week_start()) fri <- change_week_start(res$Friday, get_week_start()) c(res$Sunday[1], wed[2], fri[3]) ``` ### Dates can be appended to aweek objects Dates combined with aweek objects will will be automatically converted. ```{r add_dates} c(res$Monday, as.Date("2019-04-03")) ``` ### Add characters with caution You can also add character representation of weeks, but be aware that **it is assumed that these have the same `week_start` as the first object.** ```{r add_chars} s <- c(res$Saturday, "2019-W14-3") s m <- c(res$Monday, "2019-W14-3") m ``` **These will translate into different dates** ```{r char2date} as.Date(s[7:8]) as.Date(m[7:8]) ``` Working with weeks in data frames ================================= You may encounter a situation where you have a merged data frame with weeks starting on different days. This section will cover two situations where you may have weeks as numbers and weeks as ISO-week strings. First we will create our demonstration data that represents the same week with different `week_start` attributes. ```{r date_a_frame} # create a table with all days in the week d <- as.Date("2019-03-03") + 0:6 res <- lapply(weekdays(d), function(i) date2week(d, week_start = i)) resn <- lapply(weekdays(d), function(i) date2week(d, week_start = i, numeric = TRUE)) datf <- data.frame(wday = rep(weekdays(d), each = 7), week = unlist(res), # note: unlist converts to character week_number = unlist(resn), year = 2019, stringsAsFactors = FALSE) datf$day <- substring(datf$week, 10, 11) head(datf, 10) ``` To get the weeks (numbers or strings) to aweek objects, you should use the `start` argument to specify which day of the week they start on. Internally, this translates the week to their corresponding dates and then to aweek objects with the same `week_start` attribute (which defaults to `get_week_start()`). weeks as numbers ------------ Most commonly, you will have weeks across data sets represented by numbers. These can be converted to aweek objects using the `get_aweek()` function and to dates using the `get_date()` function: ```{r get_aweek} datf$aweek <- with(datf, get_aweek(week = week_number, year = year, day = day, start = wday)) datf$date <- with(datf, get_date(week = week_number, year = year, day = day, start = wday)) head(datf, 10) ``` These functions are also useful for constructing weeks or dates on the fly if you only have a week and a year: ```{r} get_aweek(11, 2019) get_date(11, 2019) ``` ```{r remove_things, include = FALSE} datf$aweek <- NULL datf$date <- NULL ``` weeks as characters ------------------- If you have weeks formatted as ISO-week strings, then you can convert to aweek objects using `as.aweek()`: ```{r date_a_frame_2} datf$aweek <- with(datf, as.aweek(week, start = wday)) head(datf, 10) str(datf) ``` We can tabulate them to see how they transformed: ```{r tabluate_data_frame, R.options = list(width = 100)} print(with(datf, table(before = week, after = aweek)), zero.print = ".") ``` Converting weeks to dates ========================= If you receive data that contains week definitions, you can convert it back to a date if you know where the week starts. ```{r week2date} week2date("2019-W10-1", week_start = "Sunday") # 2019-03-03 week2date("2019-W10-1", week_start = "Monday") # 2019-03-04 ``` If you have an `aweek` object, however, it will use the `week_start` attribute defined in the object, even if the default `week_start` attribute is different: ```{r week2date_aweek} set_week_start("Monday") # Set the default week_start to ISO week get_week_start(w) # show the default week_start for w week2date(w) identical(week2date(w), dat) # TRUE identical(week2date(as.character(w)), dat) # FALSE ``` You can also use `as.Date()` and `as.POISXlt()` if you have an `aweek` object: ```{r asdate} as.Date(w) as.POSIXlt(w) ``` aweek/R/0000755000176200001440000000000014317406457011562 5ustar liggesusersaweek/R/as.data.frame.aweek.R0000644000176200001440000000073514053270040015371 0ustar liggesusers#' Convert aweek objects to a data frame #' #' @param x an aweek object #' @param ... unused #' #' @return a data frame with an aweek column #' @export #' @seealso [date2week()] [print.aweek()] #' @examples #' #' d <- as.Date("2019-03-25") + 0:6 #' w <- date2week(d, "Sunday") #' dw <- data.frame(date = d, week = w) #' dw #' dw$week as.data.frame.aweek <- function(x, ...) { nm <- deparse(substitute(x)) as.data.frame.vector(x, ..., nm = nm, stringsAsFactors = FALSE) } aweek/R/change_week_start.R0000644000176200001440000000300314053270040015336 0ustar liggesusers#' Change the week start of an aweek object #' #' This will change the week_start attribute of an aweek object and adjust the #' observations accordingly. #' #' @param week_start a number indicating the start of the week based on the ISO #' 8601 standard from 1 to 7 where 1 = Monday OR an abbreviation of the #' weekdate in an English or current locale. _Note: using a non-English locale #' may render your code non-portable._ Unlike [date2week()], this defaults to #' NULL, which will throw an error unless you supply a value. #' #' @inheritParams date2week #' #' @export #' @seealso [get_week_start()] for accessing the global and local `week_start` #' attribute, [as.aweek()], which wraps this function. #' @examples #' # New Year's 2019 is the third day of the week starting on a Sunday #' s <- date2week(as.Date("2019-01-01"), week_start = "Sunday") #' s #' #' # It's the second day of the week starting on a Monday #' m <- change_week_start(s, "Monday") #' m #' #' # When you compare the underlying dates, they are exactly the same #' identical(as.Date(s), as.Date(m)) #' #' # Since this will pass arguments to `date2week()`, you can modify other #' # aspects of the aweek object this way, but this is not advised. #' #' change_week_start(s, "Monday", floor_day = TRUE) change_week_start <- function(x, week_start = NULL, ...) { if (!inherits(x, "aweek")) { stop("x must be an aweek object") } week_start <- parse_week_start(week_start) d <- as.Date(x) date2week(d, week_start = week_start, ...) } aweek/R/utils.R0000644000176200001440000001010314053270040013020 0ustar liggesusers#' create a date for each row in the week matrix #' #' @param mat a integer matrix with four columns representing the year, week, day, and week_start #' @return a vector of dates the same lengths as the number of rows in the matrix. #' @noRd #' @examples #' mat <- matrix(c( #' 2019, 11, 1, 7, # 2019-03-10 #' 2019, 11, 2, 7, #' 2019, 11, 3, 7, #' 2019, 11, 4, 7, #' 2019, 11, 5, 7, #' 2019, 11, 6, 7, #' 2019, 11, 7, 7 # 2019-03-16 #' ), ncol = 4, byrow = TRUE) #' colnames(mat) <- c("year", "week", "day", "week_start") #' mat #' d <- date_from_week_matrix(mat) #' identical(d, as.Date("2019-03-10") + 0:6) #' d #' mat[, "week_start"] <- 7:1 #' date_from_week_matrix(mat) #' date2week(date_from_week_matrix(mat)) #' date_from_week_matrix <- function(mat) { # Steps: # # 1. find the weekday (relative to week_start) that represents 1 January. # # 2. subtract a week if the weekday is before the 4th day because that means # that the previous year was included in the week counts # # 3. determine the first date of the year by subtracting the weekday (relative # to week_start) from 1 January # # 4. add the weeks_as_days plus the number of days minus one to get the dates # replace any truncated aweeks with the first day of the week mat[, "day"] <- ifelse(is.na(mat[, "day"]), 1L, mat[, "day"]) # throw an error if any of these days are incorrect stop_if_not_weekday(mat[, "day"]) stop_if_not_weekday(mat[, "week_start"]) # missing years are set to NA january_1 <- ifelse(is.na(mat[, "year"]), NA, sprintf("%s-01-01", mat[, "year"])) january_1 <- iso_date(january_1) j1_day <- get_wday(january_1, mat[, "week_start"]) - 1L # If the previous year is included in this year's first date, subtract a week j1_is_first <- as.integer(j1_day < 4) weeks_as_days <- (mat[, "week"] - j1_is_first) * 7L first_week <- january_1 - j1_day unname(first_week + (weeks_as_days + mat[, "day"] - 1L)) } #' Template the week matrix #' #' @param n the number of rows for the matrix to have #' @noRd template_week_matrix <- function(n = 1L) { out <- matrix(NA_integer_, ncol = 4, nrow = n) colnames(out) <- c("year", "week", "day", "week_start") out } #' Helpers for getting the integer components of an aweek string #' @noRd int_year <- function(x) as.integer(substr(x, 1, 4)) int_week <- function(x) as.integer(substr(x, 7, 8)) int_wday <- function(x) as.integer(substr(x, 10, 10)) #' Get the weekday given an integer/date and a start date #' #' @param x a Date or an integer day relative to the ISO week #' #' @param s an integer representing the day to start the week #' #' @return the day of the week for x relative to s #' #' @note R stores its dates on a Sunday -- Saturday week where the numbering #' starts with 0 to 6. Happily, this doesn't affect the calculation since #' 1 + (0 + 6) %% 7 is the same as 1 + (7 + 6) %% 7 #' @noRd #' #' @examples #' #' get_wday(1:7, 1) # The isoweekdays get returned #' get_wday(as.Date("2007-01-01"), 1) # this is a Monday #' get_wday(as.Date("2007-01-01"), 7) #' get_wday(1, 5) # Monday is the fourth day in a Friday - Saturday week get_wday <- function(x, s) { if (inherits(x, c("Date", "POSIXt"))) { x <- as.integer(as.POSIXlt(x, tz = "UTC")$wday) # + 1L) } return(1L + (x + (7L - s)) %% 7L) } #' Retrieve the week in the year #' #' This finds the number of days between the current date and January 1 of that #' year and performs integer division to obtain the number of weeks. #' #' @param the_date a date/POSIXt object #' #' @return the numeric week of the year #' #' @noRd #' week_in_year <- function(the_date) { jan1 <- iso_date(sprintf("%s-01-01", format(the_date, "%Y"))) 1L + (as.numeric(the_date) - as.numeric(jan1)) %/% 7L } #' Convert ISO date strings to a date, quickly #' #' This is basically the quick version of as.Date() #' #' @param d a date string in ISO 8601 format #' @return a Date object #' #' @noRd iso_date <- function(d) as.Date(strptime(d, format = "%Y-%m-%d", tz = "UTC")) vlogic <- function(x, FUN, ...) { vapply(x, FUN = match.fun(FUN), FUN.VALUE = logical(1), ...) } aweek/R/conversions.R0000644000176200001440000000162414317166217014255 0ustar liggesusers#' Convert aweek objects to characters or dates #' #' @param x an object of class [aweek][print.aweek]. #' @param tz passed on to [as.POSIXlt()] #' @param ... parameters passed to `as.POSIXlt()`. #' @inheritParams date2week #' #' @export #' @rdname aweek-conversions #' @seealso [date2week()] [print.aweek()] #' @examples #' w <- date2week(Sys.Date(), week_start = "Sunday") #' w #' # convert to POSIX #' as.POSIXlt(w) #' as.POSIXlt(w, floor_day = TRUE) #' as.POSIXlt(w, floor_day = TRUE, tz = "KST") #' #' # convert to date #' as.Date(w) #' as.Date(w, floor_day = TRUE) #' #' # convert to character (strip attributes) #' as.character(w) as.POSIXlt.aweek <- function(x, tz = "", floor_day = FALSE, ...) { as.POSIXlt(as.Date(x, floor_day), tz = tz, ...) } #' @export #' @rdname aweek-conversions as.character.aweek <- function(x, ...) { xx <- NextMethod("as.character", x) names(xx) <- names(x) xx } aweek/R/get_aweek.R0000644000176200001440000001104514053270040013621 0ustar liggesusers#' Convert week numbers to dates or aweek objects #' #' These are vectorized functions that take integer vectors and return Date or #' an aweek objects, making it easier to convert bare weeks to dates. #' #' @param week an integer vector, defaults to 1, representing the first week of the year. #' @param year an integer vector, defaults to the current year #' @param day an integer vector, defaults to 1, representing the first day of #' the first week of the year. #' @param start an integer (or character) vector of days that the weeks #' start on for each corresponding week. Defaults to the value of #' [get_week_start()]. Note that these will not determine the final week. #' @inheritParams date2week #' @param ... parameters passed on to [date2week()] #' @return #' - get_aweek(): an aweek object #' - get_date(): a Date object #' #' @note Any missing weeks, years, or start elements will result in a #' missing element in the resulting vector. Any missing days will #' revert to the first day of the week. #' #' @seealso [as.aweek()] [date2week()] [week2date()] #' @export #' @examples #' #' # The default results in the first week of the year using the default #' # default week_start (from get_week_start()) #' #' get_aweek() #' get_date() # this is equivalent to as.Date(get_week()), but faster #' #' # Some years, like 2015, have 53 weeks #' #' get_aweek(53, 2015) #' #' # If you specify 53 weeks for a year that doesn't have 53 weeks, aweek will #' # happily correct it for you #' #' get_aweek(53, 2014) # this will be 2015-W01-1 #' #' # you can use this to quickly make a week without worrying about formatting #' # here, you can define an observation interval of 20 weeks #' #' obs_start <- get_date(week = 10, year = 2018) #' obs_end <- get_date(week = 29, year = 2018, day = 7) #' c(obs_start, obs_end) #' #' # If you have a data frame of weeks, you can use it to convert easily #' #' mat <- matrix(c( #' 2019, 11, 1, 7, # 2019-03-10 #' 2019, 11, 2, 7, #' 2019, 11, 3, 7, #' 2019, 11, 4, 7, #' 2019, 11, 5, 7, #' 2019, 11, 6, 7, #' 2019, 11, 7, 7 #' ), ncol = 4, byrow = TRUE) #' #' colnames(mat) <- c("year", "week", "day", "start") #' m <- as.data.frame(mat) #' m #' sun <- with(m, get_date(week, year, day, start)) #' sun #' as.aweek(sun) # convert to aweek starting on the global week_start #' as.aweek(sun, week_start = "Sunday") # convert to aweek starting on Sunday #' #' # You can also change starts #' mon <- with(m, get_aweek(week, year, day, "Monday", week_start = "Monday")) #' mon #' as.Date(mon) #' #' # If you use multiple week starts, it will convert to date and then to #' # the correct week, so it won't appear to match up with the original #' # data frame. #' #' sft <- with(m, get_aweek(week, year, day, 7:1, week_start = "Sunday")) #' sft #' as.Date(sft) get_aweek <- function( week = 1L, year = format(Sys.Date(), "%Y"), day = 1L, start = week_start, week_start = get_week_start(), ... ) { week_start <- parse_week_start(week_start) date2week(get_date(week = week, year = year, day = day, start = start), week_start = week_start, ...) } #' @rdname get_aweek #' @export get_date <- function( week = 1L, year = format(Sys.Date(), "%Y"), day = 1L, start = get_week_start() ) { lens <- vapply(list(week, year, day, start), FUN = length, FUN.VALUE = integer(1), USE.NAMES = FALSE) if (any(lens == 0)) { stop("all arguments must not be NULL") } # if the week_start is length one, then we can just add it as a week # attribute and be done with it. Otherwise, we will have to convert to date # and then back to week. easy_week <- lens[[4]] == 1 if (is.character(start)) { if (easy_week) { start <- weekday_from_char(start) } else { start <- vapply(start, weekday_from_char, integer(1)) } } week <- as.integer(week) year <- as.integer(year) day <- as.integer(day) start <- as.integer(start) stop_if_not_weekday(day) stop_if_not_weekday(start) stop_if_not_valid_week(week) # converting to date and then to week mat <- matrix(NA_integer_, ncol = 4L, nrow = max(lens)) colnames(mat) <- c("year", "week", "day", "week_start") mat[, "year"] <- year mat[, "week"] <- week mat[, "day"] <- day mat[, "week_start"] <- start date_from_week_matrix(mat) } aweek/R/factor_aweek.R0000644000176200001440000000254214053270040014322 0ustar liggesusers#' Coerce an aweek object to factor to include missing weeks #' #' @param x an aweek object #' @return an aweek object that inherits from [factor()] with levels that span #' the range of the weeks in the object. #' #' @note when factored aweek objects are combined with other aweek objects, they #' are converted back to characters. #' #' @export #' @examples #' w <- get_aweek(week = (1:2) * 5, year = 2019, day = c(7, 1)) #' w #' wf <- factor_aweek(w) #' wf #' #' # factors are destroyed if combined with aweek objects #' c(w, wf) factor_aweek <- function(x) { if (!inherits(x, "aweek")) { stop("x must be an 'aweek' object") } week_start <- get_week_start(x) # convert back to dates to get the first days of the week drange <- week2date(range(x, na.rm = TRUE), week_start = week_start, floor_day = TRUE) # create the sequence from the first week to the last week lvls <- seq.Date(drange[1], drange[2] + 1, by = 7L) # convert to weeks to use for levels lvls <- date2week(lvls, week_start = week_start, floor_day = TRUE, factor = FALSE, numeric = FALSE) # convert to factor xx <- factor(trunc(x), levels = lvls) attr(xx, "week_start") <- week_start class(xx) <- c("aweek", oldClass(xx)) xx } aweek/R/validators.R0000644000176200001440000000135514053270040014041 0ustar liggesusers stop_if_not_weekday <- function(x) { not_date <- !inherits(x, c("Date", "POSIXt")) if (not_date && any(!is.na(x) & (x < 1L | x > 7L))) { stop("Weekdays must be between 1 and 7") } invisible(NULL) } stop_if_not_valid_week <- function(x) { if (any(!is.na(x) & (x > 53 | x < 1))) { stop("Weeks must be between 1 and 53") } invisible(NULL) } stop_if_not_aweek_string <- function(x) { okay <- test_aweek_string(x) if (!all(okay)) { stop(sprintf( "aweek strings must match the pattern 'YYYY-Www-d'. The first incorrect string was: '%s'", x[!okay][1] )) } invisible(NULL) } test_aweek_string <- function(x) { grepl("[0-9]{4}-W(?=[0-5])[0-9]-?[1-7]?", x, perl = TRUE) | is.na(x) } aweek/R/week_start.R0000644000176200001440000000447714053270040014051 0ustar liggesusers#' Get and set the global week_start variable #' #' This is a convenience wrapper around [options()] and [getOption()], which #' allows users to input both numeric and character week start values #' #' @param x a character or integer specifying the day of the week for conversion #' between dates and weeks. #' @param w if `NULL`, the global option "aweek.week_start" is returned. If `w` #' is an aweek object, then the "week_start" attribute is returned. #' #' @return for `set_week_start`, the old value of `week_start` is returned, #' invisibly. For `get_week_start`, the current value of `week_start` is #' returned. #' #' @export #' @rdname week_start #' @seealso [change_week_start()] for changing the week_start attribute of an #' aweek object, [date2week()], [week2date()] #' @examples #' #' # get the current definition of the week start #' get_week_start() # defaults to Monday (1) #' getOption("aweek.week_start", 1L) # identical to above #' #' # set the week start #' mon <- set_week_start("Sunday") # set week start to Sunday (7) #' get_week_start() #' print(set_week_start(mon)) # reset the default #' get_week_start() #' #' # Get the week_start of a specific aweek object. #' w <- date2week("2019-05-04", week_start = "Sunday") #' get_week_start(w) set_week_start <- function(x = 1L) { if (is.character(x)) { x <- weekday_from_char(x) } if (x < 1L || x > 7L || as.integer(x) != x) { stop("week_start must be a whole number from 1 to 7, representing the days of the week.") } ows <- options(aweek.week_start = x)[[1]] if (is.null(ows)) invisible(1L) else invisible(ows) } #' @export #' @rdname week_start get_week_start <- function(w = NULL) { if (is.null(w)) { getOption("aweek.week_start", 1L) } else if (inherits(w, "aweek")) { attr(w, "week_start") } else { stop("w must be an 'aweek' object or NULL") } } #' Parse the week_start scalar #' #' This will check the length and enforce integers and non-missing #' #' @noRd parse_week_start <- function(w) { if (is.null(w)) { stop("please provide a week_start") } if (length(w) != 1) { stop("week_start must be length 1") } if (is.na(w)) { stop("week_start must not be missing") } if (is.character(w)) { w <- weekday_from_char(w) } else { w <- as.integer(w) } stop_if_not_weekday(w) return(w) } aweek/R/as.aweek.R0000644000176200001440000001275714053270040013377 0ustar liggesusers#' Convert characters or dates to aweek objects #' #' @param x a [Date][Date], [POSIXct][POSIXct], [POSIXlt][POSIXlt], or a #' correctly formatted (YYYY-Www-d) character string that represents the year, #' week, and weekday. #' @param ... arguments passed on to [date2week()] and [as.POSIXlt()] #' @inheritParams get_aweek #' @inheritParams date2week #' @return an [aweek][aweek-class] object #' #' @seealso ["aweek-class"][aweek-class] for details on the aweek object, #' [get_aweek()] for converting numeric weeks to weeks or dates, #' [date2week()] for converting dates to weeks, [week2date()] for converting #' weeks to dates. #' #' @details The `as.aweek()` will coerce character, dates, and datetime objects #' to aweek objects. Dates are trivial to convert to weeks because there is #' only one correct way to convert them with any given `week_start`. #' #' There is a bit of nuance to be aware of when converting #' characters to aweek objects: #' #' - The characters must be correctly formatted as `YYYY-Www-d`, where YYYY #' is the year relative to the week, Www is the week number (ww) prepended #' by a W, and d (optional) is the day of the week from 1 to 7 where 1 #' represents the week_start. This means that characters formatted as #' dates will be rejected. #' - By default, the `week_start` and `start` parameters are identical. If #' your data contains heterogeneous weeks (e.g. some dates will have the #' week start on Monday and some will have the week start on Sunday), then #' you should use the `start` parameter to reflect this. Internally, the #' weeks will first be converted to dates with their respective starts and #' then converted back to weeks, unified under the `week_start` parameter. #' #' @note factors are first converted to characters before they are converted to #' aweek objects. #' #' @export #' @examples #' #' # aweek objects can only be created from valid weeks: #' #' as.aweek("2018-W10-5", week_start = 7) # works! #' try(as.aweek("2018-10-5", week_start = 7)) # doesn't work :( #' #' # you can also convert dates or datetimes #' as.aweek(Sys.Date()) #' as.aweek(Sys.time()) #' #' # all functions get passed to date2week, so you can use any of its arguments: #' as.aweek("2018-W10-5", week_start = 7, floor_day = TRUE, factor = TRUE) #' as.aweek(as.Date("2018-03-09"), floor_day = TRUE, factor = TRUE) #' #' # If you have a character vector where different elements begin on different #' # days of the week, you can use the "start" argument to ensure they are #' # correctly converted. #' as.aweek(c(mon = "2018-W10-1", tue = "2018-W10-1"), #' week_start = "Monday", #' start = c("Monday", "Tuesday")) #' #' # you can convert aweek objects to aweek objects: #' x <- get_aweek() #' as.aweek(x) #' as.aweek(x, week_start = 7) as.aweek <- function(x, week_start = get_week_start(), ...) UseMethod("as.aweek") #' @export #' @rdname as.aweek as.aweek.default <- function(x, week_start = NULL, ...) { cl <- paste(class(x), collapse = ", ") stop(sprintf("There is no method to convert an object of class '%s' to an aweek object.", cl)) } #' @export #' @rdname as.aweek as.aweek.NULL <- function(x, week_start = NULL, ...) { stop("aweek objects can not be NULL") } #' @export #' @rdname as.aweek as.aweek.character <- function(x, week_start = get_week_start(), start = week_start, ...) { # Sanity checks -------------------------------------------------------------- stop_if_not_aweek_string(x) week_start <- parse_week_start(week_start) # if the week_start is length one, then we can just add it as a week # attribute and be done with it. Otherwise, we will have to convert to date # and then back to week. easy_week <- length(start) == 1 if (is.character(start)) { if (easy_week) { start <- weekday_from_char(start) } else { start <- vapply(start, weekday_from_char, integer(1)) } } stop_if_not_weekday(start) # Processing ----------------------------------------------------------------- # preserve the names nx <- names(x) if (easy_week) { # There's only one start, so we can handle it from here ^_^ attr(x, "week_start") <- start class(x) <- "aweek" if (week_start != start) { x <- change_week_start(x, week_start) } } else { # each of these characters represents a different week, so they need to be # converted separately. x <- get_aweek(week = int_week(x), year = int_year(x), day = int_wday(x), start = start, week_start = week_start ) } # ensuring the names are correct names(x) <- nx # Process extra arguments ---------------------------------------------------- .dots <- list(...) if (length(.dots) > 0) { # if the user specified options like "factor" or "floor_day" x <- do.call("date2week", c(list(x = x, week_start = week_start), .dots)) } x } #' @rdname as.aweek #' @export as.aweek.factor <- function(x, week_start = get_week_start(), ...) { as.aweek(as.character(x), week_start = week_start, ...) } #' @export #' @rdname as.aweek as.aweek.Date <- function(x, week_start = get_week_start(), ...) { date2week(x, week_start = week_start, ...) } #' @export #' @rdname as.aweek as.aweek.POSIXt <- as.aweek.Date #' @export #' @rdname as.aweek as.aweek.aweek <- function(x, week_start = NULL, ...) { if (is.null(week_start)) return(x) change_week_start(x, parse_week_start(week_start), ...) } aweek/R/week2date.R0000644000176200001440000000036714053270040013546 0ustar liggesusers#' @export #' @rdname date2week week2date <- function(x, week_start = get_week_start(), floor_day = FALSE) { if (!inherits(x, "aweek")) { x <- as.aweek(x, start = week_start, week_start = week_start) } return(as.Date(x, floor_day)) } aweek/R/as.Date.aweek.R0000644000176200001440000000132514053270040014240 0ustar liggesusers#' @export #' @rdname aweek-conversions as.Date.aweek <- function(x, floor_day = FALSE, ...) { # out <- matrix(NA_integer_, ncol = 4, nrow = length(x)) # colnames(out) <- c("year", "week", "day", "week_start") out <- template_week_matrix(length(x)) week_start <- attr(x, "week_start") x <- as.character(x) out[, "week_start"] <- week_start out[, "year"] <- int_year(x) # as.integer(substr(x, 1, 4)) out[, "week"] <- int_week(x) # as.integer(substr(x, 7, 8)) if (!floor_day) { out[, "day" ] <- int_wday(x) # as.integer(substr(x, 10, 11)) } # Convert the dates to week the_dates <- date_from_week_matrix(out) names(the_dates) <- names(x) the_dates } aweek/R/aweek-package.R0000644000176200001440000000764714053270040014370 0ustar liggesusers#' Convert dates to weeks and back again #' #' The aweek package is a lightweight solution for converting dates to weeks #' that can start on any weekday. It implements the [aweek class][aweek-class], #' which can easily be converted to date and weeks that start on different days. #' #' #' @section Before you begin: #' #' When you work with aweek, you will want to make sure that you set the default #' `week_start` variable to indicate which day of the week your weeks should #' begin. This can be done with [set_week_start()]. It will ensure that all of #' your weeks will begin on the same day. #' #' - [get_week_start()] returns the global week_start option #' - [set_week_start()] sets the global week_start option #' #' @section Conversions: #' #' \subsection{Dates to weeks}{ #' #' This conversion is the simplest because dates are unambiguous. #' #' - [date2week()] converts dates, datetimes, and characters that look like dates to weeks #' - [as.aweek()] is a wrapper around [date2week()] that converts dates and datetimes #' #' } #' \subsection{Week numbers to weeks or dates}{ #' #' If you have separate columns for week numbers and years, then this is the #' option for you. This allows you to specify a different start for each week #' element using the `start` argument. #' #' - [get_aweek()] converts week numbers (with years and days) to [aweek objects][aweek-class]. #' - [get_date()] converts week numbers (with years and days) to [Dates][Date]. #' #' } #' \subsection{ISO week strings (YYYY-Www-d or YYYY-Www) to weeks or dates}{ #' #' - [as.aweek()] converts ISO-week formatted strings to [aweek objects][aweek-class]. #' - [week2date()] converts ISO-week formatted strings to [Date][Date]. #' #' } #' \subsection{aweek objects to dates or datetimes}{ #' #' This conversion is simple for [aweek][aweek-class] objects since their #' week_start is unambiguous #' #' - [as.Date()][as.Date.aweek()] converts to [Date][Date]. #' - [as.POSIXlt()][as.POSIXlt.aweek()] converts to [POSIXlt][POSIXlt]. #' #' } #' #' \subsection{aweek objects to characters}{ #' #' You can strip the week_start attribute of the aweek object by converting to #' a character with [as.character()] #' #' } #' @section Manipulating aweek objects: #' #' - [trunc()] removes the weekday element of the ISO week string. #' - [factor_aweek()] does the same thing as trunc(), but will create a factor #' with levels spanning all the weeks from the first week to the last week. #' Useful for creating tables with zero counts for unobserved weeks. #' - [change_week_start()] will change the week_start attribute and adjust the #' weeks accordingly so that the dates will always be consistent. #' #' When you combine aweek objects, they must have the same week_start attribute. #' Characters can be added to aweek objects as long as they are in ISO week #' format and you can safely assume that they start on the same weekday. Dates #' are trivial to add to aweek objects. See the [aweek-class][aweek-class] #' documentation for details. #' #' #' @name aweek-package #' @docType package #' @examples #' # At the beginning of your analysis, set the week start to the weeks you want #' # to use for reporting #' ow <- set_week_start("Sunday") #' #' # convert dates to weeks #' d <- as.Date(c("2014-02-11", "2014-03-04")) #' w <- as.aweek(d) #' w #' #' # get the week numbers #' date2week(d, numeric = TRUE) #' #' # convert back to date #' as.Date(w) #' #' # convert to factor #' factor_aweek(w) #' #' # append a week #' w[3] <- as.Date("2014-10-31") #' w #' #' # change week start variable (if needed) #' change_week_start(w, "Monday") #' #' # note that the date remains the same #' as.Date(change_week_start(w, "Monday")) #' #' # Don't forget to reset the week_start at the end #' set_week_start(ow) "_PACKAGE" # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start ## usethis namespace: end NULL aweek/R/date2week.R0000644000176200001440000002077414317406427013567 0ustar liggesusers#' Convert date to a an arbitrary week definition #' #' @param x a [Date], [POSIXt], [character], or any data that can be easily #' converted to a date with [as.POSIXlt()]. #' #' @param week_start a number indicating the start of the week based on the ISO #' 8601 standard from 1 to 7 where 1 = Monday OR an abbreviation of the #' weekdate in an English or current locale. _Note: using a non-English locale #' may render your code non-portable._ Defaults to the value of #' [get_week_start()] #' #' @param floor_day when `TRUE`, the days will be set to the start of the week. #' #' @param numeric if `TRUE`, only the numeric week be returned. If `FALSE` #' (default), the date in the format "YYYY-Www-d" will be returned. #' #' @param factor if `TRUE`, a factor will be returned with levels spanning the #' range of dates. This should only be used with `floor_day = TRUE` to #' produce the sequence of weeks between the first and last date as the #' factor levels. Currently, `floor_date = FALSE` will still work, but will #' produce a message indicating that it is deprecated. _Take caution when #' using this with a large date range as the resulting factor can contain all #' days between dates_. #' #' @param ... arguments passed to [as.POSIXlt()], unused in all other cases. #' #' @details Weeks differ in their start dates depending on context. The ISO #' 8601 standard specifies that Monday starts the week #' () while the US CDC uses #' Sunday as the start of the week #' (). For #' example, MSF has varying start dates depending on country in order to #' better coordinate response. #' #' While there are packages that provide conversion for ISOweeks and epiweeks, #' these do not provide seamless conversion from dates to epiweeks with #' non-standard start dates. This package provides a lightweight utility to #' be able to convert each day. #' #' @return #' - `date2week()` an [aweek][aweek-class] object which represents dates in #' `YYYY-Www-d` format where `YYYY` is the year (associated with the week, #' not necessarily the day), `Www` is the week number prepended by a "W" that #' ranges from 01-53 and `d` is the day of the week from 1 to 7 where 1 #' represents the first day of the week (as defined by the `week_start` #' attribute). #' - `week2date()` a [Date][Date] object. #' #' @note `date2week()` will initially convert the input with [as.POSIXlt()] and #' use that to calculate the week. If the user supplies character input, it #' is expected that the input will be of the format yyyy-mm-dd _unless_ the #' user explicitly passes the "format" parameter to [as.POSIXlt()]. If the #' input is not in yyyy-mm-dd and the format parameter is not passed, #' `date2week()` will result in an error. #' #' @author Zhian N. Kamvar #' @export #' @seealso [set_week_start()], [as.Date.aweek()], [print.aweek()], [as.aweek()], #' [get_aweek()] #' @examples #' #' ## Dates to weeks ----------------------------------------------------------- #' #' # The same set of days will occur in different weeks depending on the start #' # date. Here we can define a week before and after today #' #' print(dat <- as.Date("2018-12-31") + -6:7) #' #' # By default, the weeks are defined as ISO weeks, which start on Monday #' print(iso_dat <- date2week(dat)) #' #' # This can be changed by setting the global default with set_week_start() #' #' set_week_start("Sunday") #' #' date2week(dat) #' #' # If you want lubridate-style numeric-only weeks, you need look no further #' # than the "numeric" argument #' date2week(dat, numeric = TRUE) #' #' # To aggregate weeks, you can use `floor_day = TRUE` #' date2week(dat, floor_day = TRUE) #' #' # If you want aggregations into factors that include missing weeks, use #' # `floor_day = TRUE, factor = TRUE`: #' date2week(dat[c(1, 14)], floor_day = TRUE, factor = TRUE) #' #' #' ## Weeks to dates ----------------------------------------------------------- #' #' # The aweek class can be converted back to a date with `as.Date()` #' as.Date(iso_dat) #' #' # If you don't have an aweek class, you can use week2date(). Note that the #' # week_start variable is set by the "aweek.week_start" option, which we will #' # set to Monday: #' #' set_week_start("Monday") #' week2date("2019-W01-1") # 2018-12-31 #' #' # This can be overidden by the week_start argument; #' week2date("2019-W01-1", week_start = "Sunday") # 2018-12-30 #' #' # If you want to convert to the first day of the week, you can use the #' # `floor_day` argument #' as.Date(iso_dat, floor_day = TRUE) #' #' ## The same two week timespan starting on different days -------------------- #' # ISO week definition: Monday -- 1 #' date2week(dat, 1) #' date2week(dat, "Monday") #' #' # Tuesday -- 2 #' date2week(dat, 2) #' date2week(dat, "Tuesday") #' #' # Wednesday -- 3 #' date2week(dat, 3) #' date2week(dat, "W") # you can use valid abbreviations #' #' # Thursday -- 4 #' date2week(dat, 4) #' date2week(dat, "Thursday") #' #' # Friday -- 5 #' date2week(dat, 5) #' date2week(dat, "Friday") #' #' # Saturday -- 6 #' date2week(dat, 6) #' date2week(dat, "Saturday") #' #' # Epiweek definition: Sunday -- 7 #' date2week(dat, 7) #' date2week(dat, "Sunday") date2week <- function(x, week_start = get_week_start(), floor_day = factor, numeric = FALSE, factor = FALSE, ...) { # Cromulence checks ---------------------------------------------------------- format_exists <- !is.null(list(...)$format) nas <- is.na(x) nams <- names(x) week_start <- parse_week_start(week_start) if (!inherits(x, "aweek") && is.character(x) && !format_exists) { iso_std <- grepl("^[0-9]{4}[^[:alnum:]]+[01][0-9][^[:alnum:]]+[0-3][0-9]$", trimws(x)) iso_std[nas] <- TRUE # prevent false alarms if (!all(iso_std)) { msg <- paste("Not all dates are in ISO 8601 standard format (yyyy-mm-dd).", "The first incorrect date is %s" ) stop(sprintf(msg, x[!iso_std][1])) } } x <- tryCatch(as.POSIXlt(x, ...), error = function(e) e) if (inherits(x, "error")) { mc <- match.call() msg <- "%s could not be converted to a date. as.POSIXlt() returned this error:\n%s" stop(sprintf(msg, deparse(mc[["x"]]), x$message)) } # Conversion from date to week ----------------------------------------------- # # Step 1: Get the weekday as an integer in pseudo ISO 8601 --------- wday <- as.integer(x$wday) # weekdays in R run 0:6, 0 being Sunday the_date <- as.Date.POSIXlt(x) # Step 2: Find week and weekday ------------------------------------ # This part is important for accurately assessing the week. It shifts the date # to the middle of the relative week so that it can accurately be counted. # # If the weekday is in the first three days of the week, then the middle of # the week is in the future, but if the weekday is in the last three days of # the week, then the middle of the week has already past and the date needs # to be shifted backward. wday <- get_wday(wday, week_start) midweek <- the_date + (4L - wday) the_week <- week_in_year(midweek) if (!numeric) { # Adding years to weeks ---------------------------------------------------- # Step 1: find the months of each date ----------------------------- december <- format(the_date, "%m") == "12" january <- format(the_date, "%m") == "01" boundary_adjustment <- integer(length(the_date)) # Step 2: Shift the years based on month/week agreement ------------ # Shift the year backwards if the date is in january, but the week is not boundary_adjustment[january & the_week >= 52] <- -1L # Shift the year forwards if the date is in december, but it's the first week boundary_adjustment[december & the_week == 1] <- 1L # Step 3: Create ISO week strings ---------------------------------- the_year <- as.integer(format(the_date, "%Y")) the_week <- sprintf("%04d-W%02d-%d", the_year + boundary_adjustment, the_week, wday ) # set the missing data back to missing ----------------------------- the_week[nas] <- NA class(the_week) <- c("aweek", oldClass(the_week)) attr(the_week, "week_start") <- week_start if (floor_day) { the_week <- trunc(the_week) } if (factor) { if (!floor_day) { stop("as of aweek 1.0, using factor without floor_day is not allowed.") } the_week <- factor_aweek(the_week) } } names(the_week) <- names(x) the_week } aweek/R/weekday_from_char.R0000644000176200001440000000253414053270040015342 0ustar liggesusers#' Helper function to find the weekday from a character string #' #' @param x a character string specifying the weekday in the current locale or #' English. #' #' @return an integer from 1 to 7 indicating the day of the ISO 8601 week. #' @keywords internal #' @noRd #' @examples #' #' # Will always work #' weekday_from_char("Monday") #' weekday_from_char("Tue") #' weekday_from_char("W") #' #' # Change to a German locale #' lct <- Sys.getlocale("LC_TIME") #' Sys.setlocale("LC_TIME", "de_DE.utf8") #' #' weekday_from_char("Sonntag") #' #' # Reset locale #' Sys.setlocale("LC_TIME", lct) weekday_from_char <- function(x) { # First try with an English locale w <- c("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday") weekdate <- grep(x, w, ignore.case = TRUE) if (length(weekdate) == 0) { # find the definitions of the weekdays in the current locale w <- weekdays(as.Date(get_aweek(day = 1:7, week_start = 1L))) weekdate <- grep(x, w, ignore.case = TRUE) } if (length(weekdate) != 1) { msg <- paste("The weekday '%s' did not match any of the valid weekdays in", "the current locale ('%s') or an English locale:\n %s") stop(sprintf(msg, x, Sys.getlocale('LC_TIME'), paste(w, collapse = ", ")), call. = FALSE) } return(as.integer(weekdate)) } aweek/R/aweek-methods.R0000644000176200001440000002115114317406457014442 0ustar liggesusers#' The aweek class #' #' The aweek class is a character or factor in the format YYYY-Www(-d) with a #' "week_start" attribute containing an integer specifying which day of the ISO #' 8601 week each week should begin. #' #' @param x an object of class `aweek` #' @param ... a series of `aweek` objects, characters, or Dates, (unused in `print.aweek()`) #' @param recursive,use.names parameters passed on to [unlist()] #' #' @return an object of class `aweek` #' #' @details Weeks differ in their start dates depending on context. The ISO #' 8601 standard specifies that Monday starts the week #' () while the US CDC uses #' Sunday as the start of the week #' (). For #' example, MSF has varying start dates depending on country in order to #' better coordinate response. #' #' While there are packages that provide conversion for ISOweeks and epiweeks, #' these do not provide seamless conversion from dates to epiweeks with #' non-standard start dates. This package provides a lightweight utility to #' be able to convert each day. #' #' \subsection{Calculation of week numbers}{ #' #' Week numbers are calculated in three steps: #' #' 1. Find the day of the week, relative to the week_start (d). The day of the #' week (d) relative to the week start (s) is calculated using the ISO week #' day (i) via `d = 1L + ((i + (7L - s)) %% 7L)`. #' 2. Find the date that represents midweek (m). The date that represents #' midweek is found by subtracting the day of the week (d) from 4 and #' adding that number of days to the current date: `m = date + (4 - d)`. #' 3. Find the week number (w) by counting the number of days since 1 January #' to (m), and use integer division by 7: `w = 1L + ((m - yyyy-01-01) %/% 7)` #' #' For the weeks around 1 January, the year is determined by the week number. #' If the month is January, but the week number is 52 or 53, then the year for #' the week (YYYY) is the calendar year (yyyy) minus 1. However, if the month #' is December, but the week number is 1, then the year for the week (YYYY) is #' the calendar year (yyyy) plus 1. #' #' } #' \subsection{Structure of the aweek object}{ #' #' The aweek object is a character vector in either the precise ISO week #' format (YYYY-Www-d) or imprecise ISO week format (YYYY-Www) with #' a `week_start` attribute indicating which ISO week day the week begins. #' The precise ISO week format can be broken down like this: #' #' - **YYYY** is an ISO week-numbering year, which is the year relative to #' the week, not the day. For example, the date 2016-01-01 would be #' represented as 2015-W53-5 (ISO week), because while the date is in the #' year 2016, the week is still part of the final week of 2015. #' - W**ww** is the week number, prefixed by the character "W". This ranges #' from 01 to 52 or 53, depending on whether or not the year has 52 or 53 #' weeks. #' - **d** is a digit representing the weekday where 1 represents the first #' day of the week and 7 represents the last day of the week. #' #' The attribute `week_start` represents the first day of the week as an ISO #' week day. This defaults to 1, which is Monday. If, for example, an aweek #' object represented weeks starting on Friday, then the `week_start` #' attribute would be 5, which is Friday of the ISO week. #' #' Imprecise formats (YYYY-Www) are equivalent to the first day of the week. #' For example, 2015-W53 and 2015-W53-1 will be identical when converted to #' date. #' #' } #' #' @note when combining aweek objects together, you must ensure that they have #' the same week_start attribute. You can use [change_week_start()] to adjust #' it. #' #' #' @export #' @aliases aweek-class #' @rdname aweek-class #' @seealso [date2week()], [get_aweek()], [as.Date.aweek()], [change_week_start()] #' @examples #' d <- as.Date("2018-12-20") + 1:40 #' w <- date2week(d, week_start = "Sunday") #' print(w) #' #' # subsetting acts as normal #' w[1:10] #' #' # Combining multiple aweek objects will only work if they have the same #' # week_start day #' c(w[1], w[3], w[5], as.aweek(as.Date("2018-12-01"), week_start = "Sunday")) #' #' # differing week_start days will throw an error #' mon <- date2week(as.Date("2018-12-01"), week_start = "Monday") #' mon #' try(c(w, mon)) #' #' # combining Dates will be coerced to aweek objects under the same rules #' c(w, Sys.Date()) #' #' # truncated aweek objects will be un-truncated #' w2 <- date2week(d[1:5], week_start = "Sunday", floor_day = TRUE) #' w2 #' c(w[1:5], w2) print.aweek <- function(x, ...) { tmp <- week2date("2019-W08-1", attr(x, "week_start")) cat(sprintf("\n", format(tmp, "%A"))) y <- x attr(x, "week_start") <- NULL class(x) <- class(x)[class(x) != "aweek"] NextMethod("print") invisible(y) } #' @export #' @param i index for subsetting an aweek object. #' @rdname aweek-class `[.aweek` <- function(x, i) { cl <- oldClass(x) ws <- attr(x, "week_start") xx <- NextMethod("[") attr(xx, "week_start") <- ws oldClass(xx) <- cl xx } #' @export #' @rdname aweek-class `[[.aweek` <- function(x, i) { cl <- oldClass(x) ws <- attr(x, "week_start") xx <- NextMethod("[[") attr(xx, "week_start") <- ws oldClass(xx) <- cl xx } #' @export #' @param value a value to add or replace in an aweek object #' @rdname aweek-class `[<-.aweek` <- function(x, i, value) { ws <- attr(x, "week_start") cl <- oldClass(x) if (inherits(value, "aweek")) { if (ws != attr(value, "week_start")) { stop("aweek objects must have the same week_start attribute") } } if (inherits(value, "character")) { value <- as.aweek(value, week_start = ws) } if (inherits(value, "factor")) { value <- as.character(value) stop_if_not_aweek_string(value) value <- get_aweek(week = int_week(value), year = int_year(value), day = int_wday(value), week_start = ws) } if (inherits(value, c("Date", "POSIXt"))) { value <- date2week(value, week_start = ws) } if (!is.null(value) && all(is.na(value))) { value <- as.aweek(as.character(value), week_start = ws) } if (!inherits(value, "aweek")) { stop(sprintf("Cannot add an object of class '%s' to an aweek object", paste(class(value), collapse = ", "))) } xx <- NextMethod("[") attr(xx, "week_start") <- ws oldClass(xx) <- cl xx } #' @export #' @rdname aweek-class as.list.aweek <- function(x, ...) { xx <- NextMethod("as.list") xx <- lapply(xx, function(i, ws, cl){ attr(i, "week_start") <- ws oldClass(i) <- cl i }, ws = attr(x, "week_start"), cl = oldClass(x)) xx } #' @export #' @rdname aweek-class trunc.aweek <- function(x, ...) { if (inherits(x, "factor")) { levels(x) <- gsub("\\-\\d", "", levels(x)) } else { x <- gsub("\\-\\d", "", x) } x } #' @export #' @rdname aweek-class rep.aweek <- function(x, ...) { ws <- attr(x, "week_start") cl <- oldClass(x) xx <- NextMethod("rep") attr(xx, "week_start") <- ws oldClass(xx) <- cl xx } #' @export #' @rdname aweek-class c.aweek <- function(..., recursive = FALSE, use.names = TRUE) { # Find all the aweek objects and test that they all have the same week_start # attribute. Throw an error if this isn't true the_dots <- list(...) week_start <- get_week_start(the_dots[[1]]) aweeks <- vlogic(the_dots, inherits, "aweek") identical_week_starts <- vapply(the_dots[aweeks], get_week_start, integer(1)) == week_start if (!all(identical_week_starts)) { stop("All aweek objects must have the same week_start attribute. Please use change_week_start() to adjust the week_start attribute if you wish to combine these objects.") } # Find all the dates and convert them to aweek objects dates <- vlogic(the_dots, inherits, c("Date", "POSIXt")) the_dots[dates] <- lapply(the_dots[dates], date2week, week_start = week_start) # convert everything to characters and unlist them res <- unlist(lapply(the_dots, as.character), recursive = recursive, use.names = FALSE) date_chars <- grepl("[0-9]{4}-[0-9]{2}-[0-9]{2}", res, perl = TRUE) res[date_chars] <- as.character(date2week(res[date_chars], week_start = week_start)) # convert the characters to aweek objects out <- get_aweek(week = int_week(res), year = int_year(res), day = int_wday(res), start = week_start, week_start = week_start ) names(out) <- names(res) out } aweek/NEWS.md0000644000176200001440000001236014317167547012465 0ustar liggesusers# aweek 1.0.3 ## CRAN MAINTENANCE * `as.POSIXlt()` method for aweek objects no longer converts dates to character before converting to POSIXlt # aweek 1.0.2 ## CRAN MAINTENANCE * A test that was failing due to an incorrect assumption that the first day of the year would always fall on the first week of the year was fixed (see https://github.com/reconhub/aweek/issues/35 for details). (#36, @zkamvar). ## MISC * Continuous integration has been migrated from Travis-CI + AppVeyor to GitHub actions. # aweek 1.0.1 ## CRAN MAINTENANCE * `as.POSIXlt()` now converts NULL to an empty POSIXlt object, so a test that was previously checking for an error failed. That test has been fixed (#33, @zkamvar) # aweek 1.0.0 ## NEW FUNCTIONS * `change_week_start()` allows the user to change the `week_start` attribute of an aweek object, adjusting the weeks to match the new attribute accordingly. * `get_aweek()` can generate aweek objects from a vector of week numbers. It has the ability to take into account different week start times. * `get_date()` is similar to `get_aweek()`, but returns dates instead. * `as.aweek()` allows users to create aweek object directly from characters with validation. It also allows for dates by passing to `date2week()`. * `as.data.frame.aweek()` is a new function that allows aweek objects to be directly incorporated into data frames. * `as.list.aweek()` will now preserve the aweek structure in lists * `trunc.aweek()` will truncate the day to the first day of the week. * `rep.aweek()` allows repeating aweek characters. * `factor_aweek()` allows the user to create aggregated aweek objects on the fly. ## BREAKING CHANGES There are a couple of breaking changes coming to aweek that will improve stability by removing unclear coercion methods (see https://github.com/reconhub/aweek/issues/20). * It is no longer possible to combine aweek objects if they do not share the same `week_start` attribute. This will result in an error informing the user to adjust the `week_start` attribute with the `change_week_start()` function. * Factors will no longer coerce factors when combining aweek objects. * Using `date2week()` with `factor = TRUE` and `floor_day = FALSE` now throws an error instead of a warning (as prophesized in #13). ## DOCUMENTATION * The vignette has been updated to include an explanation of the underlying week calculation from date. * The aweek class documentation has been updated to detail the different elements of the object and the calculations. * Package documentation `package?aweek` has been added for an introduction. ## BUG FIX * conversions will now retain the names of the object. ## MISC * More checks that weekdays and weeks are within the correct bounds have been added. * as.Date.aweek has been simplified to no longer rely on regex since the aweek class is standard. * The internal `get_wday()` has been vastly simplified with improved speed. # aweek 0.3.0 * The `week_start` argument now defaults to the global option `aweek.week_start`, which will be a number from 1 to 7, representing the days of the week in the ISO 8601 standard. * `set_week_start()` is a convenience allowing the user to set the default `aweek.week_start` option via integer or character input. * `get_week_start()` is a wrapper for `getOption("aweek.week_start", 1L)` and `attr(w, "week_start")` for aweek objects. # aweek 0.2.2 * Simplified conversion to factors. * Using `factor = TRUE` without `floor_day = TRUE` will now issue a message indicating that this is deprecated in future versions of aweek (see #13). # aweek 0.2.1 * Fix bug where NAs threw errors in the dates (found: @aspina7, #12) * `as.data.frame.aweek()` will now convert aweek objects to columns of data frames without losing class or attributes * The introduction vignette has been updated to reflect this change. # aweek 0.2.0 * Subsetting and concatenating methods added to the `aweek` class (see #1) * Documentation divided into smaller chunks * `as.POSIXlt()` bug where `tz` was not being passed was fixed. * `date2week()`: an error is now issued if users specify non-ISO 8601 dates OR don't specify a `format` option. (found: @scottyaz, #2) * Best practices added to vignette * Fix test that would fail every seven days on CRAN # aweek 0.1.0 * First official release of aweek on CRAN # aweek 0.0.5.9000 * `date2week()` and `week2date()` can now take days represented as characters in the current or English locale. # aweek 0.0.4.9000 * `date2week()` gains a `factor` argument, which will automatically compute the levels within the date range. # aweek 0.0.3.9000 * `date2week()` now properly accounts for dates in December that occur in the first week of the next year. # aweek 0.0.2.9000 * `floor_day` now truncates the week instead changing the last digit to 1 for aesthetics. (Thanks to @aspina7 for the suggestion) q `print.aweek()` now displays the day of the week in the current locale. # aweek 0.0.1.9000 * First version of package * `date2week()` converts dates to `aweek` objects * `week2date()` converts `aweek` objects or character strings to dates * `as.Date()` does the same thing as above * `as.POSIXlt()` as well * `as.character()` will unclass the `aweek` object * Added a `NEWS.md` file to track changes to the package. aweek/MD50000644000176200001440000000515614317444553011677 0ustar liggesusersc149b7fbf82a412e7792b64c0d018b9b *DESCRIPTION 04de04634279483349c6d84fc4142e97 *LICENSE 0258ce26805047b8eddbfac00518de7e *NAMESPACE 68f1acbb49761ffbcefdf51c7271957c *NEWS.md 0a7ce83f25680056f651584a764aeea7 *R/as.Date.aweek.R 9875edd647f5f402558dbafad2c596c2 *R/as.aweek.R 3d9272b0943732728e439ab67f819602 *R/as.data.frame.aweek.R f0b2ab10db2d3072d4329e59b6227692 *R/aweek-methods.R 2a22a4b06c79b1bf9b0a92ae111dd0f0 *R/aweek-package.R 334d7fb9aea1eecdf311d57c9051e0b3 *R/change_week_start.R 6aadf08aa227ee2299d68629ef5ceb68 *R/conversions.R a92b875d372dcb5d1dd5b17242319843 *R/date2week.R ff3478e87b2c9b0d41f63a82a3192cc9 *R/factor_aweek.R 50c62f4f91762cca038703a536212212 *R/get_aweek.R 9c026d085b6541016eec8b84ba8fcc0e *R/utils.R b6a2cd267e1ba623e4e0143f3d5d4b02 *R/validators.R 484100d94992eb57aa4bf6fb2b569444 *R/week2date.R b48ca3a551a369d8268b12588d3bb125 *R/week_start.R 8849c30404efd60c227ab90c2d459226 *R/weekday_from_char.R ce52baedf65a947555381d567c1b7c32 *build/vignette.rds 15821876ab2dfcb2cf107089da15a359 *inst/WORDLIST 598b097c30b4da61fd1012755de6f102 *inst/doc/introduction.R f15a4765c9b3f7aae802cd78e351d227 *inst/doc/introduction.Rmd 0efc995a9d09e9eed1eee705fa59bbde *inst/doc/introduction.html 3539b2dd084df3992f9378e9998c177a *man/as.aweek.Rd 4f9578d76742fd23960e41336f14a91e *man/as.data.frame.aweek.Rd 09d3acb4cf5e84dfb39566b95ff4ff2f *man/aweek-class.Rd d67178615aa1295f0df4cb720a7cee00 *man/aweek-conversions.Rd 3e6b659f3cafc7e58258c7637c655117 *man/aweek-package.Rd ff6f015e8e0fdc4f0be3db758bf945e3 *man/change_week_start.Rd fc0b3161e363277970e073b9e0977728 *man/date2week.Rd 1e6fe51e6e77b1663241d6a19d939be7 *man/factor_aweek.Rd 2e72166e9b5a82cf937e109b1c479e2e *man/get_aweek.Rd c3bc6dfbe063ccc9dece9ac320621375 *man/week_start.Rd 8e1b2c37a4d07e22fa6ee05df09223e5 *tests/spelling.R 541466147e2a5e0bb06cecc3e2a0ff90 *tests/testthat.R 4df6f8908d8fb9cc5e35cf661f2ab834 *tests/testthat/test-as.aweek.R 585f9503dd379d902fec12a6b1538ea3 *tests/testthat/test-as.data.frame.R 813ebcc9c431d60825ee0f19f717c2fe *tests/testthat/test-change_week_start.R 6c022f3ba866de8d9a73194aaa7a8c81 *tests/testthat/test-conversion.R e12c4618cb422176afc4d0b1f7a086fa *tests/testthat/test-date2week.R e581eb66f15b55d6bacb0156d48fc2f1 *tests/testthat/test-examples.R c8798efcb64da668c1ff22b11f11cda3 *tests/testthat/test-factor_aweek.R f964bed74dde2e34912722d3b74568a9 *tests/testthat/test-make_aweek.R 468af857792d6f88054eb3c3d8a0e761 *tests/testthat/test-subset.R cd00ab8fa821d45fb73b8f3e24e7995f *tests/testthat/test-week_start.R 97c7c139fa7f7cbee4e765692632a0de *tests/testthat/test-weekday-conversion.R f15a4765c9b3f7aae802cd78e351d227 *vignettes/introduction.Rmd aweek/inst/0000755000176200001440000000000014317440023012321 5ustar liggesusersaweek/inst/doc/0000755000176200001440000000000014317440023013066 5ustar liggesusersaweek/inst/doc/introduction.Rmd0000644000176200001440000003100614317437750016270 0ustar liggesusers--- title: "aweek means 'any week'" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: toc: true toc_depth: 2 vignette: > %\VignetteIndexEntry{aweek means 'any week'} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- Introduction ============ The day in which a week starts differs depending on context. For countries like the UK, the first day of the week is the first working day, which is Monday. This definition conforms with the [ISO 8601 standard definition for the beginning of a week](https://en.wikipedia.org/wiki/ISO_week_date), but there are examples of situations where the first day of the week is different: - [The US CDC defines an "MMWR" week which starts on a Sunday](https://stacks.cdc.gov/view/cdc/22305). - In some regions, MSF will define a week that starts on Saturday This package provides tools to convert dates to weeks and back where a week can start on any day. You can use this package for any of the following: - convert date to week starting on any day - convert week numbers and years to dates - convert week to date - convert week to week - create a factor of weeks that contains ordered levels that includes missing weeks. Converting dates to weeks ========================= You can convert dates to weeks starting on any day by using `date2week()` with the `week_start` argument. This argument can be a number from 1 to 7 representing the ISO 8601 day of the week OR it can be a string representing the day of the week in either an English locale or the locale defined on your computer. The default of this argument is the value of `get_week_start()`, which is a thin wrapper around `options("aweek.week_start", 1L)`. **Unless you have specified a default `aweek.week_start` option with `set_week_start()`, this will always be set to 1 (Monday).** > It is **highly recommended** that you set the default `aweek.week_start` either > in the beginning of your Rscript, Rmarkdown document, or in your .Rprofile. ```{r date2week} library("aweek") set_week_start("Sunday") # setting the default week_start to Sunday set.seed(2019-03-03) dat <- as.Date("2019-03-03") + sample(-6:7, 10, replace = TRUE) dat print(w <- date2week(dat)) ``` If you need a different day on the fly, you can supply an integer or character day to the `week_start` argument. ```{r date2week_week_start} # Use character days date2week(dat, week_start = "Monday") # Use ISO 8601 days date2week(dat, week_start = 1) ``` If you want to save two extra keystrokes, you can also use the `as.aweek()` method for dates, which wraps `date2week()`: ```{r as.aweek.Date} as.aweek(dat, week_start = 1) ``` What you get back is an `aweek` class object. It can be converted back to a date with either `as.Date()` or `week2date()`: ```{r date2week2date} week2date(w) as.Date(w) ``` How does it work? ----------------- The calculation of weeks from dates requires knowledge of the current day of the week and the number of days past 1 January. Week numbers are calculated in three steps: 1. Find the day of the week, relative to the week_start (d). The day of the week (d) relative to the week start (s) is calculated using the ISO week day (i) via `d = 1L + ((i + (7L - s)) %% 7L)`. 2. Find the date that represents midweek (m). The date that represents midweek is found by subtracting the day of the week (d) from 4 and adding that number of days to the current date: `m = date + (4 - d)`. 3. Find the week number (w) by counting the number of days since 1 January to (m), and use integer division by 7: `w = 1L + ((m - yyyy-01-01) %/% 7)` For example, here's how to calculate the week for Tuesday, 6 December 2016, assuming the week start is a Sunday: ```{r example_day} the_date <- as.Date("2016-12-06") jan_1 <- as.Date("2016-01-01") i <- as.POSIXlt(the_date)$wday # 2, the ISO date for Tuesday s <- 7L # week_start for sunday # 1. Find the day of the week print(d <- 1L + ((i + (7L - s)) %% 7L)) # 2. Find the date that represents midweek print(m <- the_date + (4L - d)) # 3. Find the week print(w <- 1L + as.integer(m - jan_1) %/% 7L) # Format the week sprintf("2016-W%02d-%d", w, d) ``` For the weeks around 1 January, the year is determined by the week number. If the month is January, but the week number is 52 or 53, then the year for the week (YYYY) is the calendar year (yyyy) minus 1. However, if the month is December, but the week number is 1, then the year for the week (YYYY) is the calendar year (yyyy) plus 1. The `aweek` class --------------- The result you see above is an object of class "aweek". The `aweek` class is a character that contains the `week_start` attribute. This attribute allows it to be easily converted back to a date without the user needing to enter the start day every time. You can convert a character that matches the `YYYY-Www-d` pattern to an `aweek` class object with `as.aweek()`: ```{r as.aweek.character} x <- as.aweek("2019-W10-1") x ``` > Under the hood, it checks the validity of the week string and then add the > attribute and class: > > x <- "2019-W10-1" > attr(x, "week_start") <- 7 # Sunday > class(x) <- "aweek" > If you need to remove the class, you can just use `as.character()`: ```{r ascharacter} as.character(x) ``` Best practices -------------- The `date2week()` function only checks that dates are in ISO 8601 (yyyy-mm-dd) format before converting to weeks, *and otherwise assumes that the dates are accurate* so it's strongly recommended to make sure your dates are in either `Date` or `POISXt` format and accurate before converting to weeks. The [lubridate](https://cran.r-project.org/package=lubridate) can be used for this purpose. Use `set_week_start()` at the beginning of all your scripts to explicitly define the day on which your weeks start. This can be overridden if need be in specific parts of your scripts. Otherwise, the default will be dependent on the value of `getOption("aweek.week_start", 1L)`. Because the `week_start` arguments default to `get_week_start()`, it's recommended to specify `week_start` in `date2week()` and `week2date()` if you don't have an `aweek` object. Before you combine aweek objects, confirm that they are actually aweek objects with `inherits(myObject, "aweek")`. Weekly aggregation ------------------ There are times where you would want to aggregate your days into weeks, you can do this by specifying `floor_day = TRUE` in `date2week()`. For example, here we can show the individual weeks: ```{r date2week_floor} print(wf <- date2week(dat, week_start = "Saturday", floor_day = TRUE)) table(wf) ``` If you convert this to date, then all the dates will represent the beginning of the week: ```{r date2week_floor2date} print(dwf <- week2date(wf)) weekdays(dwf) ``` If you want to aggregate your `aweek` objects after you created them, you can always use the `trunc()` function: ```{r trunc} w <- date2week(dat) w trunc(w) ``` Factors ------- Weeks can be represented as factors, which is useful for tabulations across weeks. You can use `factor = TRUE` in `date2week()` and it will automatically fill in any missing weeks. ```{r factors} dat[1] + c(0, 15) date2week(dat[1] + c(0, 15), week_start = 1, factor = TRUE) ``` If you already have an aweek object and want to convert it to a factor, you can use `factor_aweek()`: ```{r factor_aweek} factor_aweek(w) ``` Be careful when combining factors with other dates or aweek objects as they will force the other objects to be truncated as well. Weeks to weeks -------------- You can use `change_week_start()` to convert between different week definitions if you have an `aweek` object: ```{r week2week_wednesday} w # week starting on Sunday ww <- change_week_start(w, week_start = "wednesday") # same dates, starting on Wednesday ww identical(as.Date(w), as.Date(ww)) ``` ```{r week2week, R.options=list(width = 100)} # create a table with all days in the week d <- as.Date("2019-03-03") + 0:6 res <- lapply(weekdays(d), function(i) date2week(d, week_start = i)) names(res) <- weekdays(d) data.frame(res) ``` All of these columns contain the same dates: ```{r week2week2date, R.options=list(width = 100)} data.frame(lapply(res, as.Date)) ``` Combining `aweek` objects ----------------------- You can add dates, aweek objects, or characters to aweek objects: ```{r caweekdate} c(as.aweek("2010-W10-1"), res$Sunday, "2010-W12-1", as.Date(res$Monday[1]) + 14) ``` However, you *can not* combine aweek objects with different `week_start` attributes. ```{r cweek2week_err, error = TRUE} c(res$Sunday[1], res$Wednesday[2], res$Friday[3]) ``` If you want to combine different aweek objects, you must first change their `week_start` attribute: ```{r cweekweek} wed <- change_week_start(res$Wednesday, get_week_start()) fri <- change_week_start(res$Friday, get_week_start()) c(res$Sunday[1], wed[2], fri[3]) ``` ### Dates can be appended to aweek objects Dates combined with aweek objects will will be automatically converted. ```{r add_dates} c(res$Monday, as.Date("2019-04-03")) ``` ### Add characters with caution You can also add character representation of weeks, but be aware that **it is assumed that these have the same `week_start` as the first object.** ```{r add_chars} s <- c(res$Saturday, "2019-W14-3") s m <- c(res$Monday, "2019-W14-3") m ``` **These will translate into different dates** ```{r char2date} as.Date(s[7:8]) as.Date(m[7:8]) ``` Working with weeks in data frames ================================= You may encounter a situation where you have a merged data frame with weeks starting on different days. This section will cover two situations where you may have weeks as numbers and weeks as ISO-week strings. First we will create our demonstration data that represents the same week with different `week_start` attributes. ```{r date_a_frame} # create a table with all days in the week d <- as.Date("2019-03-03") + 0:6 res <- lapply(weekdays(d), function(i) date2week(d, week_start = i)) resn <- lapply(weekdays(d), function(i) date2week(d, week_start = i, numeric = TRUE)) datf <- data.frame(wday = rep(weekdays(d), each = 7), week = unlist(res), # note: unlist converts to character week_number = unlist(resn), year = 2019, stringsAsFactors = FALSE) datf$day <- substring(datf$week, 10, 11) head(datf, 10) ``` To get the weeks (numbers or strings) to aweek objects, you should use the `start` argument to specify which day of the week they start on. Internally, this translates the week to their corresponding dates and then to aweek objects with the same `week_start` attribute (which defaults to `get_week_start()`). weeks as numbers ------------ Most commonly, you will have weeks across data sets represented by numbers. These can be converted to aweek objects using the `get_aweek()` function and to dates using the `get_date()` function: ```{r get_aweek} datf$aweek <- with(datf, get_aweek(week = week_number, year = year, day = day, start = wday)) datf$date <- with(datf, get_date(week = week_number, year = year, day = day, start = wday)) head(datf, 10) ``` These functions are also useful for constructing weeks or dates on the fly if you only have a week and a year: ```{r} get_aweek(11, 2019) get_date(11, 2019) ``` ```{r remove_things, include = FALSE} datf$aweek <- NULL datf$date <- NULL ``` weeks as characters ------------------- If you have weeks formatted as ISO-week strings, then you can convert to aweek objects using `as.aweek()`: ```{r date_a_frame_2} datf$aweek <- with(datf, as.aweek(week, start = wday)) head(datf, 10) str(datf) ``` We can tabulate them to see how they transformed: ```{r tabluate_data_frame, R.options = list(width = 100)} print(with(datf, table(before = week, after = aweek)), zero.print = ".") ``` Converting weeks to dates ========================= If you receive data that contains week definitions, you can convert it back to a date if you know where the week starts. ```{r week2date} week2date("2019-W10-1", week_start = "Sunday") # 2019-03-03 week2date("2019-W10-1", week_start = "Monday") # 2019-03-04 ``` If you have an `aweek` object, however, it will use the `week_start` attribute defined in the object, even if the default `week_start` attribute is different: ```{r week2date_aweek} set_week_start("Monday") # Set the default week_start to ISO week get_week_start(w) # show the default week_start for w week2date(w) identical(week2date(w), dat) # TRUE identical(week2date(as.character(w)), dat) # FALSE ``` You can also use `as.Date()` and `as.POISXlt()` if you have an `aweek` object: ```{r asdate} as.Date(w) as.POSIXlt(w) ``` aweek/inst/doc/introduction.html0000644000176200001440000016311114317440023016500 0ustar liggesusers aweek means ‘any week’

aweek means ‘any week’

2022-10-05

Introduction

The day in which a week starts differs depending on context. For countries like the UK, the first day of the week is the first working day, which is Monday. This definition conforms with the ISO 8601 standard definition for the beginning of a week, but there are examples of situations where the first day of the week is different:

This package provides tools to convert dates to weeks and back where a week can start on any day. You can use this package for any of the following:

  • convert date to week starting on any day
  • convert week numbers and years to dates
  • convert week to date
  • convert week to week
  • create a factor of weeks that contains ordered levels that includes missing weeks.

Converting dates to weeks

You can convert dates to weeks starting on any day by using date2week() with the week_start argument. This argument can be a number from 1 to 7 representing the ISO 8601 day of the week OR it can be a string representing the day of the week in either an English locale or the locale defined on your computer. The default of this argument is the value of get_week_start(), which is a thin wrapper around options("aweek.week_start", 1L). Unless you have specified a default aweek.week_start option with set_week_start(), this will always be set to 1 (Monday).

It is highly recommended that you set the default aweek.week_start either in the beginning of your Rscript, Rmarkdown document, or in your .Rprofile.

library("aweek")

set_week_start("Sunday") # setting the default week_start to Sunday

set.seed(2019-03-03)
dat <- as.Date("2019-03-03") + sample(-6:7, 10, replace = TRUE)
dat
##  [1] "2019-03-10" "2019-03-05" "2019-02-27" "2019-03-01" "2019-02-28"
##  [6] "2019-02-25" "2019-03-03" "2019-02-25" "2019-03-03" "2019-03-02"
print(w <- date2week(dat))
## <aweek start: Sunday>
##  [1] "2019-W11-1" "2019-W10-3" "2019-W09-4" "2019-W09-6" "2019-W09-5"
##  [6] "2019-W09-2" "2019-W10-1" "2019-W09-2" "2019-W10-1" "2019-W09-7"

If you need a different day on the fly, you can supply an integer or character day to the week_start argument.

# Use character days
date2week(dat, week_start = "Monday")
## <aweek start: Monday>
##  [1] "2019-W10-7" "2019-W10-2" "2019-W09-3" "2019-W09-5" "2019-W09-4"
##  [6] "2019-W09-1" "2019-W09-7" "2019-W09-1" "2019-W09-7" "2019-W09-6"
# Use ISO 8601 days
date2week(dat, week_start = 1)
## <aweek start: Monday>
##  [1] "2019-W10-7" "2019-W10-2" "2019-W09-3" "2019-W09-5" "2019-W09-4"
##  [6] "2019-W09-1" "2019-W09-7" "2019-W09-1" "2019-W09-7" "2019-W09-6"

If you want to save two extra keystrokes, you can also use the as.aweek() method for dates, which wraps date2week():

as.aweek(dat, week_start = 1)
## <aweek start: Monday>
##  [1] "2019-W10-7" "2019-W10-2" "2019-W09-3" "2019-W09-5" "2019-W09-4"
##  [6] "2019-W09-1" "2019-W09-7" "2019-W09-1" "2019-W09-7" "2019-W09-6"

What you get back is an aweek class object. It can be converted back to a date with either as.Date() or week2date():

week2date(w)
##  [1] "2019-03-10" "2019-03-05" "2019-02-27" "2019-03-01" "2019-02-28"
##  [6] "2019-02-25" "2019-03-03" "2019-02-25" "2019-03-03" "2019-03-02"
as.Date(w)
##  [1] "2019-03-10" "2019-03-05" "2019-02-27" "2019-03-01" "2019-02-28"
##  [6] "2019-02-25" "2019-03-03" "2019-02-25" "2019-03-03" "2019-03-02"

How does it work?

The calculation of weeks from dates requires knowledge of the current day of the week and the number of days past 1 January.

Week numbers are calculated in three steps:

  1. Find the day of the week, relative to the week_start (d). The day of the week (d) relative to the week start (s) is calculated using the ISO week day (i) via d = 1L + ((i + (7L - s)) %% 7L).
  2. Find the date that represents midweek (m). The date that represents midweek is found by subtracting the day of the week (d) from 4 and adding that number of days to the current date: m = date + (4 - d).
  3. Find the week number (w) by counting the number of days since 1 January to (m), and use integer division by 7: w = 1L + ((m - yyyy-01-01) %/% 7)

For example, here’s how to calculate the week for Tuesday, 6 December 2016, assuming the week start is a Sunday:

the_date <- as.Date("2016-12-06")
jan_1    <- as.Date("2016-01-01")

i <- as.POSIXlt(the_date)$wday # 2, the ISO date for Tuesday 
s <- 7L                        # week_start for sunday

# 1. Find the day of the week
print(d <- 1L + ((i + (7L - s)) %% 7L))
## [1] 3
# 2. Find the date that represents midweek
print(m <- the_date + (4L - d))
## [1] "2016-12-07"
# 3. Find the week
print(w <- 1L + as.integer(m - jan_1) %/% 7L)
## [1] 49
# Format the week
sprintf("2016-W%02d-%d", w, d)
## [1] "2016-W49-3"

For the weeks around 1 January, the year is determined by the week number. If the month is January, but the week number is 52 or 53, then the year for the week (YYYY) is the calendar year (yyyy) minus 1. However, if the month is December, but the week number is 1, then the year for the week (YYYY) is the calendar year (yyyy) plus 1.

The aweek class

The result you see above is an object of class “aweek”. The aweek class is a character that contains the week_start attribute. This attribute allows it to be easily converted back to a date without the user needing to enter the start day every time. You can convert a character that matches the YYYY-Www-d pattern to an aweek class object with as.aweek():

x <- as.aweek("2019-W10-1")
x
## <aweek start: Sunday>
## [1] "2019-W10-1"

Under the hood, it checks the validity of the week string and then add the attribute and class:

x <- "2019-W10-1"
attr(x, "week_start") <- 7 # Sunday 
class(x) <- "aweek"

If you need to remove the class, you can just use as.character():

as.character(x)
## [1] "2019-W10-1"

Best practices

The date2week() function only checks that dates are in ISO 8601 (yyyy-mm-dd) format before converting to weeks, and otherwise assumes that the dates are accurate so it’s strongly recommended to make sure your dates are in either Date or POISXt format and accurate before converting to weeks. The lubridate can be used for this purpose.

Use set_week_start() at the beginning of all your scripts to explicitly define the day on which your weeks start. This can be overridden if need be in specific parts of your scripts. Otherwise, the default will be dependent on the value of getOption("aweek.week_start", 1L).

Because the week_start arguments default to get_week_start(), it’s recommended to specify week_start in date2week() and week2date() if you don’t have an aweek object.

Before you combine aweek objects, confirm that they are actually aweek objects with inherits(myObject, "aweek").

Weekly aggregation

There are times where you would want to aggregate your days into weeks, you can do this by specifying floor_day = TRUE in date2week(). For example, here we can show the individual weeks:

print(wf <- date2week(dat, week_start = "Saturday", floor_day = TRUE))
## <aweek start: Saturday>
##  [1] "2019-W11" "2019-W10" "2019-W09" "2019-W09" "2019-W09" "2019-W09"
##  [7] "2019-W10" "2019-W09" "2019-W10" "2019-W10"
table(wf)
## wf
## 2019-W09 2019-W10 2019-W11 
##        5        4        1

If you convert this to date, then all the dates will represent the beginning of the week:

print(dwf <- week2date(wf))
##  [1] "2019-03-09" "2019-03-02" "2019-02-23" "2019-02-23" "2019-02-23"
##  [6] "2019-02-23" "2019-03-02" "2019-02-23" "2019-03-02" "2019-03-02"
weekdays(dwf)
##  [1] "Saturday" "Saturday" "Saturday" "Saturday" "Saturday" "Saturday"
##  [7] "Saturday" "Saturday" "Saturday" "Saturday"

If you want to aggregate your aweek objects after you created them, you can always use the trunc() function:

w <- date2week(dat)
w
## <aweek start: Sunday>
##  [1] "2019-W11-1" "2019-W10-3" "2019-W09-4" "2019-W09-6" "2019-W09-5"
##  [6] "2019-W09-2" "2019-W10-1" "2019-W09-2" "2019-W10-1" "2019-W09-7"
trunc(w)
## <aweek start: Sunday>
##  [1] "2019-W11" "2019-W10" "2019-W09" "2019-W09" "2019-W09" "2019-W09"
##  [7] "2019-W10" "2019-W09" "2019-W10" "2019-W09"

Factors

Weeks can be represented as factors, which is useful for tabulations across weeks. You can use factor = TRUE in date2week() and it will automatically fill in any missing weeks.

dat[1] + c(0, 15)
## [1] "2019-03-10" "2019-03-25"
date2week(dat[1] + c(0, 15), week_start = 1, factor = TRUE)
## <aweek start: Monday>
## [1] 2019-W10 2019-W13
## Levels: 2019-W10 2019-W11 2019-W12 2019-W13

If you already have an aweek object and want to convert it to a factor, you can use factor_aweek():

factor_aweek(w)
## <aweek start: Sunday>
##  [1] 2019-W11 2019-W10 2019-W09 2019-W09 2019-W09 2019-W09 2019-W10 2019-W09
##  [9] 2019-W10 2019-W09
## Levels: 2019-W09 2019-W10 2019-W11

Be careful when combining factors with other dates or aweek objects as they will force the other objects to be truncated as well.

Weeks to weeks

You can use change_week_start() to convert between different week definitions if you have an aweek object:

w # week starting on Sunday
## <aweek start: Sunday>
##  [1] "2019-W11-1" "2019-W10-3" "2019-W09-4" "2019-W09-6" "2019-W09-5"
##  [6] "2019-W09-2" "2019-W10-1" "2019-W09-2" "2019-W10-1" "2019-W09-7"
ww <- change_week_start(w, week_start = "wednesday") # same dates, starting on Wednesday
ww
## <aweek start: Wednesday>
##  [1] "2019-W10-5" "2019-W09-7" "2019-W09-1" "2019-W09-3" "2019-W09-2"
##  [6] "2019-W08-6" "2019-W09-5" "2019-W08-6" "2019-W09-5" "2019-W09-4"
identical(as.Date(w), as.Date(ww))
## [1] TRUE
# create a table with all days in the week
d   <- as.Date("2019-03-03") + 0:6
res <- lapply(weekdays(d), function(i) date2week(d, week_start = i))
names(res) <- weekdays(d)
data.frame(res)
##       Sunday     Monday    Tuesday  Wednesday   Thursday     Friday   Saturday
## 1 2019-W10-1 2019-W09-7 2019-W09-6 2019-W09-5 2019-W09-4 2019-W09-3 2019-W10-2
## 2 2019-W10-2 2019-W10-1 2019-W09-7 2019-W09-6 2019-W09-5 2019-W09-4 2019-W10-3
## 3 2019-W10-3 2019-W10-2 2019-W10-1 2019-W09-7 2019-W09-6 2019-W09-5 2019-W10-4
## 4 2019-W10-4 2019-W10-3 2019-W10-2 2019-W10-1 2019-W09-7 2019-W09-6 2019-W10-5
## 5 2019-W10-5 2019-W10-4 2019-W10-3 2019-W10-2 2019-W10-1 2019-W09-7 2019-W10-6
## 6 2019-W10-6 2019-W10-5 2019-W10-4 2019-W10-3 2019-W10-2 2019-W10-1 2019-W10-7
## 7 2019-W10-7 2019-W10-6 2019-W10-5 2019-W10-4 2019-W10-3 2019-W10-2 2019-W11-1

All of these columns contain the same dates:

data.frame(lapply(res, as.Date))
##       Sunday     Monday    Tuesday  Wednesday   Thursday     Friday   Saturday
## 1 2019-03-03 2019-03-03 2019-03-03 2019-03-03 2019-03-03 2019-03-03 2019-03-03
## 2 2019-03-04 2019-03-04 2019-03-04 2019-03-04 2019-03-04 2019-03-04 2019-03-04
## 3 2019-03-05 2019-03-05 2019-03-05 2019-03-05 2019-03-05 2019-03-05 2019-03-05
## 4 2019-03-06 2019-03-06 2019-03-06 2019-03-06 2019-03-06 2019-03-06 2019-03-06
## 5 2019-03-07 2019-03-07 2019-03-07 2019-03-07 2019-03-07 2019-03-07 2019-03-07
## 6 2019-03-08 2019-03-08 2019-03-08 2019-03-08 2019-03-08 2019-03-08 2019-03-08
## 7 2019-03-09 2019-03-09 2019-03-09 2019-03-09 2019-03-09 2019-03-09 2019-03-09

Combining aweek objects

You can add dates, aweek objects, or characters to aweek objects:

c(as.aweek("2010-W10-1"), 
  res$Sunday, 
  "2010-W12-1", 
  as.Date(res$Monday[1]) + 14)
## <aweek start: Sunday>
##  [1] "2010-W10-1" "2019-W10-1" "2019-W10-2" "2019-W10-3" "2019-W10-4"
##  [6] "2019-W10-5" "2019-W10-6" "2019-W10-7" "2010-W12-1" "2019-W12-1"

However, you can not combine aweek objects with different week_start attributes.

c(res$Sunday[1], res$Wednesday[2], res$Friday[3])
## Error in c.aweek(res$Sunday[1], res$Wednesday[2], res$Friday[3]): All aweek objects must have the same week_start attribute. Please use change_week_start() to adjust the week_start attribute if you wish to combine these objects.

If you want to combine different aweek objects, you must first change their week_start attribute:

wed <- change_week_start(res$Wednesday, get_week_start())
fri <- change_week_start(res$Friday, get_week_start())
c(res$Sunday[1], wed[2], fri[3])
## <aweek start: Sunday>
## [1] "2019-W10-1" "2019-W10-2" "2019-W10-3"

Dates can be appended to aweek objects

Dates combined with aweek objects will will be automatically converted.

c(res$Monday, as.Date("2019-04-03"))
## <aweek start: Monday>
## [1] "2019-W09-7" "2019-W10-1" "2019-W10-2" "2019-W10-3" "2019-W10-4"
## [6] "2019-W10-5" "2019-W10-6" "2019-W14-3"

Add characters with caution

You can also add character representation of weeks, but be aware that it is assumed that these have the same week_start as the first object.

s <- c(res$Saturday, "2019-W14-3")
s
## <aweek start: Saturday>
## [1] "2019-W10-2" "2019-W10-3" "2019-W10-4" "2019-W10-5" "2019-W10-6"
## [6] "2019-W10-7" "2019-W11-1" "2019-W14-3"
m <- c(res$Monday, "2019-W14-3")
m
## <aweek start: Monday>
## [1] "2019-W09-7" "2019-W10-1" "2019-W10-2" "2019-W10-3" "2019-W10-4"
## [6] "2019-W10-5" "2019-W10-6" "2019-W14-3"

These will translate into different dates

as.Date(s[7:8])
## [1] "2019-03-09" "2019-04-01"
as.Date(m[7:8])
## [1] "2019-03-09" "2019-04-03"

Working with weeks in data frames

You may encounter a situation where you have a merged data frame with weeks starting on different days. This section will cover two situations where you may have weeks as numbers and weeks as ISO-week strings. First we will create our demonstration data that represents the same week with different week_start attributes.

# create a table with all days in the week
d    <- as.Date("2019-03-03") + 0:6
res  <- lapply(weekdays(d), function(i) date2week(d, week_start = i))
resn <- lapply(weekdays(d), function(i) date2week(d, week_start = i, numeric = TRUE))
datf <- data.frame(wday = rep(weekdays(d), each = 7), 
                   week = unlist(res), # note: unlist converts to character
                   week_number = unlist(resn),
                   year  = 2019,
                   stringsAsFactors = FALSE)
datf$day <- substring(datf$week, 10, 11)
head(datf, 10)
##      wday       week week_number year day
## 1  Sunday 2019-W10-1          10 2019   1
## 2  Sunday 2019-W10-2          10 2019   2
## 3  Sunday 2019-W10-3          10 2019   3
## 4  Sunday 2019-W10-4          10 2019   4
## 5  Sunday 2019-W10-5          10 2019   5
## 6  Sunday 2019-W10-6          10 2019   6
## 7  Sunday 2019-W10-7          10 2019   7
## 8  Monday 2019-W09-7           9 2019   7
## 9  Monday 2019-W10-1          10 2019   1
## 10 Monday 2019-W10-2          10 2019   2

To get the weeks (numbers or strings) to aweek objects, you should use the start argument to specify which day of the week they start on. Internally, this translates the week to their corresponding dates and then to aweek objects with the same week_start attribute (which defaults to get_week_start()).

weeks as numbers

Most commonly, you will have weeks across data sets represented by numbers. These can be converted to aweek objects using the get_aweek() function and to dates using the get_date() function:

datf$aweek <- with(datf, get_aweek(week = week_number, year = year, day = day, start = wday))
datf$date  <- with(datf, get_date(week = week_number, year = year, day = day, start = wday))
head(datf, 10)
##      wday       week week_number year day      aweek       date
## 1  Sunday 2019-W10-1          10 2019   1 2019-W10-1 2019-03-03
## 2  Sunday 2019-W10-2          10 2019   2 2019-W10-2 2019-03-04
## 3  Sunday 2019-W10-3          10 2019   3 2019-W10-3 2019-03-05
## 4  Sunday 2019-W10-4          10 2019   4 2019-W10-4 2019-03-06
## 5  Sunday 2019-W10-5          10 2019   5 2019-W10-5 2019-03-07
## 6  Sunday 2019-W10-6          10 2019   6 2019-W10-6 2019-03-08
## 7  Sunday 2019-W10-7          10 2019   7 2019-W10-7 2019-03-09
## 8  Monday 2019-W09-7           9 2019   7 2019-W10-1 2019-03-03
## 9  Monday 2019-W10-1          10 2019   1 2019-W10-2 2019-03-04
## 10 Monday 2019-W10-2          10 2019   2 2019-W10-3 2019-03-05

These functions are also useful for constructing weeks or dates on the fly if you only have a week and a year:

get_aweek(11, 2019)
## <aweek start: Sunday>
## [1] "2019-W11-1"
get_date(11, 2019)
## [1] "2019-03-10"

weeks as characters

If you have weeks formatted as ISO-week strings, then you can convert to aweek objects using as.aweek():

datf$aweek <- with(datf, as.aweek(week, start = wday))
head(datf, 10)
##      wday       week week_number year day      aweek
## 1  Sunday 2019-W10-1          10 2019   1 2019-W10-1
## 2  Sunday 2019-W10-2          10 2019   2 2019-W10-2
## 3  Sunday 2019-W10-3          10 2019   3 2019-W10-3
## 4  Sunday 2019-W10-4          10 2019   4 2019-W10-4
## 5  Sunday 2019-W10-5          10 2019   5 2019-W10-5
## 6  Sunday 2019-W10-6          10 2019   6 2019-W10-6
## 7  Sunday 2019-W10-7          10 2019   7 2019-W10-7
## 8  Monday 2019-W09-7           9 2019   7 2019-W10-1
## 9  Monday 2019-W10-1          10 2019   1 2019-W10-2
## 10 Monday 2019-W10-2          10 2019   2 2019-W10-3
str(datf)
## 'data.frame':    49 obs. of  6 variables:
##  $ wday       : chr  "Sunday" "Sunday" "Sunday" "Sunday" ...
##  $ week       : chr  "2019-W10-1" "2019-W10-2" "2019-W10-3" "2019-W10-4" ...
##  $ week_number: num  10 10 10 10 10 10 10 9 10 10 ...
##  $ year       : num  2019 2019 2019 2019 2019 ...
##  $ day        : chr  "1" "2" "3" "4" ...
##  $ aweek      : 'aweek' chr  "2019-W10-1" "2019-W10-2" "2019-W10-3" "2019-W10-4" ...
##   ..- attr(*, "week_start")= int 7

We can tabulate them to see how they transformed:

print(with(datf, table(before = week, after = aweek)), zero.print = ".")
##             after
## before       2019-W10-1 2019-W10-2 2019-W10-3 2019-W10-4 2019-W10-5 2019-W10-6 2019-W10-7
##   2019-W09-3          1          .          .          .          .          .          .
##   2019-W09-4          1          1          .          .          .          .          .
##   2019-W09-5          1          1          1          .          .          .          .
##   2019-W09-6          1          1          1          1          .          .          .
##   2019-W09-7          1          1          1          1          1          .          .
##   2019-W10-1          1          1          1          1          1          1          .
##   2019-W10-2          1          1          1          1          1          1          1
##   2019-W10-3          .          1          1          1          1          1          1
##   2019-W10-4          .          .          1          1          1          1          1
##   2019-W10-5          .          .          .          1          1          1          1
##   2019-W10-6          .          .          .          .          1          1          1
##   2019-W10-7          .          .          .          .          .          1          1
##   2019-W11-1          .          .          .          .          .          .          1

Converting weeks to dates

If you receive data that contains week definitions, you can convert it back to a date if you know where the week starts.

week2date("2019-W10-1", week_start = "Sunday") # 2019-03-03
## [1] "2019-03-03"
week2date("2019-W10-1", week_start = "Monday") # 2019-03-04
## [1] "2019-03-04"

If you have an aweek object, however, it will use the week_start attribute defined in the object, even if the default week_start attribute is different:

set_week_start("Monday") # Set the default week_start to ISO week
get_week_start(w)        # show the default week_start for w
## [1] 7
week2date(w)
##  [1] "2019-03-10" "2019-03-05" "2019-02-27" "2019-03-01" "2019-02-28"
##  [6] "2019-02-25" "2019-03-03" "2019-02-25" "2019-03-03" "2019-03-02"
identical(week2date(w), dat)               # TRUE
## [1] TRUE
identical(week2date(as.character(w)), dat) # FALSE
## [1] FALSE

You can also use as.Date() and as.POISXlt() if you have an aweek object:

as.Date(w)
##  [1] "2019-03-10" "2019-03-05" "2019-02-27" "2019-03-01" "2019-02-28"
##  [6] "2019-02-25" "2019-03-03" "2019-02-25" "2019-03-03" "2019-03-02"
as.POSIXlt(w)
##  [1] "2019-03-10 UTC" "2019-03-05 UTC" "2019-02-27 UTC" "2019-03-01 UTC"
##  [5] "2019-02-28 UTC" "2019-02-25 UTC" "2019-03-03 UTC" "2019-02-25 UTC"
##  [9] "2019-03-03 UTC" "2019-03-02 UTC"
aweek/inst/doc/introduction.R0000644000176200001440000001324714317440022015740 0ustar liggesusers## ----date2week---------------------------------------------------------------- library("aweek") set_week_start("Sunday") # setting the default week_start to Sunday set.seed(2019-03-03) dat <- as.Date("2019-03-03") + sample(-6:7, 10, replace = TRUE) dat print(w <- date2week(dat)) ## ----date2week_week_start----------------------------------------------------- # Use character days date2week(dat, week_start = "Monday") # Use ISO 8601 days date2week(dat, week_start = 1) ## ----as.aweek.Date------------------------------------------------------------ as.aweek(dat, week_start = 1) ## ----date2week2date----------------------------------------------------------- week2date(w) as.Date(w) ## ----example_day-------------------------------------------------------------- the_date <- as.Date("2016-12-06") jan_1 <- as.Date("2016-01-01") i <- as.POSIXlt(the_date)$wday # 2, the ISO date for Tuesday s <- 7L # week_start for sunday # 1. Find the day of the week print(d <- 1L + ((i + (7L - s)) %% 7L)) # 2. Find the date that represents midweek print(m <- the_date + (4L - d)) # 3. Find the week print(w <- 1L + as.integer(m - jan_1) %/% 7L) # Format the week sprintf("2016-W%02d-%d", w, d) ## ----as.aweek.character------------------------------------------------------- x <- as.aweek("2019-W10-1") x ## ----ascharacter-------------------------------------------------------------- as.character(x) ## ----date2week_floor---------------------------------------------------------- print(wf <- date2week(dat, week_start = "Saturday", floor_day = TRUE)) table(wf) ## ----date2week_floor2date----------------------------------------------------- print(dwf <- week2date(wf)) weekdays(dwf) ## ----trunc-------------------------------------------------------------------- w <- date2week(dat) w trunc(w) ## ----factors------------------------------------------------------------------ dat[1] + c(0, 15) date2week(dat[1] + c(0, 15), week_start = 1, factor = TRUE) ## ----factor_aweek------------------------------------------------------------- factor_aweek(w) ## ----week2week_wednesday------------------------------------------------------ w # week starting on Sunday ww <- change_week_start(w, week_start = "wednesday") # same dates, starting on Wednesday ww identical(as.Date(w), as.Date(ww)) ## ----week2week, R.options=list(width = 100)------------------------------------------------------- # create a table with all days in the week d <- as.Date("2019-03-03") + 0:6 res <- lapply(weekdays(d), function(i) date2week(d, week_start = i)) names(res) <- weekdays(d) data.frame(res) ## ----week2week2date, R.options=list(width = 100)-------------------------------------------------- data.frame(lapply(res, as.Date)) ## ----caweekdate--------------------------------------------------------------- c(as.aweek("2010-W10-1"), res$Sunday, "2010-W12-1", as.Date(res$Monday[1]) + 14) ## ----cweek2week_err, error = TRUE--------------------------------------------- c(res$Sunday[1], res$Wednesday[2], res$Friday[3]) ## ----cweekweek---------------------------------------------------------------- wed <- change_week_start(res$Wednesday, get_week_start()) fri <- change_week_start(res$Friday, get_week_start()) c(res$Sunday[1], wed[2], fri[3]) ## ----add_dates---------------------------------------------------------------- c(res$Monday, as.Date("2019-04-03")) ## ----add_chars---------------------------------------------------------------- s <- c(res$Saturday, "2019-W14-3") s m <- c(res$Monday, "2019-W14-3") m ## ----char2date---------------------------------------------------------------- as.Date(s[7:8]) as.Date(m[7:8]) ## ----date_a_frame------------------------------------------------------------- # create a table with all days in the week d <- as.Date("2019-03-03") + 0:6 res <- lapply(weekdays(d), function(i) date2week(d, week_start = i)) resn <- lapply(weekdays(d), function(i) date2week(d, week_start = i, numeric = TRUE)) datf <- data.frame(wday = rep(weekdays(d), each = 7), week = unlist(res), # note: unlist converts to character week_number = unlist(resn), year = 2019, stringsAsFactors = FALSE) datf$day <- substring(datf$week, 10, 11) head(datf, 10) ## ----get_aweek---------------------------------------------------------------- datf$aweek <- with(datf, get_aweek(week = week_number, year = year, day = day, start = wday)) datf$date <- with(datf, get_date(week = week_number, year = year, day = day, start = wday)) head(datf, 10) ## ----------------------------------------------------------------------------- get_aweek(11, 2019) get_date(11, 2019) ## ----remove_things, include = FALSE------------------------------------------- datf$aweek <- NULL datf$date <- NULL ## ----date_a_frame_2----------------------------------------------------------- datf$aweek <- with(datf, as.aweek(week, start = wday)) head(datf, 10) str(datf) ## ----tabluate_data_frame, R.options = list(width = 100)------------------------------------------- print(with(datf, table(before = week, after = aweek)), zero.print = ".") ## ----week2date---------------------------------------------------------------- week2date("2019-W10-1", week_start = "Sunday") # 2019-03-03 week2date("2019-W10-1", week_start = "Monday") # 2019-03-04 ## ----week2date_aweek---------------------------------------------------------- set_week_start("Monday") # Set the default week_start to ISO week get_week_start(w) # show the default week_start for w week2date(w) identical(week2date(w), dat) # TRUE identical(week2date(as.character(w)), dat) # FALSE ## ----asdate------------------------------------------------------------------- as.Date(w) as.POSIXlt(w) aweek/inst/WORDLIST0000644000176200001440000000041314053270040013506 0ustar liggesusersAppVeyor CMD Codecov datetime datetimes DOI epiweeks github https ISOWeekYear ISOweek ISOweeks isoweeks lubridate MMWR MSF POSIXct POSIXlt POSIXt prophesized repidemicsconsortium Rmarkdown Rprofile Rscript trunc unclass vectorized weekdate ww www Www yyyy YYYY Zhian