无法在Ruby中迭代Time对象

Rub*_*Pry 1 ruby time google-calendar-api

我正在写一个约会表格,让用户选择一个日期.然后,它将按照日期检查Google日历,在上午10:00到下午5:00之间的30分钟时间间隔内查看该日期的可用时段.

在我的Calendar类中,我有一个available_times方法:

def available_times(appointment_date)
    appointment_date_events = calendar.events.select { |event| Date.parse(event.start_time) == appointment_date } 

    conflicts = appointment_date_events.map { |event| [Time.parse(event.start_time), Time.parse(event.end_time)] }
    results = resolve_time_conflicts(conflicts) 
end
Run Code Online (Sandbox Code Playgroud)

此方法需要一个日期和抓住start_timeend_time在该日期每个事件.然后它调用resolve_time_conflicts(conflicts):

def resolve_time_conflicts(conflicts)
    start_time = Time.parse('10:00am') 
    available_times = [] 
    14.times do |interval_multiple|
      appointment_time = (start_time + interval_multiple * (30 * 60))  
      available_times << appointment_time unless conflicts.each{ |conflict| (conflict[0]..conflict[1]).include?(appointment_time)}  
    end
      available_times 
end
Run Code Online (Sandbox Code Playgroud)

当我尝试迭代冲突数组时,会抛出'无法迭代时间'错误.我试图调用to_enum冲突数组但仍然得到相同的错误.

我在SO上看到的所有其他问题都是引用该step方法,这似乎不适用于这种情况.

更新:

Thanks @caryswoveland and @fivedigit. I combined both of your answers, which were very helpful for different aspects of my solution:

  def available_times(appointment_date)
    appointment_date_events = calendar.events.select { |event| Date.parse(event.start_time) == appointment_date } 

    conflicts = appointment_date_events.map { |event| DateTime.parse(event.start_time)..DateTime.parse(event.end_time) }
    results = resolve_time_conflicts(conflicts) 
  end

  def resolve_time_conflicts(conflicts)
    date = conflicts.first.first   
    start_time = DateTime.new(date.year, date.month, date.day, 10, 00).change(offset: date.zone) 
    available_times = [] 
    14.times do |interval_multiple|
      appointment_time = (start_time + ((interval_multiple * 30).minutes))
      available_times << appointment_time unless conflicts.any? { |conflict| conflict.cover?(appointment_time)}  
    end
      available_times 
  end
Run Code Online (Sandbox Code Playgroud)

fiv*_*git 6

问题来自这一点:

(conflict[0]..conflict[1]).include?(appointment_time)
# TypeError: can't iterate from Time
Run Code Online (Sandbox Code Playgroud)

您正在创建一系列时间,然后检查是否appointment_time属于该范围内.这就是导致您遇到错误的原因.

而不是include?,你应该使用cover?:

(conflict[0]..conflict[1]).cover?(appointment_time)
Run Code Online (Sandbox Code Playgroud)

这假设这conflict[0]是最早的时间.


Car*_*and 5

例外

@fivedigit解释了引发异常的原因.

其他问题

你需要的any?地方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]
Run Code Online (Sandbox Code Playgroud)

我建议你转换appointment_time为一个Time对象,make conflicts和数组元素[start_time, end_time],然后appointment_time与端点进行比较:

...unless conflicts.any?{ |start_time, end_time|
     start_time <= appointment_time && appointment_time <= end_time }  
Run Code Online (Sandbox Code Playgroud)

旁白:范围#include?仅在端点为Range#cover? does"数字"时查看端点(as ).Range#include?只需要在端点作为Time对象时查看端点,但我不知道Ruby是否将Time对象视为"数字".我想可以查看源代码.有人知道吗?

替代方法

我想建议一种不同的方法来实现您的方法.我将以一个例子来做.

假设约会时间为15分钟,第一个区块为上午10:00至上午10:15,最后一个区域为下午4:45至下午5:00.(块可能更短,当然,持续时间只有1秒.)

让10:00 am-10:15 am为0区块,10:15 am-10:30 am为1区块,依此类推,直至27区,下午4:45-5-5:00.

接下来,表示conflicts为块范围的数组,由下式给出[start, end].假设在以下地点有预约:

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)
Run Code Online (Sandbox Code Playgroud)

然后:

conflicts = [[3,5], [12,13], [17,19]]
Run Code Online (Sandbox Code Playgroud)

您必须编写一个reserved_blocks(appointment_date)返回的方法conflicts.

其余代码如下:

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
Run Code Online (Sandbox Code Playgroud)

让我们看看我们得到了什么:

available_times("anything") 
  #=> [["10:00am", "10:45am"],
  #    ["11:30am",  "1:00pm"],
  #    [ "1:45pm",  "2:15pm"], 
  #    [ "3:00pm",  "5:00pm"]]
Run Code Online (Sandbox Code Playgroud)

说明

这是发生了什么:

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>
Run Code Online (Sandbox Code Playgroud)

我们可以将它转换为一个数组,看看如果map没有遵循它将传递给块的值:

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]]]
Run Code Online (Sandbox Code Playgroud)

Enumerable#chunk对枚举器的连续值进行分组.它通过在遇到非连续值test之间true和之间对其值进行分组和翻转来实现这false一点.

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]]]
Run Code Online (Sandbox Code Playgroud)

您可能会认为enum2是"复合"枚举器.

最后,我们将enum2传递给块的每个值的第二个元素(块变量a,等于[0,1,2]传递的第一个元素)转换为表示为12小时时间的范围.不使用enum2(truefalse)每个值的第一个元素,所以我用下划线替换了它的块变量.这提供了期望的结果:

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"]]
Run Code Online (Sandbox Code Playgroud)