日期算法获得4个星期一,周五改变

时间:2017-06-16 09:58:08

标签: algorithm date

我们试图在将来获取日期,以便遵循以下规则:

  • 日期必须是星期一
  • 日期必须每两周更改一次
  • 日期必须提前4周(因此它总是在未来并且不会与当前日期过于接近)
  • 日期必须在星期五下午3点更改

所以,例如:

| on                                             | date is    |
|------------------------------------------------|------------|
| 2017-06-15                                     | 2017-07-04 |
| 2017-06-16 14:00:00 (friday before 15:00)      | 2017-07-04 |
| 2017-06-16 15:00:00 (friday after 15:00)       | 2017-07-17 |
| 2017-06-16 16:00:00                            | 2017-07-17 |
| 2017-06-17                                     | 2017-07-17 |
| 2017-06-18                                     | 2017-07-17 |
| 2017-06-19                                     | 2017-07-17 |
| 2017-06-20                                     | 2017-07-17 |
| 2017-06-21                                     | 2017-07-17 |
| 2017-06-22                                     | 2017-07-17 |
| 2017-06-23 (friday again, dates do not change) | 2017-07-17 |
| 2017-06-24                                     | 2017-07-17 |
| 2017-06-25                                     | 2017-07-17 |
| 2017-06-26                                     | 2017-07-17 |
| 2017-06-27                                     | 2017-07-17 |
| 2017-06-28                                     | 2017-07-17 |
| 2017-06-29                                     | 2017-07-17 |
| 2017-06-30 14:00:00 (friday before 15:00)      | 2017-07-17 |
| 2017-06-30 15:00:00 (friday after 15:00)       | 2017-07-31 |
| 2017-06-30 16:00:00                            | 2017-07-31 |
| 2017-07-01                                     | 2017-07-31 |

这就是我提出的:https://jsfiddle.net/2yunw713/

它有点粗糙,我开始改变太多,所以目前它在小时尺度上甚至都不正确。

算法可以使用任何语言,我选择JavaScript只是因为它很容易测试。

3 个答案:

答案 0 :(得分:1)

任何语言......所以这里是bash solution =)

readonly start_date='2017-06-15'
# first monday after start_date
readonly start_mon="$(date -d "$start_date -$(date -d $start_date +%u) day + 8 day")"
monday_id=2 # offset in weeks from start_mon
monday_str=$(date -d "$start_mon $monday_id week" +'%Y-%m-%d')
fri_cnt=1 # 1 if date has to be changed on first friday, else 0
printf '|%-30s|%-10s|\n' 'on' 'date is'
for d in $(seq 0 56)  # 4 mondays ahead
do
    printf '|%-30s|%-10s|\n' "$(date -d "$start_date $d day" +'%Y-%m-%d')" "$monday_str"
    if [[ $(date -d "$start_date $d day" +'%a') == $(date -d 'fri' +'%a') ]]
    then  # it's friday
        fri_cnt=$((fri_cnt+1))
        if [[ $fri_cnt == 2 ]]
        then # it's second friday, change date
            fri_cnt=0
            monday_id=$((monday_id+2))
            monday_str=$(date -d "$start_mon $monday_id week" +'%Y-%m-%d')
            printf "|%-30s|%-10s|\n" "$(date -d "$start_date 15:00 $d day" +'%Y-%m-%d %H:%M:%S')" "$monday_str"
        fi
    fi
done

结果:

|on                            |date is   |
|2017-06-15                    |2017-07-03|
|2017-06-16                    |2017-07-03|
|2017-06-16 15:00:00           |2017-07-17|
|2017-06-17                    |2017-07-17|
|2017-06-18                    |2017-07-17|
|2017-06-19                    |2017-07-17|
|2017-06-20                    |2017-07-17|
|2017-06-21                    |2017-07-17|
|2017-06-22                    |2017-07-17|
|2017-06-23                    |2017-07-17|
|2017-06-24                    |2017-07-17|
|2017-06-25                    |2017-07-17|
|2017-06-26                    |2017-07-17|
|2017-06-27                    |2017-07-17|
|2017-06-28                    |2017-07-17|
|2017-06-29                    |2017-07-17|
|2017-06-30                    |2017-07-17|
|2017-06-30 15:00:00           |2017-07-31|
|2017-07-01                    |2017-07-31|
|2017-07-02                    |2017-07-31|

答案 1 :(得分:1)

我已经使用Java 8 new java.time API制作了这个算法,尽管它也可以使用ThreeTen Backport用于Java< = 7。

以下代码适用于两者。 唯一的区别是包名称(在Java 8中为java.time而在ThreeTen Backport中为org.threeten.bp),但类和方法名称是相同的。

首先,它不清楚你如何设置最初的星期五(当这一切都开始时)。所以,我考虑2017-06-16 15:00作为第一个星期五(我的算法开始的地方;发生变化的最后一天)。

该算法基本上是:

  • 获取最后一次更改日期(某些星期五的15:00)并计算下一次更改的时间(2周后)
  • 检查今天的日期是否在下一次更改之前(如果它等于或之后,重新计算更改日期)
  • 在最后一次更改前4周获得下一个星期一
import java.time.temporal.TemporalAdjusters;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

// starting at 2017-06-16 15:00:00 (the last date when a change occured)
LocalDateTime lastChange = LocalDateTime.of(2017, 6, 16, 15, 0);

// nextChange: 2 weeks after the last change
LocalDateTime nextChange = lastChange.plusWeeks(2);

// get the current date
LocalDateTime today = LocalDateTime.now();

// check if today is after or equals the nextChange
if (!today.isBefore(nextChange)) {
    // today is equal or after the next change, adjust the last and next change dates
    lastChange = nextChange;
    nextChange = lastChange.plusWeeks(2);
}

LocalDate futureDate = futureDate(lastChange);
System.out.println(futureDate);

// auxiliary method: get the future date based on the last change date
// using LocalDate because time (hour/minute/second) doesn't seem to matter in output
// (but if it does, use a LocalDateTime instead - don't call toLocalDate() in the last line)
public LocalDate futureDate(LocalDateTime lastChange) {
    // double checking (last change date is a Friday at 15:00) - not sure if it's really needed
    if (lastChange.getDayOfWeek() != DayOfWeek.FRIDAY || (!lastChange.toLocalTime().equals(LocalTime.of(15, 0)))) {
        return null; // if the change it's not Friday or it's not 15:00, it should return null? (not sure)
    }

    // get the next Monday and add 4 weeks
    return lastChange.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
                     .plusWeeks(4).toLocalDate();
}

输出(考虑到今天是2017-06-19)将是:

  

2017年7月17日

我还在14:00(返回2017-06-30)和15:00(返回2017-07-17)使用2017-07-31进行了测试。使用您的示例进行测试也给了我相同的结果。

备注:

  • 在您的输出示例中,您只需按年/月/日打印,因此我使用了LocalDate课程。如果您还需要时间字段(小时/分钟/秒),请更改方法以返回LocalDateTime,并删除toLocalDate()语句中对return的调用。
  • 因为在第一个星期五(当这一切都开始了吗?)时我不清楚,我假设2017-06-16
  • 在您的示例输出中,2017-06-15的结果不应该是2017-07-03?因为2017-07-04是星期二

如果你想要相同的输出,你可以像下面那样进行循环(使用相同的futureDate()方法,并假设2017-06-15的结果是2017-07-03,如上所述)。

请注意,我使用java.time.temporal.TemporalAdjuster来获取下一个日期 - 因为当它是星期五时,我必须检查它是否更改日期并获得结果14:00,15: 00和16:00,然后转到第二天(所以有时我需要将时间设置为14:00,或者添加1小时,或者添加1天 - 我会为每种情况选择相应的调节器。)

我还使用了java.time.format.DateTimeFormatter来更改输出格式(当星期五更改并且它有几小时的自定义格式时):

// starting 2 weeks before 2017-06-16 15:00:00 (the last date when a change occured)
LocalDateTime lastChange = LocalDateTime.of(2017, 6, 16, 15, 0).minusWeeks(2);

// nextChange: 2 weeks after the last change
LocalDateTime nextChange = lastChange.plusWeeks(2);

// starting at 2017-06-15
LocalDateTime today = LocalDateTime.of(2017, 6, 15, 0, 0);

LocalTime twoPM = LocalTime.of(14, 0);
LocalTime threePM = LocalTime.of(15, 0);
LocalTime fourPM = LocalTime.of(16, 0);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
TemporalAdjuster adjuster; // adjuster for the next date

// adjuster for next hour
TemporalAdjuster nextHour = t -> t.plus(1, ChronoUnit.HOURS);
// adjuster for next day
TemporalAdjuster nextDay = t -> t.plus(1, ChronoUnit.DAYS);

System.out.println("| on                                             | date is    |");
System.out.println("|------------------------------------------------|------------|");
for (int i = 0; i < 40; i++) {
    String sep = "";
    StringBuilder sb = new StringBuilder("| ");
    if (today.getDayOfWeek() == DayOfWeek.FRIDAY) {
        if (ChronoUnit.DAYS.between(lastChange.toLocalDate(), today.toLocalDate()) == 7) {
            sep = " (friday again, dates do not change)";
            adjuster = nextDay;
            sb.append(today.toLocalDate());
        } else {
            LocalTime time = today.toLocalTime();
            if (time.equals(twoPM)) {
                sep = " (friday before 15:00)    ";
                adjuster = nextHour;
                sb.append(formatter.format(today));
            } else if (time.equals(threePM)) {
                sep = " (friday after 15:00)      ";
                adjuster = nextHour;
                sb.append(formatter.format(today));
            } else if (time.equals(fourPM)) {
                sep = "                           ";
                adjuster = nextDay;
                sb.append(formatter.format(today));
            } else {
                sep = " (friday before 15:00)     ";
                adjuster = nextHour;
                today = today.with(twoPM);
                sb.append(formatter.format(today));
            }
        }
    } else {
        // get the next day at start of day
        sep = "                                    ";
        adjuster = t -> LocalDate.from(t).plusDays(1).atStartOfDay();
        sb.append(today.toLocalDate());
    }

    // check if today is after or equals the nextChange
    if (!today.isBefore(nextChange)) {
        // today is equal or after the next change, adjust the last and next change dates
        lastChange = nextChange;
        nextChange = lastChange.plusWeeks(2);
    }

    LocalDate futureDate = futureDate(lastChange);
    sb.append(sep).append(" | ").append(futureDate).append(" |");
    System.out.println(sb.toString());

    // get the next date
    today = today.with(adjuster);
}

输出结果为:

| on                                             | date is    |
|------------------------------------------------|------------|
| 2017-06-15                                     | 2017-07-03 |
| 2017-06-16 14:00:00 (friday before 15:00)      | 2017-07-03 |
| 2017-06-16 15:00:00 (friday after 15:00)       | 2017-07-17 |
| 2017-06-16 16:00:00                            | 2017-07-17 |
| 2017-06-17                                     | 2017-07-17 |
| 2017-06-18                                     | 2017-07-17 |
| 2017-06-19                                     | 2017-07-17 |
| 2017-06-20                                     | 2017-07-17 |
| 2017-06-21                                     | 2017-07-17 |
| 2017-06-22                                     | 2017-07-17 |
| 2017-06-23 (friday again, dates do not change) | 2017-07-17 |
| 2017-06-24                                     | 2017-07-17 |
| 2017-06-25                                     | 2017-07-17 |
| 2017-06-26                                     | 2017-07-17 |
| 2017-06-27                                     | 2017-07-17 |
| 2017-06-28                                     | 2017-07-17 |
| 2017-06-29                                     | 2017-07-17 |
| 2017-06-30 14:00:00 (friday before 15:00)      | 2017-07-17 |
| 2017-06-30 15:00:00 (friday after 15:00)       | 2017-07-31 |
| 2017-06-30 16:00:00                            | 2017-07-31 |
| 2017-07-01                                     | 2017-07-31 |
| 2017-07-02                                     | 2017-07-31 |
| 2017-07-03                                     | 2017-07-31 |
| 2017-07-04                                     | 2017-07-31 |
| 2017-07-05                                     | 2017-07-31 |
| 2017-07-06                                     | 2017-07-31 |
| 2017-07-07 (friday again, dates do not change) | 2017-07-31 |
| 2017-07-08                                     | 2017-07-31 |
| 2017-07-09                                     | 2017-07-31 |
| 2017-07-10                                     | 2017-07-31 |
| 2017-07-11                                     | 2017-07-31 |
| 2017-07-12                                     | 2017-07-31 |
| 2017-07-13                                     | 2017-07-31 |
| 2017-07-14 14:00:00 (friday before 15:00)      | 2017-07-31 |
| 2017-07-14 15:00:00 (friday after 15:00)       | 2017-08-14 |
| 2017-07-14 16:00:00                            | 2017-08-14 |
| 2017-07-15                                     | 2017-08-14 |
| 2017-07-16                                     | 2017-08-14 |
| 2017-07-17                                     | 2017-08-14 |
| 2017-07-18                                     | 2017-08-14 |

另一种方法是创建一个地图来存储相对于发生更改的日期的所有未来日期(因此您不需要一直计算它):

// maps a change date with the respective future date
Map<LocalDateTime, LocalDate> futureDates = new HashMap<>();

// starting at 2017-06-16
LocalDateTime change = LocalDateTime.of(2017, 6, 16, 15, 0);
// storing just 30 dates, but you can change this accordingly, with as many dates as you need
for (int i = 0; i < 30; i++) {
    futureDates.put(change, futureDate(change));
    change = change.plusWeeks(2);
}

支票会有所改变,因为您需要准确了解与您检查日期对应的密钥:

LocalDateTime today = LocalDateTime.now();
// find the future date
for (LocalDateTime dt : futureDates.keySet()) {
    long days = ChronoUnit.DAYS.between(dt, today);
    if (days >= 0 && days <= 13) {
        System.out.println(today + "  " + futureDates.get(dt));
        break;
    }
}

这与上面的第一个版本的工作方式相同。

答案 2 :(得分:0)

我已经提供了一个Python解决方案,它可以作为库或下面的灵活命令行脚本。

./script.py demo运行,它会生成类似于您所拥有的图表(我必须手动交错切换时间):

| on                                             | date is    |
|------------------------------------------------|------------|
| 2017-06-15 00:00                               | 2017-07-03 |
| 2017-06-16 00:00                               | 2017-07-03 |
| 2017-06-17 00:00                               | 2017-07-03 |
| 2017-06-18 00:00                               | 2017-07-17 |
| 2017-06-19 00:00                               | 2017-07-17 |
| 2017-06-20 00:00                               | 2017-07-17 |
| 2017-06-21 00:00                               | 2017-07-17 |
| 2017-06-22 00:00                               | 2017-07-17 |
| 2017-06-23 00:00                               | 2017-07-17 |
| 2017-06-24 00:00                               | 2017-07-17 |
| 2017-06-25 00:00                               | 2017-07-17 |
| 2017-06-26 00:00                               | 2017-07-17 |
| 2017-06-27 00:00                               | 2017-07-17 |
| 2017-06-28 00:00                               | 2017-07-17 |
| 2017-06-29 00:00                               | 2017-07-17 |
| 2017-06-30 00:00                               | 2017-07-17 |
| 2017-07-01 00:00                               | 2017-07-17 |
| 2017-07-02 00:00                               | 2017-07-31 |

运行,例如./script.py 2017-06-15 2017-06-18 3,它会在两个日期之间生成一个图表,条目间隔3小时:

| on                                             | date is    |
|------------------------------------------------|------------|
| 2017-06-15 00:00                               | 2017-07-03 |
| 2017-06-15 03:00                               | 2017-07-03 |
| 2017-06-15 06:00                               | 2017-07-03 |
| 2017-06-15 09:00                               | 2017-07-03 |
| 2017-06-15 12:00                               | 2017-07-03 |
| 2017-06-15 15:00                               | 2017-07-03 |
| 2017-06-15 18:00                               | 2017-07-03 |
| 2017-06-15 21:00                               | 2017-07-03 |
| 2017-06-16 00:00                               | 2017-07-03 |
| 2017-06-16 03:00                               | 2017-07-03 |
| 2017-06-16 06:00                               | 2017-07-03 |
| 2017-06-16 09:00                               | 2017-07-03 |
| 2017-06-16 12:00                               | 2017-07-03 |
| 2017-06-16 15:00                               | 2017-07-03 |
| 2017-06-16 18:00                               | 2017-07-17 |
| 2017-06-16 21:00                               | 2017-07-17 |
| 2017-06-17 00:00                               | 2017-07-17 |
| 2017-06-17 03:00                               | 2017-07-17 |
| 2017-06-17 06:00                               | 2017-07-17 |
| 2017-06-17 09:00                               | 2017-07-17 |
| 2017-06-17 12:00                               | 2017-07-17 |
| 2017-06-17 15:00                               | 2017-07-17 |
| 2017-06-17 18:00                               | 2017-07-17 |
| 2017-06-17 21:00                               | 2017-07-17 |
| 2017-06-18 00:00                               | 2017-07-17 |
| 2017-06-18 03:00                               | 2017-07-17 |

脚本如下:

#!/usr/bin/env python3

import datetime
import sys

MONDAY = 0 # 0 = Monday, 1=Tuesday, 2=Wednesday...
FRIDAY = 4

#Date of first change Friday. All future changes are calculated from here
FIRST_PREV_FRIDAY = datetime.datetime(2017,6,2,15,0,0)  

#Get the next weekday following the query_date
def GetNextWeekday(query_date, wdaynum):
  days_ahead = wdaynum - query_date.weekday()                      #0 = Monday, 1=Tuesday, 2=Wednesday, ...
  if days_ahead<=0:                                                #Day already happened this week
    days_ahead += 7                                                #Add 7 days to get the next day
  new_date = query_date + datetime.timedelta(days_ahead)           #Do date math to get the day in datetime
  return new_date

#Get a weekday several weeks in advance
def GetWeekdayInFuture(query_date, wdaynum, weeks_out):
  wd = query_date                                                  #Starting with the current day
  for i in range(weeks_out):                                       #Loop for a given number of weeks
    wd = GetNextWeekday(wd, wdaynum)                               #Calculating the next occurence of the day of interest
  return wd                                                        #Return the day

#Get a list of (current_date,next_monday) pairs between start_date and end_date
def GetMondays(start_date, end_date, date_increment):
  assert date_increment <= datetime.timedelta(days=1)              #If it were larger, we might skip changes!
  assert start_date     >= FIRST_PREV_FRIDAY                       #Can't calculate into the past

  #The idea is that we'll begin calculating from a point where we know things
  #are correct and only provide output in the user's specified region of
  #interest
  prev_friday   = FIRST_PREV_FRIDAY                                #First Friday at which a change took place
  change_friday = GetWeekdayInFuture(prev_friday, FRIDAY, 2)       #Next Friday at which a change will take place
  this_date     = prev_friday                                      #Set the current date to one for which we know the answer
  #Match hours minutes to start_date
  this_date     = this_date.replace(hour=start_date.hour, minute=start_date.minute)

  mondays = []                                                     #Holds the output list
  while this_date<=end_date:                                       #If current date is before the end date
    if this_date>=change_friday:                                   #Check if we've moved past a change point
      prev_friday   = change_friday                                #If so, this day becomes the point from which the future Monday is calculated
      change_friday = GetWeekdayInFuture(change_friday, FRIDAY, 2) #Calculate the next change point, two weeks in the future
    next_monday = GetWeekdayInFuture(prev_friday, MONDAY, 5)       #The Monday of interest is 5 weeks from the previous change point
    this_date += date_increment                                    #Advance to the next day
    if this_date>=start_date:                                      #Is this a date we were interested in?
      mondays.append((this_date,next_monday))                      #Gather output

  return mondays

#Get a particular Monday
def GetMondayForDate(query_date):
  start_date = FIRST_PREV_FRIDAY
  end_date   = query_date
  mondays    = GetMondays(start_date, end_date, datetime.timedelta(days=1))
  return mondays[-1][1]

#Print a nicely formatted charge of the Mondays
def PrintChart(mondays):
  print("| on                                             | date is    |")
  print("|------------------------------------------------|------------|")
  for x in mondays:
    print("| {0:47}| {1:11}|".format(x[0].strftime("%Y-%m-%d %H:%M"),x[1].strftime("%Y-%m-%d")))

#Code to run if script were executed from the command line; otherwise, it works
#as a library
def main():
  if len(sys.argv)==1:
    print("Syntax:")
    print("\t{0:20}                                 - This help screen".format(sys.argv[0]))
    print("\t{0:20} demo                            - Demonstration chart".format(sys.argv[0]))
    print("\t{0:20} <START DATE> <END DATE> <Hours> - Chart between two dates with increment of hours".format(sys.argv[0]))
  elif sys.argv[1]=='demo':
    start_date = datetime.datetime(2017,6,15,0,0,0)        #Date from which to begin calculating
    end_date   = datetime.datetime(2017,7,1,0,0,0)         #Last date for which to calculate
    mondays    = GetMondays(start_date, end_date, datetime.timedelta(days=1))
    PrintChart(mondays)
  else:
    start_date = datetime.datetime.strptime(sys.argv[1], "%Y-%m-%d")
    end_date   = datetime.datetime.strptime(sys.argv[2], "%Y-%m-%d")
    hours      = int(sys.argv[3])
    mondays    = GetMondays(start_date, end_date, datetime.timedelta(hours=hours))
    PrintChart(mondays)

if __name__ == '__main__': #Was the script executed from the command line?
  main()

给出此输出图表(当我从hours=1交错数据时):