Skip to content

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):