Date sequence with negative by - r

How do I get a sequence of monthly dates that ends on a given month and has a given length? seq(as.Date(*), length, by="month") assumes the start date is given, not the end date, and AFAIK it's impossible to specify a negative value for by in this case.
ETA: that is, I want a sequence that spans a given period, but one whose end point is specified rather than the start point. So, something like seq(to="2000-03-01", len=3, by="month") --> 2000-01-01, 2000-02-01, 2000-03-01.

Try this:
rev(seq(as.Date("2000-03-01"), length = 3, by = "-1 month"))
## [1] "2000-01-01" "2000-02-01" "2000-03-01"

library(lubridate)
ymd('2011-03-03') - months(0:5)

Maybe you could just compute it forward, using by=month as the +1 increment, and then reverse:
R> rev(seq(as.Date("2011-01-01"), length=6, by="month"))
[1] "2011-06-01" "2011-05-01" "2011-04-01" "2011-03-01" "2011-02-01" "2011-01-01"

Here you go. Base functions only:
last.days.of.month <- function(dt) {ldt<- as.POSIXlt(dt)
ldt$mon <- ldt$mon+1
ldt$mday <- 1
return(format( ldt -1, "%Y-%m-%d"))}
last.days.of.month(as.Date(c("2010-01-06","2010-03-06", "2010-02-06")) )
# [1] "2010-01-31" "2010-03-31" "2010-02-28"
seq.ldom <- function(dt, nmonths) {ldt<- rep(as.POSIXlt(dt)[1], nmonths)
ldt$mon <- ldt$mon+seq(1:nmonths)
ldt$mday <- 1
return(format( ldt -1, "%Y-%m-%d"))}
seq.ldom(as.Date("2010-01-06"), 5)
#[1] "2010-01-31" "2010-02-28" "2010-03-31" "2010-04-30"
#[5] "2010-05-31"
Oh, for some reason I thought you wanted the last days of the month. Sorry about the useless code. The first days of the month is not hard.
seq.fdom <- function(dt, nmonths) {ldt<- rep(as.POSIXlt(dt)[1], nmonths)
ldt$mon <- ldt$mon+seq(0:(nmonths-1))
ldt$mday <- 1
return(format( ldt , "%Y-%m-%d"))}
seq.fdom(as.Date("2010-01-06"), 5)
#[1] "2010-02-01" "2010-03-01" "2010-04-01" "2010-05-01"
#[5] "2010-06-01"
And getting the prior months either:
seq.prior.fdom <- function(dt, nmonths) {ldt<- rep(as.POSIXlt(dt)[1], nmonths)
ldt$mon <- ldt$mon-rev(0:(nmonths-1))
ldt$mday <- 1
return(format( ldt , "%Y-%m-%d"))}
seq.prior.fdom(as.Date("2010-01-06"), 5)
#[1] "2009-09-01" "2009-10-01" "2009-11-01" "2009-12-01"
#[5] "2010-01-01"
I think the basic principle is clear (if not beaten to death with a canoe paddle.)

Related

Generate sequences of anniversary dates between 2 dates

I tried to generate a sequence of dates between two dates. By search all the old posts, I found very nice solution using seq.Date.
For example:
> seq.Date(as.Date("2016/1/15"), as.Date("2016/5/1"), by = "month")
[1] "2016-01-15" "2016-02-15" "2016-03-15" "2016-04-15"
The above function yields very nice solution. However, it doesnt work when the date is 30 or 31 in Jan.
> seq.Date(as.Date("2016/1/30"), as.Date("2016/5/1"), by = "month")
[1] "2016-01-30" "2016-03-01" "2016-03-30" "2016-04-30"
The second anniversary jumps to March instead of being capped at 29/Feb. I couldnt find a workaround for this.
Here's an approach that also works in other cases:
library(lubridate)
fun <- function(from, to, by) {
mySeq <- seq.Date(as.Date(from), as.Date(to), by = by)
as.Date(sapply(mySeq, function(d) d + 1 - which.max(day(d - 0:3))), origin = "1970-01-01")
}
fun("2016/1/30", "2016/5/1", "month")
# [1] "2016-01-30" "2016-02-29" "2016-03-30" "2016-04-30"
fun("2017/1/31", "2017/5/1", "month")
# [1] "2017-01-31" "2017-02-28" "2017-03-31" "2017-04-30"
fun("2017/1/29", "2017/5/1", "month")
# [1] "2017-01-29" "2017-02-28" "2017-03-29" "2017-04-29"
What fun does is that it subtracts 0:3 from each date and chooses the one that has the largest day.
With lubridate package
library('lubridate')
pmin(
ymd('2018-01-30') + months(0:11), # NA where month goes over
ymd('2018-01-01') + months(1:12) - days(1), # last day of month
na.rm = T
)
[1] "2018-01-30" "2018-02-28" "2018-03-30"
[4] "2018-04-30" "2018-05-30" "2018-06-30"
[7] "2018-07-30" "2018-08-30" "2018-09-30"
[10] "2018-10-30" "2018-11-30" "2018-12-30"

Extract two combined dates and times from a string

Could you please let me know how I can extract date and time from ("2015-08-11 03:14:00 UTC--2015-08-11 04:14:00 UTC"). Note that this string contains a time interval with two dates and two times. I would like to break it down into 4 individual strings such as Date 1, Time 1, Date 2, Time 2 and then store them in 4 separate vectors.
Thanks.
Try the following.
x <- "2015-08-11 03:14:00 UTC--2015-08-11 04:14:00 UTC"
y <- strsplit(x, "--")[[1]]
dates <- as.Date(y)
times <- strftime(y, format = "%H:%M:%S")
You never mentioned whether you need functional dates and times from your input string. If you need to simply parse each portion of your timestamp then using gsub is one option.
x <- "2015-08-11 03:14:00 UTC--2015-08-11 04:14:00 UTC"
y <- unlist(strsplit(x, "--"))
dates <- sapply(y, function(x) gsub("(\\d{4}-\\d{2}-\\d{2}).*", "\\1", x))
times <- sapply(y, function(x) gsub(".*(\\d{2}:\\d{2}:\\d{2}.*)", "\\1", x))
dates
[1] "2015-08-11" "2015-08-11"
times
[1] "03:14:00 UTC" "04:14:00 UTC"
Demo here:
Rextester

Add one day to every date in a days vector

I have a vector of dates called KeyDates containing two key dates. I would like to make a new vector of dates called KeyDatesPlus containing those two key dates and the two days after, in chronological order.
KeyDates <- structure(c(15159,15165), class = "Date")
#KeyDates Output:
[1] "2011-07-04" "2011-07-10"
#desired output for KeyDatesPlus:
[1] "2011-07-04" "2011-07-05" "2011-07-10" "2011-07-11"
How could I achieve that? Thank you very much.
sort(c(KeyDates, KeyDates + 1))
[1] "2011-07-04" "2011-07-05" "2011-07-10" "2011-07-11"
structure( sapply(KeyDates, "+", (0:1)), class = "Date")
[1] "2011-07-04" "2011-07-05" "2011-07-10" "2011-07-11"
Or:
as.Date( sapply(KeyDates, "+", (0:1)))
[1] "2011-07-04" "2011-07-05" "2011-07-10" "2011-07-11"
KeyDates <- structure(c(15159,15165), class = "Date")
KeyDates.plus <- as.Date(sapply(KeyDates, function(x) c(x, x+1)))
An answer using the package lubridate:
library("lubridate")
your.vector <- c("2011-07-04", "2011-07-10")
your.vector <- parse_date_time(x = your.vector, orders = "ymd")
your.vector
# [1] "2011-07-04 UTC" "2011-07-10 UTC"
one.day <- days(x = 1)
one.day
# [1] "1d 0H 0M 0S"
your.vector + one.day
# [1] "2011-07-05 UTC" "2011-07-11 UTC"
# your exact desired output (non-UTC time zone can be specified in parse_date_time):
new.vector <- sort(x = c(your.vector, your.vector + one.day))
# [1] "2011-07-04 UTC" "2011-07-05 UTC" "2011-07-10 UTC" "2011-07-11 UTC"
Lubridate distinguishes a "period" from a "duration."
A period is the time on the clock (ie if daylight savings time happens, it's what the clock reads). That's what's specified here using days().
A duration is the physical time (ie if daylight savings time happens, it's how long you've actually been sitting there.) That could be specified instead using ddays().
KeyDates <- structure(c(15159,15165), class = "Date")
KeyDatesPlus <- KeyDates+1
KeyDatesPlus <- sort(unique(c(KeyDates, KeyDatesPlus)))

Construct vector of dates of the 1st and 15th of each month for axis breaks

I'm using R and want to construct a vector of dates, consisting of the 1st and 15th of each month, to use as breaks along the x-axis in a plot.
There are a lot of ways to do this but I'm trying to find the most elegant, straightforward approach.
My own solution to this is to create a full vector of dates and then discard the ones I don't need by checking the day.
library(lubridate)
library(magrittr)
myDateBreaks = function(start, end, days=c(1, 15){
dateBreaks = seq(as.Date(start), as.Date(end), by="1 day")
dateBreaks %<>% .[day(dateBreaks) %in% days]
return(dateBreaks)
}
x <- seq(as.Date("2015-01-01"), as.Date("2015-12-31"), by = "month")
rep(x, each = 2) + rep(c(0, 14), length(x))
#[1] "2015-01-01" "2015-01-15" "2015-02-01" "2015-02-15" "2015-03-01" "2015-03-15" "2015-04-01" "2015-04-15" "2015-05-01" "2015-05-15" "2015-06-01" "2015-06-15"
#[13] "2015-07-01" "2015-07-15" "2015-08-01" "2015-08-15" "2015-09-01" "2015-09-15" "2015-10-01" "2015-10-15" "2015-11-01" "2015-11-15" "2015-12-01" "2015-12-15"

Create vector of character strings in R using for loop

I'm trying to create a vector of dates (formatted as character strings not as dates) using a for loop. I've reviewed a few other SO questions such as (How to create a vector of character strings using a loop?), but they weren't helpful. I've created the following for loop:
start_dates <- c("1993-12-01")
j <- 1
start_dates <- for(i in 1994:as.numeric(format(Sys.Date(), "%Y"))){
date <- sprintf("%s-01-01", i)
j <- j + 1
start_dates[j] <- date
}
However, it returns a NULL (empty) vector start_dates. When I increment the i index manually it works. For example:
> years <- 1994:as.numeric(format(Sys.Date(), "%Y"))
> start_dates <- c("1993-12-01")
> j <- 1
> i <- years[1]
> date <- sprintf("%s-01-01", i)
> j <- j + 1
> start_dates[j] <- date
> start_dates
[1] "1993-12-01" "1994-01-01"
> i <- years[2]
> date <- sprintf("%s-01-01", i)
> j <- j + 1
> start_dates[j] <- date
> start_dates
[1] "1993-12-01" "1994-01-01" "1995-01-01"
It must have something to do with the construction of my for() statement, but I can't figure it out. I'm sure it's super simple. Thanks in advance.
What is wrong with:
sprintf("%s-01-01", 1994:2015)
> sprintf("%s-01-01", 1994:2015)
[1] "1994-01-01" "1995-01-01" "1996-01-01" "1997-01-01" "1998-01-01"
[6] "1999-01-01" "2000-01-01" "2001-01-01" "2002-01-01" "2003-01-01"
[11] "2004-01-01" "2005-01-01" "2006-01-01" "2007-01-01" "2008-01-01"
[16] "2009-01-01" "2010-01-01" "2011-01-01" "2012-01-01" "2013-01-01"
[21] "2014-01-01" "2015-01-01"
sprintf() is fully vectorised, take advantage of this.
Problems with your loop
The main problem is that you are assigning the value of the for() function to start_dates when the for() finished, hence overwriting all the hard work your loop did. This is effectively what is happening:
j <- 1
foo <- for (i in 1:10) {
j <- j + 1
}
foo
> foo
NULL
And reading ?'for' we see that this behaviour is by design:
Value:
....
‘for’, ‘while’ and ‘repeat’ return ‘NULL’ invisibly.
Solution: Don't assign the returned value of for(). Hence the template might be:
for(i in foo) {
# ... do stuff
start_dates[j] <- bar
}
Fix that and you still have a problem; j will be 2 by the time you assign the first date to the output as you start with j <- 1 and increment it before assigning in the loop.
This would be easier if you made i take values from a sequence 1, 2, ..., n rather than the actual years you want. You can use i to index the years vector and as an index for the elements of start_dates too.
Not that you should do the loop this way, but, if you wanted too...
years <- seq.int(1994, 2015)
start_dates <- numeric(length = length(years))
for (i in seq_along(years)) {
start_dates[i] <- sprintf("%s-01-01", years[i])
}
which would give:
> start_dates
[1] "1994-01-01" "1995-01-01" "1996-01-01" "1997-01-01" "1998-01-01"
[6] "1999-01-01" "2000-01-01" "2001-01-01" "2002-01-01" "2003-01-01"
[11] "2004-01-01" "2005-01-01" "2006-01-01" "2007-01-01" "2008-01-01"
[16] "2009-01-01" "2010-01-01" "2011-01-01" "2012-01-01" "2013-01-01"
[21] "2014-01-01" "2015-01-01"
Sometimes it is helpful to loop over the actual values in a vector (as you did) rather than it's indices (as I just did), but only in specific cases. For general operations like you have here, it is just an additional complication you need to work around. That said, think about doing vectorised operations in R before resorting to a loop.
You shouldn't assign the loop to a variable. Do:
start_dates <- c("1993-12-01")
j <- 1
for(i in 1994:as.numeric(format(Sys.Date(), "%Y"))){ #use the for-loop on its own. Don't assign it to a variable
date <- sprintf("%s-01-01", i )
j <- j + 1
start_dates[j] <- date
}
and you are fine:
> start_dates
[1] "1993-12-01" "1994-01-01" "1995-01-01" "1996-01-01" "1997-01-01" "1998-01-01" "1999-01-01" "2000-01-01" "2001-01-01"
[10] "2002-01-01" "2003-01-01" "2004-01-01" "2005-01-01" "2006-01-01" "2007-01-01" "2008-01-01" "2009-01-01" "2010-01-01"
[19] "2011-01-01" "2012-01-01" "2013-01-01" "2014-01-01" "2015-01-01"

Resources