41

I have a Time object and would like to find the next/previous month. Adding subtracting days does not work as the days per month vary.

time = Time.parse('21-12-2008 10:51 UTC')
next_month = time + 31 * 24 * 60 * 60

Incrementing the month also falls down as one would have to take care of the rolling

time = Time.parse('21-12-2008 10:51 UTC')
next_month = Time.utc(time.year, time.month+1)

time = Time.parse('01-12-2008 10:51 UTC')
previous_month = Time.utc(time.year, time.month-1)

The only thing I found working was

time = Time.parse('21-12-2008 10:51 UTC')
d = Date.new(time.year, time.month, time.day)
d >>= 1
next_month = Time.utc(d.year, d.month, d.day, time.hour, time.min, time.sec, time.usec)

Is there a more elegant way of doing this that I am not seeing? How would you do it?

tcurdt
  • 14,518
  • 10
  • 57
  • 72

13 Answers13

95

Ruby on Rails

Note: This only works in Rails (Thanks Steve!) but I'm keeping it here in case others are using Rails and wish to use these more intuitive methods.

Super simple - thank you Ruby on Rails!

Time.now + 1.month

Time.now - 1.month  

Or, another option if it's in relation to the current time (Rails 3+ only).

1.month.from_now

1.month.ago
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
  • 1
    The `#month` method is a Rails extension. It also assumes 30 days in a month, and so will return the wrong answer on the 31st. – Steve Madsen Apr 23 '10 at 15:45
  • Fairplay, I was using the script/console - guess I'm too ROR'd for my own good. – Joshua Pinter Apr 23 '10 at 15:56
  • 12
    @Steve it doesn't assume 30 days when doing date arithmetic. It's actually pretty magical. try it! (see ActiveSupport::Duration, Date#plus_with_duration, and integer/time.rb in active_support). e.g. >> Time.utc(2010, 2, 1) + 1.months. => Mon Mar 01 00:00:00 UTC 2010 >> Time.utc(2010, 3, 1) + 1.months => Thu Apr 01 00:00:00 UTC 2010 >> Time.utc(2010, 4, 1) + 1.months => Sat May 01 00:00:00 UTC 2010 Or for an extreme example: >> Time.utc(2010, 1, 31) + 1.months => Sun Feb 28 00:00:00 UTC 2010 – John Douthat Apr 24 '10 at 05:09
  • >> Time.utc(2010, 1, 31) + 1.months => Sun Feb 28 00:00:00 UTC 2010 >> Time.utc(2008, 1, 31) + 1.months => Fri Feb 29 00:00:00 UTC 2008 – John Douthat Apr 24 '10 at 05:15
  • @John: That's new, isn't it? Do you know what version of Rails added that? – Steve Madsen Apr 25 '10 at 01:24
  • @Steve 2007ish? http://github.com/rails/rails/commit/276c9f29cde80fafa23814b0039f67504255e0fd – John Douthat Apr 25 '10 at 05:02
  • 1.month.ago is easier (RoR) – joaomilho Apr 12 '13 at 04:54
  • @joaomilho For Rails 3+, for sure. I added it to the answer, thanks. – Joshua Pinter Apr 12 '13 at 15:19
  • Shouldn't this be Time.now (Rather than DateTime.now) since 1.month returns a TIme object with timezone. – Donato Jun 02 '15 at 22:49
  • @Donato Your comment made me look up the main differences between `DateTime` and `Time`. Prior to Ruby 1.9, `DateTime` was a better choice but Ruby 1.9 and above, `Time` appears to be a better. Thanks for the nudge. Read more: http://stackoverflow.com/a/21075654/293280 – Joshua Pinter Jun 03 '15 at 17:36
26

Personally I prefer using:

Time.now.beginning_of_month - 1.day      # previous month
Time.now.end_of_month + 1.day            # next month

It always works and is independent from the number of days in a month.

Find more info in this API doc

Konstantin Rudy
  • 2,237
  • 25
  • 22
  • 1
    Best answer so far – sidney Jul 11 '17 at 10:51
  • This is strange to me, but it works if you what you really want is the last day of the previous month or the first day of the next month. If all you want is the previous or next _month_ and could care less about what _day_ of that month, there's easier and more simplistic ways to get it. – Joshua Pinter Oct 18 '18 at 01:01
14

you can use standard class DateTime

require 'date'

dt = Time.new().to_datetime
=> #<DateTime: 2010-04-23T22:31:39+03:00 (424277622199937/172800000,1/8,2299161)>

dt2 = dt >> 1
=> #<DateTime: 2010-05-23T22:31:39+03:00 (424282806199937/172800000,1/8,2299161)>

t = dt2.to_time
=> 2010-05-23 22:31:39 +0200
andrykonchin
  • 2,507
  • 3
  • 18
  • 24
11

There are no built-in methods on Time to do what you want in Ruby. I suggest you write methods to do this work in a module and extend the Time class to make their use simple in the rest of your code.

You can use DateTime, but the methods (<< and >>) are not named in a way that makes their purpose obvious to someone that hasn't used them before.

Steve Madsen
  • 13,465
  • 4
  • 49
  • 67
8

below it works

previous month:

Time.now.months_since(-1)

next month:

Time.now.months_since(1)
Richie Min
  • 654
  • 5
  • 15
  • 1
    undefined method `months_since' – tcurdt Aug 15 '13 at 13:44
  • what's your code environment, ruby version ? here is my code execute results `irb(main):053:0> Time.now.months_since(-1) => 2013-10-19 08:40:04 +0800` – Richie Min Nov 19 '13 at 01:00
  • $ ruby -v ruby 1.9.3p327 (2012-11-10 revision 37606) [x86_64-darwin12.2.1] $ irb >> Time.now.months_since(-1) NoMethodError: undefined method `months_since' for 2013-11-19 02:09:23 +0100:Time – tcurdt Nov 19 '13 at 01:09
  • 1
    months_since is activesupports's methods, you can execute `ri months_since` in your cli – Richie Min Nov 19 '13 at 01:16
8

If you do not want to load and rely on additional libraries you can use something like:

module MonthRotator
  def current_month
    self.month
  end

  def month_away
    new_month, new_year = current_month == 12 ? [1, year+1] : [(current_month + 1), year]
    Time.local(new_year, new_month, day, hour, sec)
  end

  def month_ago
    new_month, new_year = current_month == 1 ? [12, year-1] : [(current_month - 1), year]
    Time.local(new_year, new_month, day, hour, sec)
  end
end

class Time
  include MonthRotator
end


require 'minitest/autorun'

class MonthRotatorTest < MiniTest::Unit::TestCase
  describe "A month rotator Time extension" do
    it 'should return a next month' do
      next_month_date = Time.local(2010, 12).month_away
      assert_equal next_month_date.month, 1
      assert_equal next_month_date.year, 2011
    end

    it 'should return previous month' do
      previous_month_date = Time.local(2011, 1).month_ago
      assert_equal previous_month_date.month, 12
      assert_equal previous_month_date.year, 2010
    end
  end
end
gmcnaughton
  • 2,233
  • 1
  • 21
  • 28
user1376019
  • 151
  • 1
  • 4
6

I just want to add my plain ruby solution for completeness replace the format in strftime to desired output

DateTime.now.prev_month.strftime("%Y-%m-%d")
DateTime.now.next_month.strftime("%Y-%m-%d")
Volker Schmitt
  • 101
  • 1
  • 4
1

You can try convert to datetime.

Time gives you current date, and DateTime allows you to operate with.

Look at this:

irb(main):041:0> Time.new.strftime("%d/%m/%Y")
        => "21/05/2015"

irb(main):040:0> Time.new.to_datetime.prev_month.strftime("%d/%m/%Y")
    => "21/04/2015"
1

Here is a solution on plain ruby without RoR, works on old ruby versions.

t=Time.local(2000,"jan",1,20,15,1,0);
curmon=t.mon;
prevmon=(Time.local(t.year,t.mon,1,0,0,0,0)-1).mon ;
puts "#{curmon} #{prevmon}"
Dmitry Perfilyev
  • 536
  • 1
  • 4
  • 14
0

Some of the solutions assume rails. But, in pure ruby you can do the following

require 'date'    

d = Date.now
last_month = d<<1

last_month.strftime("%Y-%m-%d")
Krishna Vedula
  • 1,643
  • 1
  • 27
  • 31
0

Im using the ActiveSupport::TimeZone for this example, but just in case you are using Rails or ActiveSupport it might come in handy.

If you want the previous month you can substract 1 month

time = Time.zone.parse('21-12-2008 10:51 UTC')
time.ago(1.month)
Lomefin
  • 1,173
  • 12
  • 43
0

You can get the previous month info by this code

require 'time'
time = Time.parse('2021-09-29 12:31 UTC')
time.prev_month.strftime("%b %Y")
Ramyani
  • 1,019
  • 1
  • 9
  • 12
-2
$ irb
irb(main):001:0> time = Time.now
=> 2016-11-21 10:16:31 -0800
irb(main):002:0> year = time.year
=> 2016
irb(main):003:0> month = time.month
=> 11
irb(main):004:0> last_month = month - 1
=> 10
irb(main):005:0> puts time
2016-11-21 10:16:31 -0800
=> nil
irb(main):006:0> puts year
2016
=> nil
irb(main):007:0> puts month
11
=> nil
irb(main):008:0> puts last_month
10
=> nil
KLaw
  • 1,619
  • 1
  • 12
  • 7