Date algorithm to obtain 4 mondays ahead, changing on friday
An answer to this question on Stack Overflow.
Question
We're trying to obtain a date in the future so that it follows these rules:
- the date must be Monday
- the date must change every two weeks
- the date must be 4 weeks ahead (so that it's always in the future and doesn't come too close with the current date)
- the date must change on Friday at 3 pm
So, for example:
| 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 |
This is what I've come up with: https://jsfiddle.net/2yunw713/
It's a little bit rough and I started changing too much, so currently it's not even correct on the hour scale.
The algorithm can be in any language, I picked JavaScript just because it's so easy to test.
Answer
I've put a Python solution which works as a library or a flexible command line script below.
Run as ./script.py demo, it generates a chart similar to what you have (I've had to manually interleave the switch hours):
| 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 |
Run with, e.g. ./script.py 2017-06-15 2017-06-18 3, it will generate a chart between the two dates with a 3 hour spacing between entries:
| 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 |
The script is as follows:
#!/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()
Gives this output chart (when I interleave data from hours=1):