### Tek-Tips is the largest IT community on the Internet today!

#### Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

Status
Not open for further replies.

#### Tarwn

##### Programmer
This is similar to the work-day addition problem we have seen in so many forums.

Write a function/procudure/whatever that:
Given two integer arguments:
weekday: 0-6 (sun-sat)
day of month: 1-31

return the first future date that is the given day of week and given day of the month.

Target is to do it in the fewest lines possible (with no silly line continuations or anything like that).

Also, I realize in most languages this is a few lines with a while loop, but what I'm really looking for is for people to try and do it with minimal or no looping.

SQL version is trivial with calendar table handy:
Code:
``````-- set datefirst 7
select min(calDate) as firstDate
from CalendarTable
where calDate >= getDate()
and datepart(dd, calDate) = @dayOfMonth
and datepart(dw, calDate) = @weekDay``````
Without it... I'd say problem is either procedural or requires ESquared-esque formula ... that's what you basically want?

------
[small]select stuff(stuff(replicate('<P> <B> ', 14), 109, 0, '<.'), 112, 0, '/')[/small]

vongrunt - Nice

I've come up with a horrible looking solution in VB6 that involves no looping, but a lot of IF..ELSE statements. I imagine that it can very easily be improved upon and shortened but I might as well post it anyway...
[white]
Code:
``````Public Function FindNextDate(DayofWeek As Integer, DateinMonth As Integer) As Date

Dim a As Integer
Dim SearchDate As Date

SearchDate = CDate(DateinMonth & "/" & Month(Date) & "/" & Year(Date))

If DateinMonth <= Day(Date) Then
a = 1
Else
a = 0
End If

If Weekday(DateAdd("m", a, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a, SearchDate)) = DateinMonth Then
Exit Function

ElseIf Weekday(DateAdd("m", a + 1, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 1, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 1, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 2, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 2, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 2, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 3, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 3, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 3, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 4, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 4, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 4, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 5, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 5, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 5, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 6, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 6, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 6, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 7, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 7, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 7, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 8, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 8, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 8, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 9, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 9, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 9, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 10, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 10, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 10, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 11, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 11, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 11, SearchDate)
Exit Function

ElseIf Weekday(DateAdd("m", a + 12, SearchDate)) = (DayofWeek + 1) And Day(DateAdd("m", a + 12, SearchDate)) = DateinMonth Then
FindNextDate = DateAdd("m", a + 12, SearchDate)
Exit Function
End If

End Function``````
[/white]I'm sure you'll agree that it's nasty (I'm waiting for strongm to post an infinitley shorter VB6 solution) but I'm pretty sure it works properly (though I imagine it'll be pointed out pretty quickly if it doesn't! )
I tested it using:
Code:
``````Private Sub CommandTest_Click()
Dim i1 As Integer
Dim a1 As Integer

For i1 = 0 To 6

For a1 = 1 To 31
Debug.Print Format(FindNextDate(i1, a1), "ddd") & " - " & FindNextDate(i1, a1)
Next a1

Next i1
End Sub``````

HarleyQuinn
---------------------------------
Get the most out of Tek-Tips, read FAQ222-2244 before posting.

OK, here's my attempt from ASP.NET:
Code:
``````[COLOR=white]
Private Sub GetFutureDate(ByRef CurrentDate As Date, ByVal WeekDay As Integer, ByVal DayOfMonth As Integer)
If CurrentDate.DayOfWeek = WeekDay AndAlso CurrentDate.Day = DayOfMonth Then
dteNextDate = CurrentDate
Else
End If
End Sub
[/color]``````

The only thing I'm not sure of, is if you would class using a recursive function as "looping" or not?

Also to test it, use:
Code:
``````[COLOR=white]
Private dteNextDate As Date = System.DateTime.Today
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
GetFutureDate(dteNextDate, 2, 4)
Response.Write("Next Date is " & dteNextDate)
End Sub
[/color]``````

____________________________________________________________

Try the Search Facility or read FAQ222-2244 on how to get better results.

>I'm waiting for strongm to post an infinitley shorter VB6 solution

Nah - I've seen vongrunt's really neat SQL solution, and given up trying to beat it ...

Hm... code is definitely short but... very SQL-specific. Same thing translated to procedural language ain't much better than brute-force - especially if calendar table/collection/whatever is not available at runtime.

And there is one likely-not-gonna-happen bug caused by >=. Should be > instead.

------
[small]select stuff(stuff(replicate('<P> <B> ', 14), 109, 0, '<.'), 112, 0, '/')[/small]

C using Zeller's congruence
Code:
``````#include "stdafx.h"
#include <stdio.h>
#include <time.h>
int z = 2;
void when (int dow, int d, int* mm, int* yy)
{
int m, y, u;
struct tm nowtm;
time_t now = time (0);
nowtm = *localtime (&now);
m = nowtm.tm_mon;
y = 1900 + nowtm.tm_year;
do {
m = m % 12 + 1;
if (m == 1) ++y;
u = ((d + int(((m > 2)? (m + 1) : (m + 13))* 30.6001) +	int(((m > 2) ? y : (y + 1)) * 1461 / 4)) - z) % 7;
} while (u != dow);
*mm = m;
*yy = y;
}``````
To Test
Code:
``````int main(int argc, char* argv[])
{
int m, y;
/* Next Friday 13th */
when (5, 13, &m, &y);
printf ("%d %d\n", m, y);
return 0;
}``````

and with a number table of course
Code:
``````-- do this only once
CREATE TABLE NumberPivot (NumberID INT PRIMARY KEY)
DECLARE @intLoopCounter INT
SELECT @intLoopCounter =0
WHILE @intLoopCounter <=1000
BEGIN
INSERT INTO NumberPivot
VALUES (@intLoopCounter)
SELECT @intLoopCounter = @intLoopCounter +1
END
GO``````

Here is the fun part
Code:
``````declare @dayOfMonth int,@weekDay int
select  @dayOfMonth =14,@weekDay =5

--Min
FROM dbo.NumberPivot

--Next 5
FROM dbo.NumberPivot

Denis The SQL Menace
SQL blog:
Personal Blog:

OK, I have to do it. My code looks like, well, HarleyQuinn's. It's not the same of course, but the method is. I wrote this before looking at his solution. It's shorter (counting number of lines), but not much.

Code:
``````[white]
Private Function GetDateForDayAndWeekday(ByVal TheDay As Integer, ByVal TheWeekday As Integer) As Date

Dim dteTest As Date

dteTest = DateSerial(Year(Date), Month(Date) + IIf(TheDay <= Day(Date), 1, 0), TheDay)
TheWeekday = TheWeekday + 1

Select Case True
Case Weekday(DateAdd("m", 0, dteTest)) = TheWeekday And Day(DateAdd("m", 0, dteTest)) = TheDay

Case Weekday(DateAdd("m", 1, dteTest)) = TheWeekday And Day(DateAdd("m", 1, dteTest)) = TheDay

Case Weekday(DateAdd("m", 2, dteTest)) = TheWeekday And Day(DateAdd("m", 2, dteTest)) = TheDay

Case Weekday(DateAdd("m", 3, dteTest)) = TheWeekday And Day(DateAdd("m", 3, dteTest)) = TheDay

Case Weekday(DateAdd("m", 4, dteTest)) = TheWeekday And Day(DateAdd("m", 4, dteTest)) = TheDay

Case Weekday(DateAdd("m", 5, dteTest)) = TheWeekday And Day(DateAdd("m", 5, dteTest)) = TheDay

Case Weekday(DateAdd("m", 6, dteTest)) = TheWeekday And Day(DateAdd("m", 6, dteTest)) = TheDay

Case Weekday(DateAdd("m", 7, dteTest)) = TheWeekday And Day(DateAdd("m", 7, dteTest)) = TheDay

Case Weekday(DateAdd("m", 8, dteTest)) = TheWeekday And Day(DateAdd("m", 8, dteTest)) = TheDay

Case Weekday(DateAdd("m", 9, dteTest)) = TheWeekday And Day(DateAdd("m", 9, dteTest)) = TheDay

Case Weekday(DateAdd("m", 10, dteTest)) = TheWeekday And Day(DateAdd("m", 10, dteTest)) = TheDay

Case Weekday(DateAdd("m", 11, dteTest)) = TheWeekday And Day(DateAdd("m", 11, dteTest)) = TheDay

Case Weekday(DateAdd("m", 12, dteTest)) = TheWeekday And Day(DateAdd("m", 12, dteTest)) = TheDay

End Select

End Function
[/white]``````

-George

Strong and bitter words indicate a weak cause. - Fortune cookie wisdom

Here's a bash example with the date command from GNU coreutils.

Code:
``````[white]
DAYS[1]=Sunday DAYS[1]=Monday DAYS[2]=Tuesday DAYS[3]=Wednesday
DAYS[4]=Thursday DAYS[5]=Friday DAYS[6]=Saturday
function day_day(){
CT=0
TG=\$2
while true; do
DATE=\$(date -d"\${DAYS[\$1]} \$CT week")
if [[ \$(date -d"\$DATE" +"%e") -eq \$TG ]]; then
echo \$DATE
break
fi
CT=\$((\$CT+1))
done
}

day_day 4 28
[/white]``````

Cheers,
ND

[small]bigoldbulldog AT hotmail[/small]

Same solution, different language
Code:
``````sub when (dow, d, ByRef m, ByRef y)
dim  u
do
m = (m mod 12) + 1
if m = 1 then y = y + 1
if m > 2 then
u = d + cint ((m + 1) * 30.6001) + cint (y * 1461 / 4)
else
u = d + cint ((m + 13) * 30.6001) + cint ((y + 1) * 1461 / 4)
end if
' Minor adjustment for Gregorian calendars
u = (u - 3) mod 7
if u = dow then exit sub
loop
end sub``````
To Test
Code:
``````    dim m, y
' Next Friday 13th
yoffset = 2000
m = month (date)
y = year (date) - yoffset
WScript.echo "finding next Friday 13 from " & cstr (m) & " " & cstr (yoffset + y)
when 5, 13, m, y
WScript.echo cstr (m) & " " & cstr(yoffset + y)``````

åOK, here is a FoxPro 2.6 solution to this puzzle.

This function returns the next date (AFTER the starting date) which falls on the specified day of week and falls on the specified day of month.

This has to be the most UGLY piece of code I have ever written, HOWEVER, it ...

1) Works for any starting date (I tested all dates from 9/15/1752 thru 9/15/2102),
2) Does not have any loops,
3) Does not call any external user defined function,
4) Does not call itself (Not easily done in FoxPro due to nesting level limits),
5) Does not use any tables,
6) Calculates every line once and only once on each pass through the function (No branching using IF ... ENDIF, DO CASE ... ENDCASE, etc.),
6) And is only 11 lines in length.

I used the maximum allowable line length in FoxPro to keep the line count down.

Code:
``````[white]
FUNCTION FINDNEXTDATE
PARAMETERS qDate, qDow, qDom

zd = qDate + qDow + 1 - DOW(qDate)

zdat = zd + IIF(DAY(zd + 7) = qDom, 7, IIF(DAY(zd + 14) = qDom, 14, IIF(DAY(zd + 21) = qDom, 21, IIF(DAY(zd + 28) = qDom, 28, IIF(DAY(zd + 35) = qDom, 35, IIF(DAY(zd + 42) = qDom, 42, IIF(DAY(zd + 49) = qDom, 49, IIF(DAY(zd + 56) = qDom, 56, IIF(DAY(zd + 63) = qDom, 63, IIF(DAY(zd + 70) = qDom, 70, IIF(DAY(zd + 77) = qDom, 77, IIF(DAY(zd + 84) = qDom, 84, IIF(DAY(zd + 91) = qDom, 91, IIF(DAY(zd + 98) = qDom, 98, IIF(DAY(zd + 105) = qDom, 105, 0)))))))))))))))

zdat = zdat + IIF(zdat <> zd, 0, IIF(DAY(zd + 112) = qDom, 112, IIF(DAY(zd + 119) = qDom, 119, IIF(DAY(zd + 126) = qDom, 126, IIF(DAY(zd + 133) = qDom, 133, IIF(DAY(zd + 140) = qDom, 140, IIF(DAY(zd + 147) = qDom, 147, IIF(DAY(zd + 154) = qDom, 154, IIF(DAY(zd + 161) = qDom, 161, IIF(DAY(zd + 168) = qDom, 168, IIF(DAY(zd + 175) = qDom, 175, IIF(DAY(zd + 182) = qDom, 182, IIF(DAY(zd + 189) = qDom, 189, IIF(DAY(zd + 196) = qDom, 196, IIF(DAY(zd + 203) = qDom, 203, 0)))))))))))))))

zdat = zdat + IIF(zdat <> zd, 0, IIF(DAY(zd + 210) = qDom, 210, IIF(DAY(zd + 217) = qDom, 217, IIF(DAY(zd + 224) = qDom, 224, IIF(DAY(zd + 231) = qDom, 231, IIF(DAY(zd + 238) = qDom, 238, IIF(DAY(zd + 245) = qDom, 245, IIF(DAY(zd + 252) = qDom, 252, IIF(DAY(zd + 259) = qDom, 259, IIF(DAY(zd + 266) = qDom, 266, IIF(DAY(zd + 273) = qDom, 273, IIF(DAY(zd + 280) = qDom, 280, IIF(DAY(zd + 287) = qDom, 287, IIF(DAY(zd + 294) = qDom, 294, IIF(DAY(zd + 301) = qDom, 301, 0)))))))))))))))

zdat = zdat + IIF(zdat <> zd, 0, IIF(DAY(zd + 308) = qDom, 308, IIF(DAY(zd + 315) = qDom, 315, IIF(DAY(zd + 322) = qDom, 322, IIF(DAY(zd + 329) = qDom, 329, IIF(DAY(zd + 336) = qDom, 336, IIF(DAY(zd + 343) = qDom, 343, IIF(DAY(zd + 350) = qDom, 350, IIF(DAY(zd + 357) = qDom, 357, IIF(DAY(zd + 364) = qDom, 364, IIF(DAY(zd + 371) = qDom, 371, IIF(DAY(zd + 378) = qDom, 378, IIF(DAY(zd + 385) = qDom, 385, IIF DAY(zd + 392) = qDom, 392, IIF(DAY(zd + 399) = qDom, 399, 0)))))))))))))))

zdat = zdat + IIF(zdat <> zd, 0, IIF(DAY(zd + 406) = qDom, 406, IIF(DAY(zd + 413) = qDom, 413, IIF(DAY(zd + 420) = qDom, 420, IIF(DAY(zd + 427) = qDom, 427, IIF(DAY(zd + 434) = qDom, 434, IIF(DAY(zd + 441) = qDom, 441, IIF(DAY(zd + 448) = qDom, 448, IIF(DAY(zd + 455) = qDom, 455, IIF(DAY(zd + 462) = qDom, 462, IIF(DAY(zd + 469) = qDom, 469, IIF(DAY(zd + 476) = qDom, 476, IIF(DAY(zd + 483) = qDom, 483, IIF (DAY(zd + 490) = qDom, 490, IIF(DAY(zd + 497) = qDom, 497, 0)))))))))))))))

zdat = zdat + IIF(zdat <> zd, 0, IIF(DAY(zd + 504) = qDom, 504, IIF(DAY(zd + 511) = qDom, 511, IIF(DAY(zd + 518) = qDom, 518, IIF(DAY(zd + 525) = qDom, 525, IIF(DAY(zd + 532) = qDom, 532, IIF(DAY(zd + 539) = qDom, 539, IIF(DAY(zd + 546) = qDom, 546, IIF(DAY(zd + 553) = qDom, 553, IIF(DAY(zd + 560) = qDom, 560, IIF(DAY(zd + 567) = qDom, 567, IIF(DAY(zd + 574) = qDom, 574, IIF(DAY(zd + 581) = qDom, 581, IIF(DAY(zd + 588) = qDom, 588, IIF(DAY(zd + 595) = qDom, 595, 0)))))))))))))))

zdat = zdat + IIF(zdat <> zd, 0, IIF(DAY(zd + 602) = qDom, 602, IIF(DAY(zd + 609) = qDom, 609, 0)))

RETURN zdat
[/white]``````

Test this function using this code:

Code:
``````PRIVATE ALL LIKE z*

SET CENTURY ON
SET DATE TO AMERICAN

zstart = {12/31/2006}

FOR zweekday = 0 TO 6
FOR zmonthday = 1 TO 31

zdate = FINDNEXTDATE(zstart,zweekday,zmonthday)

?
? zstart
? zdate
? ALLTRIM(STR(zdate - zstart)) + ' Days from starting date.'
? CDOW(zdate)

ENDFOR
ENDFOR

RETURN``````

I know there has to be a better way of doing this, so come on all of you FoxPro people, improve it.

Now a note for all of you. I don't know anything about C, ASP, etc., but I strongly suspect that some of the previous solutions will fail with the following inputs:

Start date = 12/31/2006
Ending weekday = 0 (Sunday)
Ending monthday = 31

Answer is 8/31/2008 (Sunday), 609 days after start date.

mmerlinn

"Political correctness is the BADGE of a COWARD!"

Very nice. I was working on one myself friday but someone erased my board

mmerlinn

"Political correctness is the BADGE of a COWARD!"

Whiteboard

I currently use this code to calc the next date for reports to run, so I will submit this.

Public Function CalcNextRun(strType As String) As String

Dim iDayofWeek As Integer

Select Case strType
Case "D" 'daily
If iDayofWeek = 7 Then
Else
End If
Case "W" 'weekly
Case "M" 'Monthly last workday of month
If Day(Now) >= 27 Then
'calc next last workday of month
iDayofWeek = Weekday(DateAdd("m", Month(Now) - 1, "01/31/" & Year(Now)))
If iDayofWeek = 1 Then
ElseIf iDayofWeek = 7 Then
Else
End If
Else
'calc next first work day of month
iDayofWeek = Weekday(DateAdd("m", Month(Now), "01/01/" & Year(Now)))
If iDayofWeek = 1 Then
ElseIf iDayofWeek = 7 Then
Else
End If
End If
Case "Y" 'Yearly
iDayofWeek = Weekday(DateAdd("yyyy", 1, "01/01/" & Year(Now)))
If iDayofWeek = 1 Then
ElseIf iDayofWeek = 7 Then
Else
End If
Case Else
CalcNextRun = Format(Now, "mm/dd/yyyy")
End Select

End Function

Here's another recursive one in VB6
Code:
``````[COLOR=white]
Public Function FutureDateR(DayofWeek As Integer, _
DayOfMonth As Integer, _
StartDate As Date) As Date

FutureDateR = DateSerial(Year(StartDate), Month(StartDate), DayOfMonth)

If Weekday(FutureDateR) <> DayofWeek Or Day(FutureDateR) <> DayOfMonth Then

FutureDateR = FutureDateR(DayofWeek, DayOfMonth, _
DateSerial(Year(FutureDateR), Month(FutureDateR) + _
IIf(Day(FutureDateR) = DayOfMonth, 1, 0), 1))

End If

End Function
[/color]``````

[small]No! No! You're not thinking ... you're only being logical.
- Neils Bohr[/small]

oops ... Found a bug.
Code:
``````[COLOR=white]
Public Function FutureDateR(DayofWeek As Integer, _
DayOfMonth As Integer, _
StartDate As Date) As Date

FutureDateR = DateSerial(Year(StartDate), Month(StartDate) + _
IIf(DayOfMonth < Day(StartDate), 1, 0), DayOfMonth)

If Weekday(FutureDateR) <> DayofWeek Or Day(FutureDateR) <> DayOfMonth Then

FutureDateR = FutureDateR(DayofWeek, DayOfMonth, _
DateSerial(Year(FutureDateR), Month(FutureDateR) + _
IIf(Day(FutureDateR) = DayOfMonth, 1, 0), 1))

End If

End Function
[/color]``````

[small]No! No! You're not thinking ... you're only being logical.
- Neils Bohr[/small]

Status
Not open for further replies.