Get Dates of a Certain Weekday from a Year in R - r

How might I generate a list of date objects (POSIXct or lt) for each Monday of a year?
For instance this year would be (In Year, Month, Day):
2012_01_02,
2012_01_09,
2102_01_16,
etc

EDIT: On further reflection, here's a cleaner function for doing the same thing:
getAllMondays <- function(year) {
days <- as.POSIXlt(paste(year, 1:366, sep="-"), format="%Y-%j")
Ms <- days[days$wday==1]
Ms[!is.na(Ms)] # Needed to remove NA from day 366 in non-leap years
}
getAllMondays(2012)
Here's a function that'll perform the more general task of finding the first Monday in an arbitrary year, and then listing it and all of the other Mondays in that year. It uses seq.POSIXt(), and the argument by = "week" (which is also available for seq.Date()).
getAllMondays <- function(year) {
day1 <- as.POSIXlt(paste(year, "01-01", sep="-"))
day365 <- as.POSIXlt(paste(year, "12-31", sep="-"))
# Find the first Monday of year
week1 <- as.POSIXlt(seq(day1, length.out=7, by="day"))
monday1 <- week1[week1$wday == 1]
# Return all Mondays in year
seq(monday1, day365, by="week")
}
head(getAllMondays(2012))
# [1] "2012-01-02 PST" "2012-01-09 PST" "2012-01-16 PST" "2012-01-23 PST"
# [5] "2012-01-30 PST" "2012-02-06 PST"

I found seq.Date which is part of base. Not sure if there are caveats to this method but it seems to do what I want:
x = seq(as.Date("2012/01/02"), as.Date("2013/01/01"), "7 days")
as.POSIXct(x)

as.Date("2012_01_02", format="%Y_%m_%d") +seq(0,366,by=7) # 2012 is a leap year.
If you really want them as DateTimes with all the attendant hassles of timezones then you can coerce them with as.POSIXct.

Related

change all datetimes to 2PM and keep the dates same

I have datetime object and I want to change all times to 2PM and keep the dates same.
I used floor_date to get the start of the corresponding date and then added period of 14 hours to get 2PM.
Sometime, result shows only the date and no time. Sometimes it shows both date and time.
Is there another approach to do this
library(lubridate)
t1 <- floor_date(Sys.time(), unit = "day") + hours(14)
t2 <- floor_date(ymd_hms("2021-08-25 10:36:00"), unit = "day") + hours(14)
You can replace the time component with the hour. Here is a function to do that.
change_time_to_x <- function(time, x) {
as.POSIXct(sub('\\s.*', x, time), tz = 'UTC')
}
input <- lubridate::ymd_hms(Sys.time(), "2021-08-25 10:36:00", "2012-12-31 00:00:00")
change_time_to_x(input, '14:00:00')
#[1] "2021-08-26 14:00:00 UTC" "2021-08-25 14:00:00 UTC" "2012-12-31 14:00:00 UTC"

POSIX date from dates in weekly time format

I have dates encoded in a weekly time format (European convention >> 01 through 52/53, e.g. "2016-48") and would like to standardize them to a POSIX date:
require(magrittr)
(x <- as.POSIXct("2016-12-01") %>% format("%Y-%V"))
# [1] "2016-48"
as.POSIXct(x, format = "%Y-%V")
# [1] "2016-01-11 CET"
I expected the last statement to return "2016-12-01" again. What am I missing here?
Edit
Thanks to Dirk, I was able to piece it together:
y <- sprintf("%s-1", x)
While I still don't get why this doesn't work
(as.POSIXct(y, format = "%Y-%V-%u"))
# [1] "2016-01-11 CET"
this does
(as.POSIXct(y, format = "%Y-%U-%u")
# [1] "2016-11-28 CET"
Edit 2
Oh my, I think using %V is a very bad idea in general:
as.POSIXct("2016-01-01") %>% format("%Y-%V")
# [1] "2016-53"
Should this be considered to be on a "serious bug" level that requires further action?!
Sticking to either %U or %W seems to be the right way to go
as.POSIXct("2016-01-01") %>% format("%Y-%U")
# [1] "2016-00"
Edit 3
Nope, not quite finished/still puzzled: the approach doesn't work for the very first week
(x <- as.POSIXct("2016-01-01") %>% format("%Y-%W"))
# [1] "2016-00"
as.POSIXct(sprintf("%s-1", x), format = "%Y-%W-%u")
# [1] NA
It does for week 01 as defined in the underlying convention when using %U or %W (so "week 2", actually)
as.POSIXct("2016-01-1", format = "%Y-%W-%u")
# [1] "2016-01-04 CET"
As I have to deal a lot with reporting by ISO weeks, I've created the ISOweek package some years ago.
The package includes the function ISOweek2date() which returns the date of a given weekdate (year, week of the year, day of week according to ISO 8601). It's the inverse function to date2ISOweek().
With ISOweek, your examples become:
library(ISOweek)
# define dates to convert
dates <- as.Date(c("2016-12-01", "2016-01-01"))
# convert to full ISO 8601 week-based date yyyy-Www-d
(week_dates <- date2ISOweek(dates))
[1] "2016-W48-4" "2015-W53-5"
# convert back to class Date
ISOweek2date(week_dates)
[1] "2016-12-01" "2016-01-01"
Note that date2ISOweek() requires a full ISO week-based date in the format yyyy-Www-d including the day of the week (1 to 7, Monday to Sunday).
So, if you only have year and ISO week number you have to create a character string with a day of the week specified.
A typical phrase in many reports is, e.g., "reporting week 31 ending 2017-08-06":h
yr <- 2017
wk <- 31
ISOweek2date(sprintf("%4i-W%02i-%1i", yr, wk, 7))
[1] "2017-08-06"
Addendum
Please, see this answer for another use case and more background information on the ISOweek package.

Is there an inverse of the yday lubridate function?

I have a list of days in the format "day of the year" obtained by applying lubridate::yday() function to a list of dates. For instance, from the following dates (mm-dd-yyyy format):
01-01-2015
01-02-2015
...
by applying yday() you get
1
2
...
Is there a function that can do the reverse given the yday output and the year? Ie, from a yday value and a year, get back to a date in the mm-dd-yyyy format?
To do this with the lubridate package, you use the parse_date_time() function with j as the order = argument.
Example:
my.yday <- "1"
parse_date_time(x = my.yday, orders = "j")
# [1] "2021-01-01 UTC"
The default appears to be the current year. If you want to specify the year just add it in!
my.yday <- "1"
parse_date_time(x = paste(1995, my.yday), orders = "yj")
# [1] "1995-01-01 UTC"
Note: make sure you supply the julian day as character, not numeric. If numeric it will fail when over 3 digits (weird bug that they don't plan to fix)
Any sequence added to a Date() type creates a new Date() sequence with just that offset.
Witness:
R> as.Date("2016-01-01") + 0:9
[1] "2016-01-01" "2016-01-02" "2016-01-03"
[4] "2016-01-04" "2016-01-05" "2016-01-06"
[7] "2016-01-07" "2016-01-08" "2016-01-09"
[10] "2016-01-10"
R> as.Date("2016-01-01") + 100:109
[1] "2016-04-10" "2016-04-11" "2016-04-12"
[4] "2016-04-13" "2016-04-14" "2016-04-15"
[7] "2016-04-16" "2016-04-17" "2016-04-18"
[10] "2016-04-19"
R>
So once again a so-called lubridate question as nothing to do with that package but simply requires to know how the base R types function.
> yday ("1990-03-17") - 1 + as.Date ("1990-01-01")
[1] "1990-03-17"
Just as round out the answer by #DirkEddelbuettel, since I was having the same issue and its been 3 years.
In order to get the desired mm-dd-yyyy for a given dataset where you have only the year and the calendar day as a result of lubridate::yday().
You just need to offset a Date() type object for that given year starting at january 1st, by what your yday() output is subtracted by one.
Then if you want to get the month or day within that month you can use lubridate's month() and day() functions to pull those parts.
(I assume you have the year since that will impact the calendar date b/c of leap years, messing up your month/day assignment. If not, any year will do)
library(dplyr)
library(magrittr)
#Example dataset with years on/around leap year
my_df <- data.frame(
year = c(2010, 2011, 2012, 2013, 2014, 2015),
my_yday = c(150, 150, 150, 150, 150, 150)
)
#skip straight back to the yyyy-mm-dd format
my_df %>% mutate(new_date = as.Date(paste0(year, "-01-01")) + (my_yday - 1))
#Get every component
my_df %>%
mutate(
new_day = lubridate::day(as.Date(str_c(year, "-01-01")) + (my_yday - 1)),
new_month = lubridate::month(as.Date(paste0(year, "-01-01")) + (my_yday - 1)),
new_date = as.POSIXct(str_c(new_month, new_day, year, sep = "/"),
format = "%m/%d/%y"))

Using as.Date to get previous n months

as.Date() has a useful function in that it can give you the last n days as, e.g.:
dt <- Sys.Date()-6
> dt
[1] "2015-09-25"
Is there a way to tell it to give the last six months instead of the last six days?
I need something more precise than 6*30, as it should be the last day of the month.
You cannot use just Sys.Date to do this but there are ways. The following will give you just the correct months but not the correct days (i.e. the last day of the month):
#Sys.Date will return 2015-10-01 today
dates <- seq( Sys.Date() - months(6), Sys.Date(), '1 month')
dates
[1] "2015-04-01" "2015-05-01" "2015-06-01" "2015-07-01" "2015-08-01" "2015-09-01" "2015-10-01"
However, I found this very nice blog on R-bloggers which defines this function ( I slightly modified it to work with Dates) that returns the last day of the month:
eom <- function(date) {
# date character string containing POSIXct date
date.lt <- as.POSIXlt(dates) # add a month, then subtract a day:
mon <- date.lt$mon + 2
year <- date.lt$year
year <- year + as.integer(mon==13) # if month was December add a year
mon[mon==13] <- 1
iso = ISOdate(1900+year, mon, 1, hour=0, tz='')
result = as.POSIXct(iso) - 86400 # subtract one day
result + (as.POSIXlt(iso)$isdst - as.POSIXlt(result)$isdst)*3600
}
Now running:
> eom(dates)
[1] "2015-04-30 BST" "2015-05-31 BST" "2015-06-30 BST" "2015-07-31 BST" "2015-08-31 BST" "2015-09-30 BST" "2015-10-31 GMT"
Returns the correct results.
Is that what you are looking for?
today = Sys.Date()
lastSixMonths = seq(today, length.out=6, by="-1 month")
print(lastSixMonths)
# [1] "2015-10-01" "2015-09-01" "2015-08-01" "2015-07-01" "2015-06-01"
# [6] "2015-05-01"

Create end of the month date from a date variable

I have a large data frame with date variables, which reflect first day of the month. Is there an easy way to create a new data frame date variable that represents the last day of the month?
Below is some sample data:
date.start.month=seq(as.Date("2012-01-01"),length=4,by="months")
df=data.frame(date.start.month)
df$date.start.month
"2012-01-01" "2012-02-01" "2012-03-01" "2012-04-01"
I would like to return a new variable with:
"2012-01-31" "2012-02-29" "2012-03-31" "2012-04-30"
I've tried the following but it was unsuccessful:
df$date.end.month=seq(df$date.start.month,length=1,by="+1 months")
To get the end of months you could just create a Date vector containing the 1st of all the subsequent months and subtract 1 day.
date.end.month <- seq(as.Date("2012-02-01"),length=4,by="months")-1
date.end.month
[1] "2012-01-31" "2012-02-29" "2012-03-31" "2012-04-30"
Here is another solution using the lubridate package:
date.start.month=seq(as.Date("2012-01-01"),length=4,by="months")
df=data.frame(date.start.month)
library(lubridate)
df$date.end.month <- ceiling_date(df$date.start.month, "month") - days(1)
df$date.end.month
[1] "2012-01-31" "2012-02-29" "2012-03-31" "2012-04-30"
This uses the same concept given by James above, in that it gets the first day of the next month and subtracts one day.
By the way, this will work even when the input date is not necessarily the first day of the month. So for example, today is the 27th of the month and it still returns the correct last day of the month:
ceiling_date(Sys.Date(), "month") - days(1)
[1] "2017-07-31"
Use timeLastDayInMonth from the timeDate package:
df$eom <- timeLastDayInMonth(df$somedate)
library(lubridate)
as.Date("2019-09-01") - days(1)
[1] "2019-08-31"
or
library(lubridate)
as.Date("2019-09-01") + months(1) - days(1)
[1] "2019-09-30"
A straightforward solution would be using the yearmonfunction with the argument frac=1 from the xts-package. frac is a number between 0 and 1 that indicates the fraction of the way through the period that the result represents.
as.Date(as.yearmon(seq.Date(as.Date('2017-02-01'),by='month',length.out = 6)),frac=1)
[1] "2017-02-28" "2017-03-31" "2017-04-30" "2017-05-31" "2017-06-30" "2017-07-31"
Or if you prefer “piping” using magrittr:
seq.Date(as.Date('2017-02-01'),by='month',length.out = 6) %>%
as.yearmon() %>% as.Date(,frac=1)
[1] "2017-02-28" "2017-03-31" "2017-04-30" "2017-05-31" "2017-06-30" "2017-07-31"
A function as below would do the work (assume dt is scalar) -
month_end <- function(dt) {
d <- seq(dt, dt+31, by="days")
max(d[format(d,"%m")==format(dt,"%m")])
}
If you have a vector of Dates, then do the following -
sapply(dates, month_end)
you can use timeperiodsR
date.start.month=seq(as.Date("2012-01-01"),length=4,by="months")
df=data.frame(date.start.month)
df$date.start.month
# install.packages("timeperiodsR")
pm <- previous_month(df$date.start.month[1]) # get previous month
start(pm) # first day of previous month
end(pm) # last day of previous month
seq(pm) # vector with all days of previous month
We can also use bsts::LastDayInMonth:
transform(df, date.end.month = bsts::LastDayInMonth(df$date.start.month))
# date.start.month date.end.month
# 1 2012-01-01 2012-01-31
# 2 2012-02-01 2012-02-29
# 3 2012-03-01 2012-03-31
# 4 2012-04-01 2012-04-30
tidyverse has added the clock package in addition to the lubridate package that has nice functionality for this:
library(clock)
date_build(2012, 1:12, 31, invalid = "previous")
# [1] "2012-01-31" "2012-02-29" "2012-03-31" "2012-04-30" "2012-05-31" "2012-06-30"
# [7] "2012-07-31" "2012-08-31" "2012-09-30" "2012-10-31" "2012-11-30" "2012-12-31"
The invalid argument specifies what to do with an invalid date (e.g. 2012-02-31). From the documentation:
"previous": The previous valid instant in time.
"previous-day": The previous valid day in time, keeping the time of
day.
"next": The next valid instant in time.
"next-day": The next valid day in time, keeping the time of day.
"overflow": Overflow by the number of days that the input is invalid
by. Time of day is dropped.
"overflow-day": Overflow by the number of days that the input is
invalid by. Time of day is kept.
"NA": Replace invalid dates with NA.
"error": Error on invalid dates.

Resources