假设我有一些截止时间对象deadline = project_start + 34.days
现在让我们假设某种假期/推迟的业务逻辑必须让我延长这些假期的最后期限。
让我们假设我的假期是阵列或范围(或几个开始/结束日期,无论如何)并且该项目于12月15日开始
project_start = Time.parse('2017-12-15 0:00') # December 15th
deadline = project_start + 34.days # January 18th
vacations = [
Time.parse('2017-12-10 0:00')..Time.parse('2018-01-20 0:00'), # Starts 5 days before the project start, so only 5 out of 10 days extend the deadline
Time.parse('2018-01-05 0:00')..Time.parse('2018-01-10 0:00'), # Starts during the project so whole vacation period must extend the deadline
Time.parse('2018-01-20 0:00')..Time.parse('2018-01-25 0:00'), # STarts after the original deadline, but dued to previous vacations, this vacation fully extends the deadline
]
考虑到假期,我如何(轻松)计算延长的截止日期?
我尝试寻找现有的解决方案,但我发现大多数宝石涉及静态假期,但我对每个用户的内容感兴趣。
基本上我在考虑订购假期,并延长每个步骤的截止日期,但我需要考虑到在实际项目开始之前可能已经开始休假。要弄清楚正确的事情变得有点困难。我将发布第一次尝试
答案 0 :(得分:0)
所以这是我设计的第一种方法,我正在编写规格,也许它已经很好但我有疑问
# Extends a deadline with extra time provided as "vacations"
#
# @param range: [Range<Time>]
# @param vacations: [Array<Range<Time>>]
#
# @return [Range]
def extend_time_range_with_vacations(range:, vacations:)
extended_deadline = range.last.dup
vacations.each do |vacation|
cover = (range.first..extended_deadline) & vacation
next unless cover.present?
# Here I need to pick more than just the intersection
# For eg maybe the vacation is 2.weeks long
# but the range started only in the 2nd week of the vacation,
# hence the time extension is 1.week only)
actual_cover = cover.first..vacation.last
extended_deadline += (actual_cover.last - actual_cover.first)
end
extended_deadline
end
但感觉还有很多工作要做,也许更简单的事情存在?
编辑:代码已更改为使用this range intersection monkeypatch
编辑:规范
describe '#extend_time_range_with_vacations' do
let(:start_d) { Time.parse('2018-04-20 8:42') }
let(:end_d) { start_d + 42.days }
let(:range) { start_d..end_d }
let(:vacations) do
[
(start_d - 4.days)..(start_d + 4.days), # 4 days extension out of 8
(start_d + 10.days)..(start_d + 14.days), # 4 days extension
(end_d + 4.days)..(end_d + 8.days), # 4 days extensions unlocked because of previous vacations
]
end
it 'returns the expected extended deadline' do
new_range = Utility.extend_time_range_with_vacations(range: range, vacations: vacations)
expect(new_range).to be_within(1).of(end_d + (4 * 3).days)
end
end
答案 1 :(得分:0)
我已经解决了以下问题。我们将获得完成项目所需的开始日期和工作天数。我们还有一系列假期。如果项目已经开始并且在给定日期尚未完成的情况下,如果该日期属于任何休假期,则该日期不会执行任何工作。每个休假期是连续几天的范围,可以在开始日期之前开始或结束,并且可以与其他休假期重叠。 1 。
目标是确定项目完成的日期。
问题的表现方式不同,但如果我的解释是准确的而不是单位(例如,我假设日期输入和日期输出是字符串而不是Time
对象),它会不难修改我的答案以符合要求。
<强>代码强>
require 'date'
require 'set'
DATE_FMT = "%Y-%m-%d"
def end_date(start_date, duration, vacations)
start_date = Date.strptime(start_date, DATE_FMT)
vacation_dates = construct_vacations_dates_set(vacations)
d = start_date
while duration > 0
duration -= 1 unless vacation_dates.include?(d)
d = d.next
end
d.strftime(DATE_FMT)
end
def construct_vacations_dates_set(vacations)
vacations.map do |a|
s, e = a.map { |s| Date.strptime(s, DATE_FMT) }
(s..e).to_a
end.
reduce(:|).
to_set
end
示例强>
start_date = '2017-12-15'
vacations = [['2017-11-20', '2017-12-03'],
['2017-12-13', '2017-12-19'],
['2017-12-23', '2018-01-02'],
['2017-12-29', '2018-01-08'],
['2018-01-14', '2018-02-28'],
['2018-05-18', '2018-06-01']]
(1..20).each { |n| puts "for duration %2d days: %s" %
[n, end_date(start_date, n, vacations)] }
打印
for duration 1 days: 2017-12-21
for duration 2 days: 2017-12-22
for duration 3 days: 2017-12-23
for duration 4 days: 2018-01-10
for duration 5 days: 2018-01-11
for duration 6 days: 2018-01-12
for duration 7 days: 2018-01-13
for duration 8 days: 2018-01-14
for duration 9 days: 2018-03-02
for duration 10 days: 2018-03-03
for duration 11 days: 2018-03-04
for duration 12 days: 2018-03-05
for duration 13 days: 2018-03-06
for duration 14 days: 2018-03-07
for duration 15 days: 2018-03-08
for duration 16 days: 2018-03-09
for duration 17 days: 2018-03-10
for duration 18 days: 2018-03-11
for duration 19 days: 2018-03-12
for duration 20 days: 2018-03-13
<强>解释强>
见Date#strftime,
Date::strptime,Date#next(又名next_day
)和Enumerable#reduce(又名inject
)。 reduce
采用&#34; set union&#34;通过应用方法Array#|来度假数组(连续日期)。
我将休假日期数组转换为快速查找集。
对于给出的示例,如果将vacation_dates
的元素转换为非重叠范围的数组,然后将范围端点转换为日期字符串,则该数组将如下所示。
["2017-11-20".."2017-12-03",
"2017-12-13".."2017-12-19",
"2017-12-23".."2018-01-08",
"2018-01-14".."2018-02-28",
"2018-05-18".."2018-06-01"]
第一个范围完全在start_date
之前,但没有必要删除它。更一般地说,没有必要在start_date
之前删除休假日期。
通过添加一些puts
语句可以看到计算假期日期集的步骤。为了提高可读性,我删除了返回值和打印行范围内的Date
个对象,用,...,
替换它们的组。我还缩写了Date
个对象。例如,我将#<Date: 2017-12-13 ((2458101j,0s,0n),+0s,2299161j)>
缩短为#<Date: 2017-12-13...>
。
def construct_vacations_dates_set(vacations)
vacations.map do |a|
puts "a = #{a}"
s, e = a.map { |s| Date.strptime(s, DATE_FMT) }
puts " s = #{s}, e = #{e}"
(s..e).to_a.
tap { |o| puts " (s..e).to_a = #{o}" }
end.
reduce(:|).
tap { |o| puts "after reduce(:|) = #{o}" }.
to_set
end
construct_vacations_dates_set([['2017-12-13', '2017-12-19'],
['2017-12-23', '2018-01-02'], ['2017-12-29', '2018-01-08']])
#=> #<Set: {#<Date: 2017-12-13...>,..., #<Date: 2017-12-19...>,
# #<Date: 2017-12-23...>,..., #<Date: 2018-01-08...>}>
打印(范围未显示的元素)
a = ["2017-12-13", "2017-12-19"]
s = 2017-12-13, e = 2017-12-19
(s..e).to_a = [#<Date: 2017-12-13...>,.., #<Date: 2017-12-19..>]
a = ["2017-12-23", "2018-01-02"]
s = 2017-12-23, e = 2018-01-02
(s..e).to_a = [#<Date: 2017-12-23...>,..., #<Date: 2018-01-02...>]
a = ["2017-12-29", "2018-01-08"]
s = 2017-12-29, e = 2018-01-08
(s..e).to_a = [#<Date: 2017-12-29...>,..., #<Date: 2018-01-08...>]
after reduce(:|) = [#<Date: 2017-12-13...>,..., #<Date: 2017-12-19...>,
#<Date: 2017-12-23...>,..., #<Date: 2018-01-08...>]
1如果保证度假范围不重叠,我的解决方案将是相同的。