duandou2763 2017-02-21 22:08
浏览 13
已采纳

根据开始日期确定日期是否是每隔一周的一部分

I have some code that loops through each day within a 10 year range starting on a specific date. The date should only be added if it meets the selected criteria from a form.

The form has fields for months and weekdays to be selected. Within each weekday there are options for every, first, second, etc. I'm adding an 'every other' option to each weekday along with a start date field.

I am trying to determine the best way to check if the current date is within the 'every other' criteria based on the start date.

After looking at other questions, I couldn't find an ideal answer that took into account year that have 53 weeks. I was able to come up with some test code that seems to be giving me accurate results, but I'm wondering if there is a simpler way to perform this check.

Test Code:

// 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');

}

Note 1: 2020 is the next year in which there are 53 weeks.

Note 2: I had a typo in this test code that was incrementing the week modifier instead of initializing it to 0. It would make more sense to me that this code would work if the modifier was initialized to be 0, but instead it only works when initialized to an odd number.

  • 写回答

1条回答 默认 最新

  • dongtan8532 2017-02-21 22:15
    关注

    Since the "every other" is evaluated in a continuous cycle, you might just keep track of the days:

    $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
    }
    

    Modular arithmetic

    This day is $w and we mark it:

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

    Now we advance by an unknown number of days $d. We need to properly flip all the days in this interval.

    One way to do that is to cycle through all the days. But it's clear that this can't be necessary - we have seven days in a week, and they are either odd or even; even if we flipped them all, it would be seven updates. Cycling through one year would be 365 or 366 updates.

    On the one hand, the 365-cycle solution has the advantage of simplicity. Is running 7 flips instead of 3652 really worth our while? If it is not, we're done. So let's suppose it is; but this evaluation should be re-done for every project.

    So note that if we advanced by 1 day, we need do nothing. If we advance by 2 days, day[w+1] must be flipped. If we advance by 5 days, days from w+1 to w+4 need to be flipped. In general, days from w+1 to w+d-1 need to be flipped:

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

    But now notice that if we advanced by 15 days, we would again need do nothing, as if we had advanced of only 1 day, since every day of the week would find itself being flipped twice:

    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)
    

    So here's a very reasonable compromise: if we need to advance by X days, treat it as if we had to advance by (X % 14) days. So now we will run at most 13 updates. Stopping now means that our code is the trivial version, enhanced by a strategically placed "% 14". We went from 3652 to 14 updates for a ten-year interval, and the best we could hope for was 7 updates. We got almost all of the bang, for very little buck.

    If we want to settle for nothing but the best, we go on (but note that the additional arithmetic might end up being more expensive than the saving in updates from the worst value of 13 to, at best, zero. In other words, doing additional checks means we will save at best 13 updates; if those checks cost more than 13 updates, we're better off not checking and going through blindly).

    So we start flipping at dIndex 1 if (1 < d%14 < 9), or (d%7) if (d%14 >=9 ). And we end at (d%14)-1 if (d%14)<8, 0 otherwise. If d%14 is 1, the start (using a simplified rule 1: d%14 < 9) is 1, the end is at 0, and since 0 is less than 1, the cycle would not even start. This means that the simplified rule should work:

    // 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)];
    }
    

    The above should flip correctly the "every other XXX" bit doing at most 7 writes, whatever the value of d. The approximate cost is about 6-7 updates, so if we're doing this in memory, it's not really that worth it, on average, if compared with the "% 14" shortcut. If we're flipping with separated SQL queries to a persistency layer, on the other hand...

    (And you really want to check out I didn't make mistakes...)

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥50 MATLAB实现圆柱体容器内球形颗粒堆积
  • ¥15 python如何将动态的多个子列表,拼接后进行集合的交集
  • ¥20 vitis-ai量化基于pytorch框架下的yolov5模型
  • ¥15 如何实现H5在QQ平台上的二次分享卡片效果?
  • ¥15 python爬取bilibili校园招聘网站
  • ¥30 求解达问题(有红包)
  • ¥15 请解包一个pak文件
  • ¥15 不同系统编译兼容问题
  • ¥100 三相直流充电模块对数字电源芯片在物理上它必须具备哪些功能和性能?
  • ¥30 数字电源对DSP芯片的具体要求