RoR, Can't iterate from DateTime/TimeWithZone
start = someModel.start_date.to_datetime
finish = someModel.end_date.to_datetime
while(start < finish) do
#bunch of awesome stuff
start += 1.day
end
Can't iterate over Time objects in Ruby
Exception
@fivedigit has explained why the exception was raised.
Other problems
You need any?
where you have each
:
appointment_times = []
#=> []
appointment = 4
#=> 4
conflicts = [(1..3), (5..7)]
#=> [1..3, 5..7]
appointment_times << 5 unless conflicts.each { |r| r.cover?(appointment) }
#=> nil
appointment_times
#=> []
appointment_times << 5 unless conflicts.any? { |r| r.include?(appointment) }
#=> [5]
appointment_times
#=> [5]
I suggest you covert appointment_time
to a Time
object, make conflicts
and array of elements [start_time, end_time]
and then compare appointment_time
to the endpoints:
...unless conflicts.any?{ |start_time, end_time|
start_time <= appointment_time && appointment_time <= end_time }
Aside: Range#include? only looks at endpoints (as Range#cover? does
) when the endpoints are "numeric". Range#include?
need only look at endpoints when they are Time
objects, but I don't know if Ruby regards Time
objects as "numeric". I guess one could look at the source code. Anybody know offhand?
Alternative approach
I would like to suggest a different way to implement your method. I will do so with an example.
Suppose appointments were in blocks of 15 minutes, with the first block being 10:00am-10:15am and the last 4:45pm-5:00pm. (blocks could be shorter, of course, as small as 1 second in duration.)
Let 10:00am-10:15am be block 0, 10:15am-10:30am be block 1, and so on, until block 27, 4:45pm-5:00pm.
Next, express conflicts
as an array of block ranges, given by [start, end]
. Suppose there were appointments at:
10:45am-11:30am (blocks 3, 4 and 5)
1:00pm- 1:30pm (blocks 12 and 13)
2:15pm- 3:30pm (blocks 17, 18 and 19)
Then:
conflicts = [[3,5], [12,13], [17,19]]
You must write a method reserved_blocks(appointment_date)
that returns conflicts
.
The remaining code is as follows:
BLOCKS = 28
MINUTES = ["00", "15", "30", "45"]
BLOCK_TO_TIME = (BLOCKS-1).times.map { |i|
"#{i<12 ? 10+i/4 : (i-8)/4}:#{MINUTES[i%4]}#{i<8 ? 'am' : 'pm'}" }
#=> ["10:00am", "10:15am", "10:30am", "10:45am",
# "11:00am", "11:15am", "11:30am", "11:45am",
# "12:00pm", "12:15pm", "12:30pm", "12:45pm",
# "1:00pm", "1:15pm", "1:30pm", "1:45pm",
# "2:00pm", "2:15pm", "2:30pm", "2:45pm",
# "3:00pm", "3:15pm", "3:30pm", "3:45pm",
# "4:00pm", "4:15pm", "4:30pm", "4:45pm"]
def available_times(appointment_date)
available = [*(0..BLOCKS-1)]-reserved_blocks(appointment_date)
.flat_map { |s,e| (s..e).to_a }
last = -2 # any value will do, can even remove statement
test = false
available.chunk { |b| (test=!test) if b > last+1; last = b; test }
.map { |_,a| [BLOCK_TO_TIME[a.first],
(a.last < BLOCKS-1) ? BLOCK_TO_TIME[a.last+1] : "5:00pm"] }
end
def reserved_blocks(date) # stub for demonstration.
[[3,5], [12,13], [17,19]]
end
Let's see what we get:
available_times("anything")
#=> [["10:00am", "10:45am"],
# ["11:30am", "1:00pm"],
# [ "1:45pm", "2:15pm"],
# [ "3:00pm", "5:00pm"]]
Explanation
Here is what's happening:
appointment_date = "anything" # dummy for demonstration
all_blocks = [*(0..BLOCKS-1)]
#=> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
# 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]
reserved_ranges = reserved_blocks(appointment_date)
#=> [[3, 5], [12, 13], [17, 19]]
reserved = reserved_ranges.flat_map { |s,e| (s..e).to_a }
#=> [3, 4, 5, 12, 13, 17, 18, 19]
available = ALL_BLOCKS - reserved
#=> [0, 1, 2, 6, 7, 8, 9, 10, 11, 14, 15, 16, 20, 21, 22, 23, 24, 25, 26, 27]
last = -2
test = false
enum1 = available.chunk { |b| (test=!test) if b > last+1; last = b; test }
#=> #<Enumerator: #<Enumerator::Generator:0x00000103063570>:each>
We can convert it to an array to see what values it would pass into the block if map
did not follow:
enum1.to_a
#=> [[true, [0, 1, 2]],
# [false, [6, 7, 8, 9, 10, 11]],
# [true, [14, 15, 16]],
# [false, [20, 21, 22, 23, 24, 25, 26, 27]]]
Enumerable#chunk groups consecutive values of the enumerator. It does so by grouping on the value of test
and flipping its value between true
and false
whenever a non-consecutive value is encountered.
enum2 = enum1.map
#=> #<Enumerator: #<Enumerator: (cont.)
#<Enumerator::Generator:0x00000103063570>:each>:map>
enum2.to_a
#=> [[true, [0, 1, 2]],
# [false, [6, 7, 8, 9, 10, 11]],
# [true, [14, 15, 16]],
# [false, [20, 21, 22, 23, 24, 25, 26, 27]]]
You might think of enum2
as a "compound" enumerator.
Lastly, we convert the second element of each value of enum2
that is passed into the block (the block variable a
, which equals [0,1,2]
for the first element passed) to a range expressed as a 12-hour time. The first element of each value of enum2
(true
or false
) is not used, so so I've replaced its block variable with an underscore. This provides the desired result:
enum2.each { |_,a|[BLOCK_TO_TIME[a.first], \
(a.last < BLOCKS-1) ? BLOCK_TO_TIME[a.last+1] : "5:00pm"] }
#=> [["10:00am", "10:45am"],
# ["11:30am", "1:00pm"],
# [ "1:45pm", "2:15pm"],
# [ "3:00pm", "5:00pm"]]
Convert ActiveSupport::TimeWithZone to DateTime
DateTime
is an old class which you generally want to avoid using. Time
and Date
are the two you want to be using. ActiveSupport::TimeWithZone
acts like Time
.
For stepping over dates you probably want to deal with Date
objects. You can convert a Time
(or ActiveSupport::TimeWithZone
) into a Date
with Time#to_date
:
from.to_date.step(to.to_date, 7) { |d| puts d.to_s }
Datetime Diff in ROR
For correct answer your both time should have same timezone utc
in this case
So it is converting 2012-09-19 16:33:09 +0530
into utc
which gives 2012-09-19 11:03:09 UTC
and hence difference is Diff is 6h 26m
How to loop through days, starting today and through a target date
Just wrote this one using de Date.upto() method and it worked... you just gotta make sure that 'goal.target_date' is also a valid instance of Date
require 'date'
from = Date.today
goto = from + 3
from.upto(goto) do |date|
puts date
end
Looping through object and blanking out fields
DATE_FIELDS.each do |field|
@copy_to.send("#{field}=".to_sym, nil) if @copy_to.send(field)
end
Get last 14 days in reverse order
Try this:
14.times do |i|
date = Date.today-i
#do stuff with date
end
Why can't I parse a date string saved to a variable in Ruby?
To make what you're trying to do work, you need to convert your date to a string with to_s
:
ActiveSupport::TimeZone["Central Time (US & Canada)"].parse(game.date.to_s).utc.to_date.strftime("%_m/%d")[1..-1]
However, you should consider whether this is really what you want to do. As it stands now, this code is taking a date, converting it to a string, parsing the string to get back to the date, then converting it to a string a second time. Are you sure you couldn't get by with something like this?
game.date.strftime(%_m/%d")[1..-1]
Related Topics
Lion: Problem with Rvm Installing Rubies - Problem Related to Openssl
Solid Tutorial for Building a Simple Wiki Application in Ruby on Rails
Can't Install Rmagick and Imagemagick on Windows 7
Result.Credit_Card_Verification Is Returning Nil Even on Error in Braintree
How to Programmatically Take Snapshot of Crawled Webpages (In Ruby)
Setting the Environment in Gemfile for Bundling Install/Update Based on a Customize File
How to Do Complex Querying with Logical Operations by Using Searchkick
How to Access a Variable Within a Heredoc in Ruby
Postgresql Ilike with Multiple Matches in Rails Activerecord
How to Read an Ini File in Ruby
Heroku Won't Reset My Database
How to Remotely Inspect the Data in My Rediscloud Dbs
Rails Console Fails with 'Switch to Inspect Mode' in Windows
Factory_Girl + Rspec Doesn't Seem to Roll Back Changes After Each Example
Have a Parent Class's Method Access the Subclass's Constants