I want to find the first weekday of the month after the 1st which also happens to fall on a weekday in Ruby, something like the following:
Min ( DayOfMonth in (2...8) && DayOfWeek in ( monday ... friday ) )
How can I state this appropriately?
We can simply start at the second day of the desired month, and keep going to the next day until we find a weekday. This will require 2 increments at most, since the worst case is when day 2 is a Saturday and 3 a Sunday. Our target then would be the 4th day. If Sunday is the 2nd day, our target is 3, and otherwise the desired day is the 2nd of the month. My first_wday method returns a Date object as well. You can use .day to get the day of the month, or .wday to get the day of the week on it.
require 'date'
def first_wday(date)
d = Date.new(date.year, date.month, 2)
d += 1 while d.wday == 6 || d.wday == 0 # Saturday or Sunday
d
end
(1..12).each do |month|
date = Date.new(2014, month, 1)
wday = first_wday(date)
puts wday.strftime('%9B %Y: %-d (%A)')
end
# January 2014: 2 (Thursday)
# February 2014: 3 (Monday)
# March 2014: 3 (Monday)
# April 2014: 2 (Wednesday)
# May 2014: 2 (Friday)
# June 2014: 2 (Monday)
# July 2014: 2 (Wednesday)
# August 2014: 4 (Monday)
# September 2014: 2 (Tuesday)
# October 2014: 2 (Thursday)
# November 2014: 3 (Monday)
# December 2014: 2 (Tuesday)
This would be one way of doing it.
Code
require 'date'
d = Date.today
d = (d-d.day+2).next_month
(d..d+2).find { |i| (1..5).cover?(i.wday) }.strftime("%m-%d-%Y %a")
Explanation
d = Date.today #=> #<Date:2014-04-28((2456776j,0s,0n),+0s,2299161j)>
d = (d-d.day+2) #=> #<Date:2014-04-02((2456750j,0s,0n),+0s,2299161j)>
d = d.next_month #=> #<Date:2014-05-02((2456780j,0s,0n),+0s,2299161j)>
d.month #=> 5
d.year #=> 2014
d.wday #=> 5 (Friday)
At least one of the first three days of the month is a weekday, so we need only consider the range (d..d+2). The first value of (d..d+2) passed to the block is d, so:
i = d #=> #<Date: 2014-05-02 ((2456780j,0s,0n),+0s,2299161j)>
i.wday #=> 5
As Date's week starts with 0 on Sunday, the five week days are 1..5:
(1..5).cover?(i.wday) #=> true
(1..5).cover?(4) #=> true
So
i.strftime("%m-%d-%Y %a") #=> "05-02-2014 Fri"
is returned.
Related
I'm trying to build in a calendar system into my app. The problem I'm currently on is once a month schedule. Basically if a person schedules on the second Wednesday of the month I want to automatically update the schedule to the second Wednesday of the next month. I've tried just adding + 1.month but that doesn't achieve exactly what I want. Because it moves it to the next month date not day of the week. Does anyone know how I can achieve this. Here is what I'm currently doing that is not right.
def call
schedule.update!(
end_at: end_at_next_month,
start_at: start_at_next_month,
status: "monthly",
arranged: true
)
end
private
def schedule
context.schedule
end
def end_at_next_month
schedule.end_at + 1.month
end
def start_at_next_month
schedule.start_at + 1.month
end
end
require 'date'
def date_of_nth_wday(month, year, wday, nth)
base_date = Date.new(year, month)
base_wday = base_date.wday
ret_date = base_date +
( wday > base_wday ? wday - base_wday : 7 - base_wday + wday) +
7*(nth-1)
ret_date.month == month ? ret_date : nil
end
Obtain the date of the second Sunday of August, 2017
date_of_nth_wday(8, 2017, 0, 2)
#=> #<Date: 2017-08-13 ((2457979j,0s,0n),+0s,2299161j)>
Obtain the date of the third Friday of August, 2017
date_of_nth_wday(8, 2017, 5, 3)
#=> #<Date: 2017-08-18 ((2457984j,0s,0n),+0s,2299161j)>
Obtain the date of the fifth Monday of August, 2017
date_of_nth_wday(8, 2017, 1, 5)
#=> nil (there are only 4 Mondays in August, 2017)
You can try something like this:
DAYS_MAPPING = { 0=>"Sunday",
1=>"Monday",
2=>"Tuesday",
3=>"Wednesday",
4=>"Thursday",
5=>"Friday",
6=>"Saturday" }
# Returns week of month in integer
# for example: 1 for matching day of 1st week of the month
def get_week_number(date)
week_number = date.strftime('%U').to_i - date.beginning_of_month.strftime('%U').to_i
offset = (Date.parse(DAYS_MAPPING[date.wday]) < date.beginning_of_month) ? 0 : 1
week_number + offset
end
def specific_day_of_next_month(schedule)
# wday => day of week in integer
# for example: 0 for Sunday; 1 for Monday & so on ...
wday = schedule.wday
week_number = get_week_number(schedule)
next_month = schedule + 1.month
days = (next_month.beginning_of_month..next_month.end_of_month).select { |d| d.wday == wday }
days[week_number-1]
end
Example 1:
schedule = Date.today
# => Sat, 01 Jul 2017 (First Saturday of July)
specific_day_of_next_month(schedule)
# => Sat, 05 Aug 2017 (First Saturday of August)
Example 2:
schedule = Date.new(2017, 7, 10)
# => Mon, 10 Jul 2017 (Second Monday of July)
specific_day_of_next_month(schedule)
# => Mon, 14 Aug 2017 (Second Monday of August)
Your title is different to your explanation in the text. With my example you don't get the last friday, but you could select the 1,2,3,4,5th wednesday (or any other day) of a month.
require 'date'
class Date
#~ class DateError < ArgumentError; end
#Get the third monday in march 2008: new_by_mday( 2008, 3, 1, 3)
#
#Based on http://forum.ruby-portal.de/viewtopic.php?f=1&t=6157
def self.new_by_mday(year, month, weekday, nr)
raise( ArgumentError, "No number for weekday/nr") unless weekday.respond_to?(:between?) and nr.respond_to?(:between?)
raise( ArgumentError, "Number not in Range 1..5: #{nr}") unless nr.between?(1,5)
raise( ArgumentError, "Weekday not between 0 (Sunday)and 6 (Saturday): #{nr}") unless weekday.between?(0,6)
day = (weekday-Date.new(year, month, 1).wday)%7 + (nr-1)*7 + 1
if nr == 5
lastday = (Date.new(year, (month)%12+1, 1)-1).day # each december has the same no. of days
raise "There are not 5 weekdays with number #{weekday} in month #{month}" if day > lastday
end
Date.new(year, month, day)
end
end
#2nd monday (weekday 1) in july
p Date.new_by_mday(2017,7,1,2)
If you look for a gem: date_tools supports ths function.
I am working on a project where I have to calculate the total previous expereince of an employee. So lets say an employee worked
Company 1 - 11th Feb 2008 to 23rd Feb 2010
Company 2 - 14 May 2010 to 17 Oct 2014
Company 3 - 22 Dec 2014 to 14 Jan 2017
I want to be able to Diff the dates for these three rows that will give me the difference in start and end dates in days, then add the three together to get the total number of days worked. But the problem I am running into is that I want to be able to show the total experience something like
7 years, 3 months and 14 days. In the Difference of time in words kind of format.
Any ideas on how this can be achieved.
Thanks
Here is a pure-Ruby answer.
Code
require 'date'
R1 = /
\s*-\s* # match a hypen optionally surrounded by whitespace
| # or
\s*to\s* # match 'to' optionally surrounded by whitespace
/x # free-spacing regex definition mode
R2 = /st|nd|rd|th # match one of the four pairs of letters
/x
def total_days(data)
data.lines.reduce(0) do |tot, str|
start_date, end_date = str.
chomp.
split(R1).
drop(1).
map { |s| Date.strptime(s.sub(R2, ''), '%d %b %Y') }
tot + (end_date - start_date).to_i
end
end
Example
data =<<-_
Company 1 - 11th Feb 2008 to 23rd Feb 2010
Company 2 - 14 May 2010 to 17 Oct 2014
Company 3 - 22 Dec 2014 to 14 Jan 2017
_
total_days(data)
#=> 3114
Explanation
See Date::strptime and (for date format codes) DateTime#strftime.
The steps are as follows.
a = data.lines
#=> ["Company 1 - 11th Feb 2008 to 23rd Feb 2010\n",
# "Company 2 - 14 May 2010 to 17 Oct 2014\n",
# "Company 3 - 22 Dec 2014 to 14 Jan 2017\n"]
The block variable tot is set to reduce's argument (0) and the first element of a is generated and passed to the block, becoming the value of the block variable str:
tot = 0
str = a.first
#=> "Company 1 - 11th Feb 2008 to 23rd Feb 2010\n"
The block calculation is now performed
a = str.chomp
#=> "Company 1 - 11th Feb 2008 to 23rd Feb 2010"
b = a.split(R1)
#=> ["Company 1", "11th Feb 2008", "23rd Feb 2010"]
c = b.drop(1)
#=> ["11th Feb 2008", "23rd Feb 2010"]
d = c.map { |s| Date.strptime(s.sub(R2, ''), '%d %b %Y') }
#=> [#<Date: 2008-02-11 ((2454508j,0s,0n),+0s,2299161j)>,
# #<Date: 2010-02-23 ((2455251j,0s,0n),+0s,2299161j)>]
In calculating d, the first element of c passed to the block is
s = c.first
# => "11th Feb 2008"
and the block calculation for that string is
g = s.sub(R2, '')
#=> "11 Feb 2008"
Date.strptime(g, '%d %b %Y')
#=> #<Date: 2008-02-11 ((2454508j,0s,0n),+0s,2299161j)>
Continuing,
start_date, end_date = d
#=> [#<Date: 2008-02-11 ((2454508j,0s,0n),+0s,2299161j)>,
# #<Date: 2010-02-23 ((2455251j,0s,0n),+0s,2299161j)>]
start_date
#=> #<Date: 2008-02-11 ((2454508j,0s,0n),+0s,2299161j)>
end_date
#=> #<Date: 2010-02-23 ((2455251j,0s,0n),+0s,2299161j)>
e = end_date - start_date
#=> (743/1) <rational>
f = e.to_i
#=> 743
tot + 743
#=> 743
f is number of days the person worked in the first job. The last value is the new value of the block variable tot, the cumulative number of days worked in all jobs processed so far.
The only thing that is wrong in this code is the return :P
How would you display how many Friday 13ths there in a year?
def unlucky_days(year)
require 'date'
start_date = Date.new(year)
end_date = Date.new(year+1)
my_fridays = [4]
thirteen = "13"
result = (start_date..end_date).to_a.select {|k| my_fridays.include?(k.wday) && thirteen.include?(k.strftime('%d'))}
result.length
end
I'd write:
require 'date'
(1..12).count { |month| Date.new(year, month, 13).friday? }
+1 to #MarkReed's comments. Also, why call .to_a on a range, and why use variables when the Date class in Ruby already has methods like .day and .friday? Here is how I would do it:
def unlucky_days(year)
s = Date.new(year, 1, 1)
e = Date.new(year, 12, 31)
((s...e).select {|d| d.friday? && d.day == 13 }).count
end
Your code is wrong on a few points.
Friday is weekday number 5, not 4.
Why [4].include?(n) instead of just n==4?
"13".include?("#{n}") is not just strange but incorrect, since it returns true for 1 and 3 as well as 13.
You can go cut down on the level of brute force by just looking at the twelve 13ths and counting how many are Fridays, rather than looking at all 365 or 366 days and seeing which of them are both 13th's and Fridays, as in #tokland's answer, reproduced here:
def unlucky_days(year)
(1..12).count { |month| Date.new(year, month, 13).friday? }
end
Or, since there are only 14 possibilities, you could also just use a prebuilt table:
# number of Friday the 13ths in a given year is given by
# UnluckyDays[weekday of Jan 1][0 if common, 1 if leap]
UnluckyDays = [ [2,3], [2,2], [2,1], [1,2], [3,2], [1,1], [1,1] ]
def unlucky_days(year)
UnluckyDays[Date.new(year,1,1).wday][Date.leap?(year) ? 1 : 0 ]
end
This is a variant of #Tokland's answer.
require 'date'
def count_em(year)
d = Date.new(year, 1, 13) << 1
12.times.count { (d >>= 1).friday? }
end
(2010..2016).each { |y| puts "%d Friday the 13ths in %s" % [count_em(y), y] }
# 1 Friday the 13ths in 2010
# 1 Friday the 13ths in 2011
# 3 Friday the 13ths in 2012
# 2 Friday the 13ths in 2013
# 1 Friday the 13ths in 2014
# 3 Friday the 13ths in 2015
# 1 Friday the 13ths in 2016
If this calculation (or one like it) were done often and performance was important, two hashes could be constructed, one for leap years, the other for non-leap years, with keys the day of week on which the first day of the year falls and the values the number of Friday the 13ths in such years.
I am attempting to round UNIX timestamps in Ruby to the nearest whole month. I have the following UNIX timestamps which I'd like to convert as shown--basically if the day of the month is the 15th and onward, it should round up to the next month (e.g. February 23rd rounds up to March 1st; February 9th rounds down to February 1st).
Here are the timestamps I have and the result I need help achieving:
1455846925 (Feburary 19th, 2016) => 1456790400 (March 1st, 2016)
1447476352 (November 14th, 2015) => 1446336000 (November 1st, 2015)
1242487963 (May 16th, 2009) => 1243814400 (June 1st, 2009).
I am okay solely relying on the logic of 1-14 (round down) / 15+ (round up). I realize this won't always take into account the days in a month and I can accept that for this if needed (although a solution that always takes into account the days in a given month is a bonus).
Ruby's DateTime module may be able to do it in combination with modulo of the number of seconds in a month but I'm not quite sure how to put it all together. If I can convert the UNIX timestamp directly without first translating it to a Ruby Date, that is perfectly fine too.
Thank you in advance for your assistance.
This rounds to the nearest second.
require 'time'
def round_to_month(secs)
t1 = Time.at secs
t2 = (t1.to_datetime >> 1).to_time
s1 = Time.new(t1.year, month=t1.month)
s2 = Time.new(t2.year, month=t2.month)
(t1-s1) < (s2-t1) ? s1 : s2
end
round_to_month(1455846925) # round 2016-02-18 17:55:25 -0800
#=> 2016-03-01 00:00:00 -0800
round_to_month(1447476352) # round 2015-11-13 20:45:52 -0800
#=> 2015-11-01 00:00:00 -0700
round_to_month(1242487963) # round 2009-05-16 08:32:43 -0700
#=> 2009-05-01 00:00:00 -0700
Consider
secs = 1455846925
The calculations are as follows:
t1 = Time.at secs
#=> 2016-02-18 17:55:25 -0800
dt = t1.to_datetime
#=> #<DateTime: 2016-02-18T17:55:25-08:00 ((2457438j,6925s,0n),-28800s,2299161j)>
dt_next = dt >> 1
#=> #<DateTime: 2016-03-18T17:55:25-08:00 ((2457467j,6925s,0n),-28800s,2299161j)>
t2 = dt_next.to_time
#=> 2016-03-18 18:55:25 -0700
s1 = Time.new(t1.year, month=t1.month)
#=> Time.new(2016, month=2)
#=> 2016-02-01 00:00:00 -0800
s2 = Time.new(t2.year, month=t2.month)
# Time.new(2016, month=3)
#=> 2016-03-01 00:00:00 -0800
(t1-s1) < (s2-t1) ? s1 : s2
#=> 1533325.0 < 972275.0 ? 2016-02-18 17:55:25 -0800 : 2016-03-01 00:00:00 -0800
#=> 2016-03-01 00:00:00 -0800
It would be easy to convert it to Time object and then convert it back to timestamp
If you're using Rails, this method should do, what you want:
def nearest_month(t)
time = Time.at(t).utc
time = time.next_month if time.day >= 15
time.beginning_of_month.to_i
end
I don't know if this is as accurate as #CarySwoveland's solution, but I like it:
require 'time'
FIFTEEN_DAYS = 15 * 24 * 60 * 60
def round_to_month(secs)
t1 = Time.at(secs + FIFTEEN_DAYS)
Time.new(t1.year, t1.month)
end
p round_to_month(1455846925) # round 2016-02-18 17:55:25 -0800
# => 2016-03-01 00:00:00 -0800
p round_to_month(1447476352) # round 2015-11-13 20:45:52 -0800
# => 2015-11-01 00:00:00 -0700
p round_to_month(1242487963) # round 2009-05-16 08:32:43 -0700
# => 2009-05-01 00:00:00 -0700
If you want it to return a UNIX timestamp instead just tack .to_i onto the last line in the method.
Something like this will work if you use ActiveSupport in Rails:
require 'date'
def round_to_nearest_month(timestamp)
# Convert the unix timestamp into a Ruby DateTime object
datetime = timestamp.to_datetime
# Get the day of the month from the datetime object
day_of_month = datetime.mday
if day_of_month < 15
datetime.at_beginning_of_month
else
datetime.at_beginning_of_month.next_month
end
return datetime
end
I need to generate next month date from today. Should I manuallt check if month == 12 and add +1 to d.year or there is easy solution?
Time class did strange math:
>> d = Time.now
=> 2015-12-03 15:15:36 +0300
>> d.month
=> 12
>> d.month.next
=> 13
Date has a method next_month:
require 'date'
Date.today #=> #<Date: 2015-12-03 ((2457360j,0s,0n),+0s,2299161j)>
Date.today.next_month #=> #<Date: 2016-01-03 ((2457391j,0s,0n),+0s,2299161j)>
Date.today.next_month.month #=> 1
To convert a Time instance to a Date, use to_date.
Yes add+1
but do (this month + 1)%12
if ( thisMonth == 12)
nextMonth = 1;
else
nextMonth = thisMonth+1;
This is just to give an idea
I did not see for any edge cases yet
there are two commonly used possible solutions:
1: Date.today+1.month OR for month only (Date.today+1.month).month
2: Date.today.next_month OR for month only Date.today.next_month.month