Reputation: 10444
I use lubridate and figured that this would be so easy
ymd("2010-01-31")+months(0:23)
But look what one gets. It is all messed up!
[1] "2010-01-31 UTC" "2010-03-03 UTC" "2010-03-31 UTC" "2010-05-01 UTC" "2010-05-31 UTC" "2010-07-01 UTC" "2010-07-31 UTC" "2010-08-31 UTC" "2010-10-01 UTC"
[10] "2010-10-31 UTC" "2010-12-01 UTC" "2010-12-31 UTC" "2011-01-31 UTC" "2011-03-03 UTC" "2011-03-31 UTC" "2011-05-01 UTC" "2011-05-31 UTC" "2011-07-01 UTC"
[19] "2011-07-31 UTC" "2011-08-31 UTC" "2011-10-01 UTC" "2011-10-31 UTC" "2011-12-01 UTC" "2011-12-31 UTC"
Then I read how lubridate caters to phenomenon such as interval, duration and period. So, OK I realize that a month is actually the number of days defined by (365*4+1)/48 = 30.438 days. So I tried to get smart and rewrite it as
ymd("2010-01-31")+ as.period(months(0:23))
But that just gave an error.
Error in as.period.default(months(0:23)) : (list) object cannot be coerced to type 'double'
Upvotes: 46
Views: 26594
Reputation: 18632
tidyverse
has added the clock package in addition to the lubridate
package that has nice functionality for various date arithmetic. There are a few ways you could answer this:
library(clock)
# sequence first day of every month and then set day to be last day
seq(as.Date("2010-01-01"), by = "1 month", length.out = 24) |> set_day("last")
date_seq
will generate invalid dates such as "2010-02-31", but you can specify what to do in these instances with the invalid
argument. In this case, go back to the previous valid date.
start <- date_build(2010, 01, 31)
date_seq(start, by = duration_months(1), total_size = 24, invalid = "previous")
Alternatively, you can sequence months and then add the last day back in at the end:
start <- calendar_narrow(year_month_day(2010, 01, 01), "month") # [1] "2010-01"
seq(start, by = 1, length.out = 24) |>
set_day("last") |>
as.Date()
Upvotes: 0
Reputation: 368241
Yes, you found the correct trick: going back a day from the first of the next month.
Here is as a one-liner in base R:
R> seq(as.Date("2010-02-01"), length=24, by="1 month") - 1
[1] "2010-01-31" "2010-02-28" "2010-03-31" "2010-04-30" "2010-05-31"
[6] "2010-06-30" "2010-07-31" "2010-08-31" "2010-09-30" "2010-10-31"
[11] "2010-11-30" "2010-12-31" "2011-01-31" "2011-02-28" "2011-03-31"
[16] "2011-04-30" "2011-05-31" "2011-06-30" "2011-07-31" "2011-08-31"
[21] "2011-09-30" "2011-10-31" "2011-11-30" "2011-12-31"
R>
So no need for lubridate which (while being a fine package) isn't needed for simple task like this. Plus, its overloading of existing base functions still strikes me as somewhat dangerous...
Upvotes: 102
Reputation: 10444
It is amazing how typing out a question focuses creative energy. I think I worked out the answer. I may as well post it here for the next poor soul who finds themselves wasting time.
ymd("2010-02-01")+ months(0:23)-days(1)
Simply specify the first day of the next month and generate a sequence from that but subtract 1 days from it to get the last day of the preceding month.
[1] "2010-01-31 UTC" "2010-02-28 UTC" "2010-03-31 UTC" "2010-04-30 UTC" "2010-05-31 UTC" "2010-06-30 UTC" "2010-07-31 UTC" "2010-08-31 UTC" "2010-09-30 UTC"
[10] "2010-10-31 UTC" "2010-11-30 UTC" "2010-12-31 UTC" "2011-01-31 UTC" "2011-02-28 UTC" "2011-03-31 UTC" "2011-04-30 UTC" "2011-05-31 UTC" "2011-06-30 UTC"
[19] "2011-07-31 UTC" "2011-08-31 UTC" "2011-09-30 UTC" "2011-10-31 UTC" "2011-11-30 UTC" "2011-12-31 UTC"
By the way, how do I get rid of the pesky "UTC" designations. Time zones are a life saver when they are needed. The rest of the time they are a nuisance.
Upvotes: 23