How to find date of end of week in Ruby? - ruby-on-rails

I have the following date object in Ruby
Date.new(2009, 11, 19)
How would I find the next Friday?

You could use end_of_week (AFAIK only available in Rails)
>> Date.new(2009, 11, 19).end_of_week - 2
=> Fri, 20 Nov 2009
But this might not work, depending what exactly you want. Another way could be to
>> d = Date.new(2009, 11, 19)
>> (d..(d+7)).find{|d| d.cwday == 5}
=> Fri, 20 Nov 2009
lets assume you want to have the next friday if d is already a friday:
>> d = Date.new(2009, 11, 20) # was friday
>> ((d+1)..(d+7)).find{|d| d.cwday == 5}
=> Fri, 27 Nov 2009

Old question but if you're using rails you can now do the following to get next Friday.
Date.today.sunday + 5.days
Likewise you can do the following to get this Friday.
Date.today.monday + 4.days

d = Date.new(2009, 11, 19)
d+=(5-d.wday) > 0 ? 5 - d.wday : 7 + 5 - d.wday

Rails' ActiveSupport::CoreExtensions::Date::Calculations has methods that can help you. If you're not using Rails, you could just require ActiveSupport.

As Ruby's modulo operation (%) returns positive numbers when your divisor is positive, you can do this:
some_date = Date.new(2009, 11, 19)
next_friday = some_date + (5 - some_date.cwday) % 7
The only issue I can see here is that if some_date is a Friday, next_friday will be the same date as some_date. If that's not the desired behavior, a slight modification can be used instead:
some_date = Date.new(...)
day_increment = (5 - some_date.cwday) % 7
day_increment = 7 if day_increment == 0
next_friday = some_date + day_increment
This code doesn't rely on additional external dependencies, and relies mostly on integer arithmetic.

Related

Ruby on Rails convert date and time value properly formatted utc time

I have a date value like 2016-11-19 and a time value like 2000-01-01 20:14:00 +0000
I want to convert it to proper date format which will show utc time like below:
2016-11-20 04:14:00 +0000
There lots of questions regarding this, but I am confused.
You need to convert your date value to a time value:
>> date = Date.new(2016, 11, 19)
>> time = date.to_time
=> 2016-11-19 00:00:00 -0700
Note that time will be set to midnight in the time zone to which your server is set. Now that you have a time object, you can add seconds to it:
>> time_increment = Time.new(2000, 1, 1, 20, 14, 0)
>> seconds_increment = time_increment.seconds_since_midnight
=> 72840.0
>> new_time = time + seconds_increment
=> Sat, 19 Nov 2016 20:14:00 MST -07:00
You can use Ruby strftime datetime format.
Like
date_value.strftime('%Y-%m-%d %H:%M:%S') #=> 24-hour clock
date_value.strftime('%Y-%m-%d %I:%M:%S') #=> 12-hour clock
Full documentation
Thanks everyone! But, I could solve the problem on my own!
time = bid_end_time.hour.to_s + ":" + bid_end_time.min.to_s + ":" + bid_end_time.sec.to_s
date_time = bid_end_date.to_s + " " + time
bid_end_date_time = Time.zone.parse(date_time).utc

breeze date (ko.observable) to moment not converting correctly

I have a breeze entity with a date in it, I need to get the year, month, day separately and was going to use momentjs to do it but I'm getting some strange results for something that I would have thought would be quite simple:
var dob = moment(observableDate());
console.log(observableDate() + ' to -> ' + dob.day() + ' - ' + dob.month() + ' - ' + dob.year());
//ouput
//Thu Dec 18 1975 11:00:00 GMT+1100 (AUS Eastern Summer Time) to -> 4 - 11 - 1975
I don't understand where the 4th of Nov is coming from....
The date is stored in Sql Server and the value is '1975-12-18 00:00:00.000'
Thanks in advance.
According to the moment.js documentation
day() returns the day of the week, i.e. a number between 0 and 6; (4 == thursday).
month() returns the month of the year but 0 origin. i.e. a number between 0 and 11 - (11 == december)
See: Moment.js docs

Rails, I want to get a list of articles by month, help in tweaking my query

I need the output to be like:
MONTH YEAR COUNT
Nov 2009 12
Oct 2009 3
Sep 2009 1
...
..
I have so far:
#articles = Article.count(:group=>'MONTH(created_at)')
But this returns:
COUNT MONTH_IN_DIGIT_FORM
1 12
32 11
3 10
..
..
This is not very portable to another DB (MySQL right now), but this will work:
Article.count(:group=>"DATE_FORMAT(created_at, '%Y %b')")

Rails times oddness : "x days from now"

when users sign up to one of my sites for a free trial, i set their account expiry to be "14.days.from_now". Then on the home page i show how many days they have remaining, which i get with:
(user.trial_expires - Time.now)/86400
(because there are 86400 seconds in a day, ie 60 * 60 * 24)
The funny thing is, this comes out as more than 14, so gets rounded up to 15. On closer investigation in the console this happens for just two days in the future (if you know what i mean). eg
>> Time.now
=> Fri Oct 29 11:09:26 0100 2010
>> future_1_day = 1.day.from_now
=> Sat, 30 Oct 2010 11:09:27 BST 01:00
#ten past eleven tomorrow
>> (future_1_day - Time.now)/86400
=> 0.999782301526931
#less than 1, what you'd expect right?
>> future_2_day = 2.day.from_now
=> Sun, 31 Oct 2010 11:09:52 GMT 00:00
>> (future_2_day - Time.now)/86400
=> 2.04162248861183
#greater than 2 - why?
I thought maybe it was to do with timezones - i noticed that the time from 1.day from now was in BST and the time 2 days from now was in GMT. So, i tried using localtime and got the same results!
>> future_2_day = 2.day.from_now.localtime
=> Sun Oct 31 11:11:24 0000 2010
>> (future_2_day - Time.now)/86400
=> 2.04160829127315
>> (future_2_day - Time.now.localtime)/86400
=> 2.04058651585648
I then wondered how big the difference is, and it turns out that it is exactly an hour out. So it looks like some time zone weirdness, or at least something to do with time zones that i don't understand. Currently my time zone is BST (british summer time) which is one hour later than UTC at the moment (till this sunday at which point it reverts to the same as UTC).
The extra hour seems to be introduced when i add two days to Time.now: check this out. I start with Time.now, add two days to it, subtract Time.now, then subtract two days of seconds from the result, and am left with an hour.
It just occurred to me, in a head slapping moment, that this is occurring BECAUSE the clocks go back on sunday morning: ie at 11.20 on sunday morning it will be two days AND an extra hour from now. I was about to delete all of this post, but then i noticed this: i thought 'ah, i can fix this by using (24*daynum).hours instead of daynum.days, but i still get the same result: even when i use seconds!
>> (Time.now + (2*24).hours - Time.now) - 86400*2
=> 3599.99969500001
>> (Time.now + (2*24*3600).seconds - Time.now) - 86400*2
=> 3599.999855
So now i'm confused again. How can now plus two days worth of seconds, minus now, minus two days worth of seconds be an hour worth of seconds? Where does the extra hour sneak in?
As willcodejavaforfood has commented, this is due to daylight saving time which ends this weekend.
When adding a duration ActiveSupport has some code in it to compensate for if the starting time is in DST and the resulting time isn't (or vice versa).
def since(seconds)
f = seconds.since(self)
if ActiveSupport::Duration === seconds
f
else
initial_dst = self.dst? ? 1 : 0
final_dst = f.dst? ? 1 : 0
(seconds.abs >= 86400 && initial_dst != final_dst) ? f + (initial_dst - final_dst).hours : f
end
rescue
self.to_datetime.since(seconds)
end
If you have 11:09:27 and add a number of days you will still get 11:09:27 on the resulting day even if the DST has changed. This results in an extra hour when you come to do calculations in seconds.
A couple of ideas:
Use the distance_of_time_in_words helper method to give the user an indication of how long is left in their trial.
Calculate the expiry as Time.now + (14 * 86400) instead of using 14.days.from_now - but some users might claim that they have lost an hour of their trial.
Set trials to expire at 23:59:59 on the expiry day regardless of the actual signup time.
You could use the Date class to calculate the number of days between today and the expire date.
expire_date = Date.new(user.trial_expires.year, user.trial_expires.month, user.trial_expires.day)
days_until_expiration = (expire_date - Date.today).to_i
Use since, example:
14.days.since.to_date

Calculate number of business days between two days

I need to calculate the number of business days between two dates. How can I pull that off using Ruby (or Rails...if there are Rails-specific helpers).
Likewise, I'd like to be able to add business days to a given date.
So if a date fell on a Thursday and I added 3 business days, it would return the next Tuesday.
Take a look at business_time. It can be used for both the things you're asking.
Calculating business days between two dates:
wednesday = Date.parse("October 17, 2018")
monday = Date.parse("October 22, 2018")
wednesday.business_days_until(monday) # => 3
Adding business days to a given date:
4.business_days.from_now
8.business_days.after(some_date)
Historical answer
When this question was originally asked, business_time didn't provide the business_days_until method so the method below was provided to answer the first part of the question.
This could still be useful to someone who didn't need any of the other functionality from business_time and wanted to avoid adding an additional dependency.
def business_days_between(date1, date2)
business_days = 0
date = date2
while date > date1
business_days = business_days + 1 unless date.saturday? or date.sunday?
date = date - 1.day
end
business_days
end
This can also be fine tuned to handle the cases that Tipx mentions in the way that you would like.
We used to use the algorithm suggested in the mikej's answer and discovered that calculating 25,000 ranges of several years each takes 340 seconds.
Here's another algorithm with asymptotic complexity O(1). It does the same calculations in 0.41 seconds.
# Calculates the number of business days in range (start_date, end_date]
#
# #param start_date [Date]
# #param end_date [Date]
#
# #return [Fixnum]
def business_days_between(start_date, end_date)
days_between = (end_date - start_date).to_i
return 0 unless days_between > 0
# Assuming we need to calculate days from 9th to 25th, 10-23 are covered
# by whole weeks, and 24-25 are extra days.
#
# Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 # 1 2 3 4 5
# 6 7 8 9 10 11 12 # 6 7 8 9 ww ww ww
# 13 14 15 16 17 18 19 # ww ww ww ww ww ww ww
# 20 21 22 23 24 25 26 # ww ww ww ww ed ed 26
# 27 28 29 30 31 # 27 28 29 30 31
whole_weeks, extra_days = days_between.divmod(7)
unless extra_days.zero?
# Extra days start from the week day next to start_day,
# and end on end_date's week date. The position of the
# start date in a week can be either before (the left calendar)
# or after (the right one) the end date.
#
# Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 # 1 2 3 4 5
# 6 7 8 9 10 11 12 # 6 7 8 9 10 11 12
# ## ## ## ## 17 18 19 # 13 14 15 16 ## ## ##
# 20 21 22 23 24 25 26 # ## 21 22 23 24 25 26
# 27 28 29 30 31 # 27 28 29 30 31
#
# If some of the extra_days fall on a weekend, they need to be subtracted.
# In the first case only corner days can be days off,
# and in the second case there are indeed two such days.
extra_days -= if start_date.tomorrow.wday <= end_date.wday
[start_date.tomorrow.sunday?, end_date.saturday?].count(true)
else
2
end
end
(whole_weeks * 5) + extra_days
end
business_time has all the functionallity you want.
From the readme:
#you can also calculate business duration between two dates
friday = Date.parse("December 24, 2010")
monday = Date.parse("December 27, 2010")
friday.business_days_until(monday) #=> 1
Adding business days to a given date:
some_date = Date.parse("August 4th, 1969")
8.business_days.after(some_date) #=> 14 Aug 1969
Here is my (non gem and non holiday) weekday count example:
first_date = Date.new(2016,1,5)
second_date = Date.new(2016,1,12)
count = 0
(first_date...second_date).each{|d| count+=1 if (1..5).include?(d.wday)}
count
Take a look at Workpattern. It alows you to specify working and resting periods and can add/subtract durations to/from a date as well as calculate the minutes between two dates.
You can set up workpatterns for different scenarios such as mon-fri working or sun-thu and you can have holidays and whole or part days.
I wrote this as away to learn Ruby. Still need to make it more Ruby-ish.
Based on #mikej's answer. But this also takes into account holidays, and returns a fraction of a day (up to the hour accurancy):
def num_days hi, lo
num_hours = 0
while hi > lo
num_hours += 1 if hi.workday? and !hi.holiday?
hi -= 1.hour
end
num_hours.to_f / 24
end
This uses the holidays and business_time gems.
Simple script to calculate total number of working days
require 'date'
(DateTime.parse('2016-01-01')...DateTime.parse('2017-01-01')).
inject({}) do |s,e|
s[e.month]||=0
if((1..5).include?(e.wday))
s[e.month]+=1
end
s
end
# => {1=>21, 2=>21, 3=>23, 4=>21, 5=>22, 6=>22, 7=>21, 8=>23, 9=>22, 10=>21, 11=>22, 12=>22}
There are two problems with the most popular solutions listed above:
They involve loops to count every single day between each date (meaning that performance gets worse the further apart the dates are.
They are unclear about whether they count from the beginning of the day or the end. If you count from the morning, there is one weekday between Friday and Saturday. If you count from the night, there are zero weekdays between Friday and Saturday.
After stewing over it, I propose this solution that addresses both problems. The below takes a reference date and an other date and calculates the number of weekdays between them (returning a negative number if other is before the reference date). The argument eod_base controls whether counting is done from end of day (eod) or start of day. It could be written more compactly but hopefully it's relatively easy to understand and it doesn't require gems or rails.
require 'date'
def weekdays_between(ref,otr,eod_base=true)
dates = [ref,otr].sort
return 0 if dates[0] == dates[1]
full_weeks = ((dates[1]-dates[0])/7).floor
dates[eod_base ? 0 : 1] += (eod_base ? 1 : -1)
part_week = Range.new(dates[0],dates[1])
.inject(0){|m,v| (v.wday >=1 && v.wday <= 5) ? (m+1) : m }
return (otr <=> ref) * (full_weeks*5 + part_week)
end

Resources