Ror, Can't Iterate from Datetime/Timewithzone

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



Leave a reply



Submit