Step in a Date Interval - ruby

I trying to do a each on a Date interval with Rails 3.2.. something like this:
(1.months.ago.to_date..5.months.from_now.to_date).step(1.month).each do |date|
puts date.strftime('%m/%Y')
end
But, the step(1.month) does not work.. seems like it get the first month (ex: today is august, it will return jully) and does not iterate the other months..
Is there a way to do that?
Thanks

You are using Date as your iteration base, and 1.month translates (behind the scenes) into seconds I believe.
When you add to the Date object, it's in days, thus:
Date.today + 1 would be tomorrow
Thus, in your example, you are trying to step 2592000 days.
What you probably want is something more like:
(1.months.ago.to_date..5.months.from_now.to_date).step(30).each { |date| puts date.strftime('%m/%Y') }
If you are looking for the iterator to be smart enough to know how many days are in each month when you are "stepping" that's not going to happen. You will need to roll that on your own.
You can intelligently iterate through months by using the >> operator, so:
date = Date.today
while date < 5.months.from_now.to_date do
puts date.strftime('%m/%Y')
date = date>>1
end

how about this:
current_date, end_date = Date.today, 5.monthes.from_now.to_date
while current_date <= end_date
puts current_date
current_date = current_date.next_month
end

Related

Ruby, Recursion Method

I have a method that is basically a loop and it calls itself at the end each time. What is the best way for the method to not call itself when the date reaches a certain point? Each iteration through adds 1 day and basically processes stats for that day. It looks like the below:
def loop(start_day)
date = start_day
#do a bunch of stuff
date = date +1.day
if date > Time.now
puts "loop should be over"
end
loop(date)
end
Each iteration through adds 1 day
That's not true for the code you've posted. In your code you add 1 day to the start date once and then you keep processing the same date over and over again because you recurse on the old date (start_date), not the incremented date (date).
What is the best way for the method to not call itself when the date reaches a certain point?
Just put the recursive call inside an if or, in this case, inside of the else of the if that you already have.
Since you set date to start_date immediately, it seems there's no point in having both. Here's the more canonical form of doing recursion:
def loop(date)
return if date > Time.now
#do a bunch of stuff
loop(date + 1.day)
end
Update: If it's not obvious to you that recursion isn't necessary here, in real life, it would make more sense to do something like this:
def process_from(date)
while date <= Time.now
# Process date
date += 1.day
end
end
What about this?
def loop(start_day)
return "loop should be over" if start_day >= Time.now
#...
loop(modified_date)
end
or...
def loop(start_day)
date = start_day.dup
time = Time.now
date += 1.day while date <= time
'loop should be over'
end
It seems like you want to iterate over all days from starting date to today. Then maybe this is even more simple:
def map_date(date)
(date.to_date..Date.today).map do |d|
d.strftime("Today is %A")
end
end
You must have base case (stop case) for recursive function;
example:
def loop(date)
if date == certain_point
return [something_for_stop]
end
loop(date - 1)
end

Ruby: how to get at meridian in Time objects?

If I've got a time object:
t = Time.now
and I want to know if that time is AM or PM, right now the only way I can figure to do this is:
t.strftime("%p") == "PM"
Now, that %p is getting interpolated from something, right? Is there another way to get to it?
I ask because I'm doing some time formatting where I want to display a time range like:
"9:00 AM - 5:00 PM"
"4:00 - 5:30 PM"
"10:15 - 11:45 AM"
Right now I have to do this checking the string value of strftime, but I'd prefer to write something like:
if start_time.am? && end_time.pm? || start_time.pm? && end_time.am?
...instead of the much more verbose strftime string comparisons I'm doing now.
Based on http://www.ruby-doc.org/core-1.9.3/Time.html, I do not believe there is any other way. You could monkey-patch Time to save you some tedious strftime, however:
class Time
def meridian
self.strftime('%p')
end
def am?
self.meridian == 'AM'
end
def pm?
self.meridian == 'PM'
end
end
There isn't anything as nice as time.am? but you can use time.hour < 12 instead.
class Time
def am?
self.hour.in? (0..12)
end
def pm?
self.hour.in? (13..24)
end
end

Ruby: Is Date greater than a particular date

I am trying to do a date comparison like so:
if date > "Sept 22"
#do stuff
else
#do other stuff
end
I just can't think of how to do this in Ruby (well at least looking at a particular date).
Normal comparison operators work with dates too:
require 'date'
date = Date.new(2012,3,3)
if date > Date.new(2011,4,4)
# do whatever
end

Finding the date for a given week number

I am trying to do some date math based on the week number of a given year. For example:
date = Date.today # Monday, March 5, 2012
puts date.cwyear # 2012
puts date.cweek # 10 (10th week of 2012)
Now that I know what the current week is, I want to figure out what the next week and previous week are. I need to take the year (2012) and the week number (10) and turn it back into a date object so I can calculate the value for the next/previous week. How can I do this?
You want Date.commercial:
require 'date'
now = Date.today #=> 2012-03-05
monday_next_week = Date.commercial(now.cwyear,now.cweek+1) #=> 2012-03-12
next_sunday_or_today = monday_next_week - 1 #=> 2012-03-11
Note that weeks start on Monday, so if you are on a Sunday and ask for next monday - 1 you'll get the same day.
Note also that if you don't want Mondays you can also specify the day number in the method:
thursday_next_week = Date.commercial(now.cwyear,now.cweek+1,4) #=> 2012-03-15
Calculating on a day basis is pretty simple with Date objects. If you just want to get the previous / next week from a given Date object use the following:
date = Date.today
previous_week = (date - 7).cweek
next_week = (date + 7).cweek
In ActiveSupport you have helper to convert Fixnum to time http://as.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Numeric/Time.html use:
date = Date.today
week_ago = date - 1.week
next_week = date + 1.week
I have created some methods to get week number of a given date
something like this:
def self.get_week(date)
year = date.year
first_monday_of_the_year = self.get_first_monday_of_the_year(year)
# The first days of January may belong to the previous year!
if date < first_monday_of_the_year
year -= 1
first_monday_of_the_year = self.get_first_monday_of_the_year(year)
end
day_difference = date - first_monday_of_the_year
week = (day_difference / 7).to_i + 1
week
end
def self.get_monday_of_year_week(year, week)
d = self.get_first_monday_of_the_year(year)
d + ((week - 1) * 7).days
end
def self.get_first_monday_of_the_year(year)
d = Date.new(year, 1, 7) # 7 Jan is always in the first week
self.get_monday_of_week(d)
end
def self.get_monday_of_week(date)
wday = (date.wday + 6) % 7
date - wday.days
end
Assuming you mean "a given week number in the current year", you can do the following:
2.weeks.since(Time.gm(Time.now.year))
=> Fri Jan 15 00:00:00 UTC 2010
Substitute (week_number - 1) for the 1 in the above, and you'll get a date in the desired week.

Iterate over Ruby Time object with delta

Is there a way to iterate over a Time range in Ruby, and set the delta?
Here is an idea of what I would like to do:
for hour in (start_time..end_time, hour)
hour #=> Time object set to hour
end
You can iterate over the Time objects, but it returns every second between the two. What I really need is a way to set the offset or delta (such as minute, hour, etc.)
Is this built in to Ruby, or is there a decent plugin available?
Prior to 1.9, you could use Range#step:
(start_time..end_time).step(3600) do |hour|
# ...
end
However, this strategy is quite slow since it would call Time#succ 3600 times. Instead,
as pointed out by dolzenko in his answer, a more efficient solution is to use a simple loop:
hour = start_time
while hour < end_time
# ...
hour += 3600
end
If you're using Rails you can replace 3600 with 1.hour, which is significantly more readable.
If your start_time and end_time are actually instances of Time class then the solution with using the Range#step would be extremely inefficient since it would iterate over every second in this range with Time#succ. If you convert your times to integers the simple addition will be used but this way you will end up with something like:
(start_time.to_i..end_time.to_i).step(3600) do |hour|
hour = Time.at(hour)
# ...
end
But this also can be done with simpler and more efficient (i.e. without all the type conversions) loop:
hour = start_time
begin
# ...
end while (hour += 3600) < end_time
Range#step method is very slow in this case. Use begin..end while, as dolzenko posted here.
You can define a new method:
def time_iterate(start_time, end_time, step, &block)
begin
yield(start_time)
end while (start_time += step) <= end_time
end
then,
start_time = Time.parse("2010/1/1")
end_time = Time.parse("2010/1/31")
time_iterate(start_time, end_time, 1.hour) do |t|
puts t
end
if in rails.

Resources