Ruby: get last day of month while sum - ruby-on-rails

Looking for a best way to get month with last day during adding operation.
For examle: I am getting last day of Movember and I need to add 1 month from this day, 1 full calendar month:
2.6.3 :002 > date = Date.strptime('November 30, 2020', '%B %d, %Y')
=> Mon, 30 Nov 2020
2.6.3 :003 > date + 1.month
=> Wed, 30 Dec 2020
This is wrong for me, because it will be 31 days in December.
Another example:
2.6.3 :005 > date2 = Date.strptime('February 29, 2020', '%B %d, %Y')
=> Sat, 29 Feb 2020
2.6.3 :006 > date2 + 1.month
=> Sun, 29 Mar 2020
I am expecting 31 of Mar instead of 29
For now - I know only one solution - something like:
2.6.3 :017 > (date + 1.month).end_of_month
=> Thu, 31 Dec 2020
2.6.3 :018 > (date2 + 1.month).end_of_month
=> Tue, 31 Mar 2020
Is it the correct and best way?

I believe the solution you came up with is the right way to solve the problem. What I had in mind is very similar:
date.next_month.end_of_month

It's easy to do this with the pure-Ruby methods Date::new and Date#>>.
require 'date'
def end_of_n_months_hence(curr_date, n)
(Date.new(curr_date.year, curr_date.month, 1) >> (n+1)) - 1
end
curr_date = Date.today
#=> #<Date: 2020-11-25 ((2459179j,0s,0n),+0s,2299161j)>
end_of_n_months_hence(curr_date, 0)
#=> #<Date: 2020-11-30 ((2459184j,0s,0n),+0s,2299161j)>
end_of_n_months_hence(curr_date, 1)
#=> #<Date: 2020-12-31 ((2459215j,0s,0n),+0s,2299161j)>
end_of_n_months_hence(curr_date, 2)
#=> #<Date: 2021-01-31 ((2459246j,0s,0n),+0s,2299161j)>
end_of_n_months_hence(curr_date, -1)
#=> #<Date: 2020-10-31 ((2459154j,0s,0n),+0s,2299161j)>
end_of_n_months_hence(curr_date, -2)
#=> #<Date: 2020-09-30 ((2459123j,0s,0n),+0s,2299161j)>
end_of_n_months_hence(curr_date, 12)
#=> #<Date: 2021-11-30 ((2459549j,0s,0n),+0s,2299161j)>
end_of_n_months_hence(curr_date, 13)
#=> #<Date: 2021-12-31 ((2459580j,0s,0n),+0s,2299161j)>
I don't know Rails but I expect you could change the operative line of the method to:
(curr_date >> n).end_of_month

Related

Ruby date skips a date on local console

I see this in my Rails console
[7] pry(main)> Date.today
=> Sat, 16 Sep 2017
[8] pry(main)> Date.tomorrow
=> Mon, 18 Sep 2017
What could be the reason for missing a date (17 Sep)?

Rails datetime format "dd/mm/yyyy"

I receive dates like "14/04/17 07:00"
I want to get only the date like this: 14/04/2017
And only the hour 07:00
I tried this way, BTW I know it's for US time, it render me 17/04/10
DateTime.parse(datetime).strftime("%d/%m/%Y")
Humm I can't find anywhere how to do this?
Thanks for your help
EDIT
with the suggestion:
my_date = DateTime.parse(datetime).strftime("%d/%m/%Y")
If I do in my terminal:
[1] pry(main)> O = Onduleur.last
Onduleur Load (0.2ms) SELECT "onduleurs".* FROM "onduleurs" ORDER BY "onduleurs"."id" DESC LIMIT ? [["LIMIT", 1]]
=> #<Onduleur:0x007fa6b70186e0
id: 144,
identifier: 2,
datetime: "11/04/17 23:00",
energy: 0,
created_at: Wed, 12 Apr 2017 15:17:04 UTC +00:00,
updated_at: Wed, 12 Apr 2017 15:17:04 UTC +00:00>
[2] pry(main)> my_date = DateTime.parse(O.datetime).strftime("%d/%m/%Y")
=> "17/04/2011"
[3] pry(main)>
For reading you can use strptime and specify the format:
datetime = "14/04/17 07:00"
DateTime.strptime(datetime, "%d/%m/%y %R")
=> Wed, 14 Apr 0017 07:00:00 +0000
Explanation
%d - Day of the month, zero-padded (01..31)
%m - Month of the year, zero-padded (01..12)
%y - year % 100 (00..99)
%R - 24-hour time (%H:%M)
For getting the date you can transform DateTime objects to Date objects using to_date or use .strftime("%d/%m/%Y") directly on DateTime to get String.
[47] pry(main)> a
=> Fri, 14 Apr 2017 07:00:00 +0000
[48] pry(main)> a.to_date
=> Fri, 14 Apr 2017
[49] pry(main)> a.strftime("%d/%m/%Y")
=> "14/04/2017"
[50] pry(main)> a.strftime("%R")
=> "07:00"
Full docs here. Also a full list of format directives is available on strftime docs
You can set to variables with the desire data
datetime = "2017-04-12 10:30:14"
my_date = DateTime.parse(datetime).strftime("%d/%m/%Y")
my_hour = DateTime.parse(datetime).strftime("%H:%M")
puts my_date
puts my_hour
Output:
12/04/2017
10:30
EDIT:
If you have this format date (11/04/2017 23:00), you can try with .to_time method
require 'active_support/core_ext/string'
datetime2 = "11/04/2017 23:00".to_time
my_date2 = datetime2.strftime("%d/%m/%Y")
my_hour2 = datetime2.strftime("%H:%M")
puts my_date2
puts my_hour2

1.year.from_now is also one day ahead

Today's date is 2016-09-19. I add one year to it, and I expect the result to be 2017-09-19. Instead, I get 2017-09-20. One year plus one day ahead. Is this behavior as intended?
$ rails c
2.3.1 :001 > Time.now.to_date.iso8601
=> "2016-09-19"
2.3.1 :002 > 1.year.from_now.to_date.iso8601
=> "2017-09-20"
If you want get ecactly date with time zones, you can use Time.current
1.year.from_now
#=> Wed, 20 Sep 2017 05:38:50 UTC +00:00
Time.current
#=> Tue, 20 Sep 2016 05:39:08 UTC +00:00
since or its alias from_now calculate the offset based on Time.current which is equivalent to Time.zone.now – both return a ActiveSupport::TimeWithZone instance:
Time.current #=> Mon, 19 Sep 2016 19:56:34 SST -11:00
Time.zone.now #=> Mon, 19 Sep 2016 19:56:35 SST -11:00
Time.now on the other hand returns an ordinary Time instance in your system's timezone, which can differ from the Rails timezone:
Time.now #=> 2016-09-20 08:56:36 +0200
To avoid confusion, you should always use Time.current / Time.zone.now when working within Rails. You can however pass another "base time" to since:
1.year.since(Time.now) #=> 2017-09-20 08:56:37 +0200
Or, because you are working with dates:
1.year.since(Date.today) #=> Wed, 20 Sep 2017
There is also Date.current which is equivalent to Time.zone.today:
1.year.since(Date.current) #=> Wed, 19 Sep 2017
Turns out it's a timezone thing. I'm UTC -7 hours.
Time.now returns the time in my timezone.
1.year.from_now returns the time in UTC+0, 7 hours ahead of where I am.
It's 10pm here, so it's the next day at UTC+0.

How to get start day and end day of a particular month and year in ruby?

Iam having the month value like (1 to 12) and year value like 2011 to 2012 by passing these parameters i want to get the starting day and ending day of the paticular month and year. how can i do this in ruby?
In addition to jfornoff's excellent answer, Rails' Active Support gem comes with a rich set of date and time methods:
require 'active_support/core_ext/date' #to cherry pick just date helpers
d = Date.new(2011, 4) #=> Fri, 01 Apr 2011
d.beginning_of_month #=> Fri, 01 Apr 2011
d.end_of_month #=> Sat, 30 Apr 2011
d.all_month #=> Fri, 01 Apr 2011..Sat, 30 Apr 2011
Date.civil sounds like what you need
start_date = Date.civil(2011, 1, 1) #=> Sat, 01 Jan 2011
end_date = Date.civil(2011, 12, -1) #=> Sat, 31 Dec 2011
Docs: here
require 'date'
start_date = Date.new(2016, 2)
#=> #<Date: 2016-02-01 ((2457420j,0s,0n),+0s,2299161j)>
end_date = (start_date >> 1) - 1
#=> #<Date: 2016-02-29 ((2457448j,0s,0n),+0s,2299161j)>

Rails: get #beginning_of_day in time zone

I have a default time zone setup for the rails application.
And an instance of the Date object.
How can I get make Date#beginning_of_day to return the beginning of the day in the specified time zone, but not my local timezone.
Is there any other method to get beginning of the day time in the specified timezone for the given date?
date = Date.new(2014,10,29)
zone = ActiveSupport::TimeZone.new('CET')
date.foo(zone) # should return "Wed, 29 Oct 2014 00:00:00 CET +01:00"
zone = ActiveSupport::TimeZone.new('UTC')
date.foo(zone) # should return "Wed, 29 Oct 2014 00:00:00 UTC +00:00"
DateTime.now.in_time_zone(Time.zone).beginning_of_day
time_zone = Time.zone # any time zone really
time_zone.local(date.year, date.month, date.day)
Problem is, Date.beginning_of_day does not honor Time.zone in ActiveSupport 2.3
Compare https://github.com/rails/rails/blob/v2.3.11/activesupport/lib/active_support/core_ext/date/calculations.rb#L64 (AS 2.3)
to https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/date/calculations.rb#L74
and
https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/date/zones.rb#L7
(AS 3)
Date#beginning_of_day will always return 00:00.
But as I understand you want to know time in other time zone while in current time zone is beginning of the day.
So. Let's find out beginning of the day in your current place. Imagine it is Paris, France:
bd = DateTime.now.in_time_zone('Paris').beginning_of_day
# or just
bd = DateTime.now.in_time_zone(1).beginning_of_day
#=> Thu, 24 Mar 2011 00:00:00 WET +01:00
Now lets found out what time is in Moscow:
moscow_time = bd.in_time_zone("Moscow") # or in_time_zone(3)
#=> Thu, 24 Mar 2011 02:00:00 AST +03:00
london_time = bd.in_time_zone("London")
#=> Wed, 23 Mar 2011 23:00:00 GMT +00:00
kyiv_time = bd.in_time_zone("Kyiv")
#=> Thu, 24 Mar 2011 01:00:00 EET +02:00
For different form now day:
# You even shouldn't call now, because it by default will be 00:00
date = DateTime(2011, 1, 3).in_time_zone("-10")
# or
date = DateTime.new(2011,1,3,0,0,0,"-10")
# and same way as above
moscow_time = date.in_time_zone("Moscow") # or in_time_zone(3)
and converting Date to DateTime
date = Date.new(2011,1,3).to_datetime.change(:offset => "EST")
Going off Peder's answer, here's what I did for the PST time zone:
DateTime.now.in_time_zone("Pacific Time (US & Canada)").beginning_of_day
I know this post is old, but what about:
Time.zone.parse("12am")
may2 = Date.new(2012,5,2)
midnight = Time.zone.local(may2.year,may2.month,may2.day).beginning_of_day
Wed, 02 May 2012 00:00:00 UTC +00:00
midnight = Time.zone.local(may2.year,may2.month,may2.day).in_time_zone("Moscow").beginning_of_day
=> Wed, 02 May 2012 00:00:00 MSK +04:00
midnight = Time.zone.local(may2.year,may2.month,may2.day).in_time_zone("Alaska").beginning_of_day
=> Tue, 01 May 2012 00:00:00 AKDT -08:00
NOTICE, it's the WRONG DAY.
What is needed is a way to construct a TimeWithZone in the correct timezone.
Time.zone="Alaska"
midnight = Time.zone.local(may2.year,may2.month,may2.day)
I really dislike this, because as far as I can see, I've just changed the system notion of what zone I'm in. The idea is to change my database searches to match the zone of the client...
So, I have to save zone and restore it:
foo = Time.zone; Time.zone="Alaska"; midnight = Time.zone.local(may2.year,may2.month,may2.day); Time.zone = foo
It seems like I ought be able to call TimeWithZone.new(), but I didn't figure out how.
ActiveSupport::TimeZone['Europe/London'].parse('30.07.2013') # 2013-07-29 23:00:00 UTC
ActiveSupport::TimeZone['Asia/Magadan'].parse('30.07.2013') # 2013-07-29 12:00:00 UTC
As Leonid Shevtsov mentioned, Date.beginning_of_day does not honor Time.zone in ActiveSupport 2.3
An alternative I used, if your stuck using Rails 4.0 or ActiveSupport 2.3, and you need to use a custom date:
date = Date.new(2014,10,29)
date.to_time.change(hour: 0, min: 0, sec: 0).in_time_zone #.beginning_of_day
date.to_time.change(hour: 23, min: 59, sec: 59).in_time_zone #.end_of_day
Results:
2.0.0-p247 :001 > date = Date.new(2014,10,29)
=> Wed, 29 Oct 2014
2.0.0-p247 :002 > date.to_time.change(hour: 0, min: 0, sec: 0)
=> 2014-10-29 00:00:00 -0500
2.0.0-p247 :003 > date.to_time.change(hour: 0, min: 0, sec: 0).in_time_zone
=> Wed, 29 Oct 2014 05:00:00 UTC +00:00
2.0.0-p247 :004 > date.to_time.change(hour: 23, min: 59, sec: 59)
=> 2014-10-29 23:59:59 -0500
2.0.0-p247 :005 > date.to_time.change(hour: 23, min: 59, sec: 59).in_time_zone
=> Thu, 30 Oct 2014 04:59:59 UTC +00:00
My original failed model scope using .beginning_of_day to .end_of_day failed to work:
scope :on_day, ->(date) { where( created_at: date.beginning_of_day..date.end_of_day ) }
And, this is what fixed it, since I could not upgrade to Rails 4.0
scope :on_day, ->(date) { where( created_at: date.to_time.change(hour: 0, min: 0, sec: 0).in_time_zone..date.to_time.change(hour: 23, min: 59, sec: 59).in_time_zone ) }
Generate new date in a specific time zone
If you would like to get beginning of the day for a specific date (and you know the exact date), then you have to use:
Time.use_zone('London'){ Time.zone.local(2022, 1, 1) }
# => Sat, 01 Jan 2022 00:00:00.000000000 GMT +00:00
It respects seasonal time change:
Time.use_zone('London'){ Time.zone.local(2022, 6, 1) }
# => Wed, 01 Jun 2022 00:00:00.000000000 BST +01:00
Although it is already referred by accepted answer I'd like to add that personally I prefer block notation.
Convert existing and find out what the day it is in another time zone
If you would like to know what day that would be for a specific timestamp/epoch/date that you already have you need to convert your existing date to the proper time zone, which is already referred by Peder's and Tim's answers:
Time.current.in_time_zone('London').beginning_of_day
Be aware, when converting existing date you may end up in another day (1st became 2nd):
Time.use_zone('London'){ Time.zone.local(2022, 6, 1, 23, 0, 0) }.in_time_zone('Tokyo').beginning_of_day
# => Thu, 02 Jun 2022 00:00:00.000000000 JST +09:00
If you are going to convert time, then Time.now or Time.current is indifferent, but I prefer using Time.current as a rule:
Time.now
# => 2022-09-30 15:29:16 +0000
Time.current
=> Fri, 30 Sep 2022 16:29:36.006315710 BST +01:00

Resources