2017-02-21 78 views
0

我有一些代码在特定日期开始的10年范围内每一天循环。只有当它符合表单中的选定标准时才应添加该日期。根据开始日期确定日期是否为隔周的一部分

该表格包含可供选择月份和工作日的字段。在每个工作日内,每个工作日都有选项,第一,第二等。我在每个工作日添加一个“其他”选项以及一个开始日期字段。

我试图确定最好的方法来检查当前日期是否在基于开始日期的“其他”条件之内。

看了其他问题后,我找不到一个理想的答案,考虑了53周的一年。我能够想出一些似乎给我准确结果的测试代码,但我想知道是否有更简单的方法来执行此检查。

测试代码:

// set start date 
$date = new \DateTime('2019-12-15'); 

// get start date week of year and modulus 
$start_week = date('W', $date->getTimestamp()); 
$start_mod = $start_week % 2; 

// set end time (10 years from start date) 
$end_time = strtotime('+10 years', $date->getTimestamp()); 

// init previous year and week modifier 
$prev_year = false; 
$week_modifier = 1; 

// each day in range 
while($date->getTimestamp() <= $end_time){ 

    // get year 
    $y = $date->format('Y'); 

    // previous year doesn't match current year 
    if($prev_year != $y){ 

     // previous year set 
     if($prev_year){ 

      // get number of weeks in year 
      $weeks_in_year = date('W', mktime(0, 0, 0, 12, 28, $prev_year)); 

      // year has odd number of weeks 
      if($weeks_in_year % 2){ 

       // increment week modifier 
       $week_modifier++; 

      } 

     } 

     // update previous year 
     $prev_year = $y; 

    } 

    // get week of year 
    $w = $date->format('W') + $week_modifier; 

    // check if meets every other criteria (based on start date) 
    $every_other = false; 
    if(($w % 2) == $start_mod){ 
     $every_other = true; 
    } 

    // print date if it is part of every other Tuesday 
    if($date->format('w') == 2 && $every_other){ 
     echo $date->format('Y-m-d'); 
     echo '<br/>'; 
    } 

    // increment day 
    $date->modify('+1 day'); 

} 

注1:2020是明年,其中有53个星期。注意2:我在这个测试代码中有一个错字,它正在递增星期修饰符而不是初始化为0.如果修饰符被初始化为0,那么这个代码将起作用更有意义,而是只在初始化为奇数时才起作用。

+1

我会考虑看看这个项目:https://github.com/simshaun/recurr它非常适合处理复发,这显示了一些你可以使用的RRULE字符串的例子:http://www.kanzaki.com/docs/ical/ rrule.html – Theo

+0

...每隔一个星期一的字符串的缩减版本看起来像这样'FREQ = WEEKLY; INTERVAL = 2; BYDAY = MO'(你可以包括开始和结束日期以及更多) – Theo

+0

Thanks @Theo。我不知道Recurr。我一定会仔细看看这个解决方案和其他几个日期标准问题! –

回答

1

由于“隔”在连续的周期进行评估,你可能只是跟踪的日子:

$odd = [ true, true, true, true, true, true, true ]; 

... 
// Flip the appropriate day of the week. 
$odd[date('w')] = !$odd[date('w')]; 
// Or start with all 1's, then $odd[date('w')] ^= 1; 

if ($odd[date('w')]) { 
    // This is the "not-other" day 
} 

模运算

这一天是$w,我们将其标记:

$odd[$w] = !$odd[$w]; 

现在我们前进了未知天数$d。我们需要在这段时间内适当翻转所有日子。

这样做的一种方法是循环所有的日子。但很显然,这不是必要的 - 我们一周有七天,他们不是奇数就是偶数;即使我们把它们全部翻过来,也会有七次更新。骑自行车一年将是365或366更新。

一方面,365循环解决方案的优势在于简单性。运行7次翻转而不是3652真的值得我们这么做吗?如果不是,我们就完成了。那么让我们假设它是它是;但是这个评估应该针对每个项目重新进行。

所以请注意,如果我们提前1天,我们不需要做任何事情。如果我们提前2天,则必须翻转日[w + 1]。如果我们提前5天,则需要翻转从w + 1到w + 4的天数。在一般情况下,天W + 1到W + d-1需要翻转:

for ($i = 1; $i < $w+$d; $i++) { 
    $odd[$i % 7] = !$odd[$i % 7]; 
} 

但现在发现,如果我们提前了15天,我们会再次不需要做任何事情,因为如果我们先进的仅1天,当周以来的每一天会发现自己被翻转了两次:

d  need to flip w+... 
1  (none) 
2  1 
3  1, 2 
4  1, 2, 3 
5  1, 2, 3, 4 
6  1, 2, 3, 4, 5 
7  1, 2, 3, 4, 5, 6 
8  1, 2, 3, 4, 5, 6, 0 (or 7) 
9   2, 3, 4, 5, 6, 0 
10   3, 4, 5, 6, 0 
11    4, 5, 6, 0 
12     5, 6, 0 
13     6, 0 
14      0 
15  (none) 

因此,这里是一个非常合理的折衷:如果我们需要通过X天来推进,把它当作如果我们已经通过提前( X%14)天。所以现在我们最多会运行13次更新。现在停止意味着我们的代码是一个微不足道的版本,由战略性地放置的“%14”增强。我们从十三年的3652次更新为14次,我们希望的最好的7次更新。我们几乎所有的砰砰声,几乎没有什么降压。

如果我们只想解决最好的问题,我们继续(但请注意,额外的算法可能最终比从最坏的13更新到最多0的更新更加昂贵。换句话说,做额外的检查意味着我们最多可以节省13次更新;如果这些检查花费超过13次更新,我们最好不要检查并盲目地经历。因此,如果(d%14> = 9),我们在dIndex 1开始翻转if(1 < d%14 < 9)或(d%7)。如果(d%14)< 8,否则我们以(d%14)-1结束。如果d%14为1,则开始(使用简化规则1:d%14)为1,结束为0,并且因为0小于1,所以周期甚至不会开始。这意味着简化的规则应该工作:

// increase by d days 
d1 = (d%14) < 9 ? 1 : (d%7); 
d2 = (d%14) < 8 ? (d%14-1) : 7; 

for (dd = d1; dd <= d2; dd++) { 
    odd[(w+dd)%7)] = !odd[(w+dd)%7)]; 
} 

上面应该正确翻转“每隔XXX”位做最多7写道,d的任何值。大约花费大约6-7次更新,所以如果我们在记忆中这样做,平均而言,与“%14”快捷键相比,这并不是真的值得。如果我们用分隔的SQL查询持久化层,而另一方面翻转......

(你真的想看看我没有犯错误......)

+0

太棒了!非常简单可靠。谢谢。 –

+0

@Lsemi原来这不适用于我的情况,因为有些情况下我需要一次将日期递增一个月。但我可能会在未来使用你的建议。 –

+0

在这种情况下,我认为模块化算术可以做些什么。我明天会回复你。 – LSerni

相关问题