Jump to content

Finding Nth weekday of any month


ces1a
 Share

Recommended Posts

;This script will calculate the Nth weekday of any month. Just replace the numbers for $Year, $Month, $Week, and $Weekday with numbers of your choice,

;Some lines are not really needed but are there to allow testing and proofing. It is based on Excel formula.

#include <Date.au3>
Global $tmp, $Year = "2019", $Month = "2", $Week = 2, $WeekDay = 4
Global $aNth = StringSplit("First  ,Second ,Third  ,Fourth ,Fifth  ", ",")

Func GetWeekDay($Week, $Year, $Month, $WeekDay)
    Local $LastDay = 8 - $WeekDay, $EndDay = $Week * 7 + 1
    $Month = $Month > 10 ? $Month : "0" & $Month
    Local $iWday = _DateToDayOfWeek($YEAR, $Month, $LastDay)
    Return $YEAR & "/" & $Month & "/" & $EndDay - $iWday
EndFunc

MsgBox(0,'',$aNth[$Week] & _DateDayOfWeek($WeekDay) & " of " & _DateToMonth($Month) & _
    " " & $Year & " is  " & GetWeekDay($Week, $Year, $Month, $WeekDay) )

 

GetNthWeekDay.au3

Link to comment
Share on other sites

Was also discussed here 
Here is Yet another demo for anyone to try:

#include <Date.au3>

Global $aNth = StringSplit("First,Second,Third,Fourth,Fifth", ",")

_DateDayByIstance() ;today

$Y = 2019
For $m = 1 To 12
    For $d = 1 To 7
        ConsoleWrite(@LF)
        For $inst = 1 To 5
            _DateDayByIstance($Y, $m, $d, $inst)
        Next
    Next
Next

Func _DateDayByIstance($iYear = @YEAR, $iMonth = @MON, $iWeekDay = @WDAY, $iInstance = Round(@MDAY / 7))
    Local $iInst = ($iInstance * 7) - 7 + 1
    Local $iDOW = $iWeekDay - _DateToDayOfWeek($iYear, $iMonth, 1)
    $Date = $iYear & "/" & $iMonth & "/" & $iDOW + ($iDOW < 0 ? 7 + $iInst : $iInst)
    If _DateIsValid($Date) Then ConsoleWrite("@ " & $aNth[$iInstance] & " instance of " & _DateTimeFormat($Date, 1) & @LF)
EndFunc

Deye

Link to comment
Share on other sites

@ces1a
Thanks for sharing :)
By the way, in the UDF Date.au3, there is an undocumented function, _WeekNumber(), which is very similiar to yours:

; #NO_DOC_FUNCTION# =============================================================================================================
; Name...........: _WeekNumber
; Description ...: Find out the week number of current date OR date given in parameters
; Syntax.........: _WeekNumber([$iYear = @YEAR[, $iMonth = @MON[, $iDay = @MDAY[, $iWeekStart = 1]]]])
; Parameters ....: $iYear      - Year value (default = current year)
;                  $iMonth    - Month value (default = current month)
;                  $iDay       - Day value (default = current day)
;                  $iWeekStart - Week starts from Sunday (1, default) or Monday (2)
; Return values .: Success - Returns week number of given date
;                  Failure - -1  and sets @ERROR to:
;                  | 1 - On faulty parameters
;                  |99 - On non-acceptable weekstart and uses default (Sunday) as starting day
; Author ........: JdeB
; Modified.......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......:
; ===============================================================================================================================
Func _WeekNumber($iYear = @YEAR, $iMonth = @MON, $iDay = @MDAY, $iWeekStart = 1)
    ; Check for erroneous input in $Day, $Month & $Year
    If $iDay > 31 Or $iDay < 1 Then
        Return SetError(1, 0, -1)
    ElseIf $iMonth > 12 Or $iMonth < 1 Then
        Return SetError(1, 0, -1)
    ElseIf $iYear < 1 Or $iYear > 2999 Then
        Return SetError(1, 0, -1)
    ElseIf $iWeekStart < 1 Or $iWeekStart > 2 Then
        Return SetError(2, 0, -1)
    EndIf
    ;
    Local $iStartWeek1, $iEndWeek1
    ;$idow = _DateToDayOfWeekISO($iYear, $iMonth, $iDay);
    Local $iDow0101 = _DateToDayOfWeekISO($iYear, 1, 1);
    Local $iDate = $iYear & '/' & $iMonth & '/' & $iDay
    ;Calculate the Start and End date of Week 1 this year
    If $iWeekStart = 1 Then
        If $iDow0101 = 6 Then
            $iStartWeek1 = 0
        Else
            $iStartWeek1 = -1 * $iDow0101 - 1
        EndIf
        $iEndWeek1 = $iStartWeek1 + 6
    Else
        $iStartWeek1 = $iDow0101 * - 1
        $iEndWeek1 = $iStartWeek1 + 6
    EndIf

    Local $iStartWeek1ny
    ;$iStartWeek1Date = _DateAdd('d',$iStartWeek1,$iYear & '/01/01')
    Local $iEndWeek1Date = _DateAdd('d', $iEndWeek1, $iYear & '/01/01')
    ;Calculate the Start and End date of Week 1 this Next year
    Local $iDow0101ny = _DateToDayOfWeekISO($iYear + 1, 1, 1);
    ;  1 = start on Sunday / 2 = start on Monday
    If $iWeekStart = 1 Then
        If $iDow0101ny = 6 Then
            $iStartWeek1ny = 0
        Else
            $iStartWeek1ny = -1 * $iDow0101ny - 1
        EndIf
        ;$IEndWeek1ny = $iStartWeek1ny + 6
    Else
        $iStartWeek1ny = $iDow0101ny * - 1
        ;$IEndWeek1ny = $iStartWeek1ny + 6
    EndIf
    Local $iStartWeek1Dateny = _DateAdd('d', $iStartWeek1ny, $iYear + 1 & '/01/01')
    ;$iEndWeek1Dateny = _DateAdd('d',$IEndWeek1ny,$iYear+1 & '/01/01')
    ;number of days after end week 1
    Local $iCurrDateDiff = _DateDiff('d', $iEndWeek1Date, $iDate) - 1
    ;number of days before next week 1 start
    Local $iCurrDateDiffny = _DateDiff('d', $iStartWeek1Dateny, $iDate)
    ;
    ; Check for end of year
    If $iCurrDateDiff >= 0 And $iCurrDateDiffny < 0 Then Return 2 + Int($iCurrDateDiff / 7)
    ; > week 1
    If $iCurrDateDiff < 0 Or $iCurrDateDiffny >= 0 Then Return 1
EndFunc   ;==>_WeekNumber

 

Click here to see my signature:

Spoiler

ALWAYS GOOD TO READ:

 

Link to comment
Share on other sites

@ces1a, Your function returns invalid date for certain requests, try to ask for the fifth Friday of February 2019 for example ...
@Deye, your function will instead return nothing in that case or also if you cross to the next month(s).
@FrancescoDiMuro, that function will return the number of the week of the year a date falls into. Is different from what OP intends to achieve.
If allowed I would propose my 2 cents, instead of returning an invalid date or nothing at all, my proposed function will return the date where you're going to end up by counting the number of weekdays you specified. So if you ask the 9° sunday of january, the function will count 9 sundays starting from the first one of january and going on up counting 9 of the requested weekday. So is up to you to ask sensate questions, otherwise the function will return however the required date (a valid date anyway) and will just set the error flag if you go beyond the current month
Hope it can be of use

#include <date.au3>
Global Enum $Sunday = 1, $Monday, $Tuesday, $Wednesday, $Thursday, $Friday, $Saturday

MsgBox(0, "Demo", _DateGetWeekDay(@YEAR, 2, 5, $Friday) & @CRLF & @CRLF & "@error: " & @error)

; counts next n. weekdays starting from passed year/month and returns target date
; sets @error if required date goes beyond required month
Func _DateGetWeekDay($Year = @YEAR, $Month = @MON, $Week = 1, $WeekDay = @WDAY, $DateFormat = 0)
    Local $iFirstDayOfMonth = _DateToDayOfWeek($Year, $Month, 1) - 1 ; the day of the Week Range is 0 to 6 where 0=Sunday.
    Local $DaysToSkip = Mod(7 + ($WeekDay - 1 - $iFirstDayOfMonth), 7) + (($Week - 1) * 7)
    Local $aTargetDate, $aDummyTime
    _DateTimeSplit(_DateAdd('D', $DaysToSkip, $Year & '/' & $Month & '/01'), $aTargetDate, $aDummyTime)
    Return SetError($Year <> $aTargetDate[1] Or $Month <> $aTargetDate[2], 0, _DateTimeFormat($aTargetDate[1] & '/' & $aTargetDate[2] & '/' & $aTargetDate[3], $DateFormat))
EndFunc   ;==>_DateGetWeekDay

 

Edited by Chimp
added check also on target year

 

image.jpeg.9f1a974c98e9f77d824b358729b089b0.jpeg Chimp

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Link to comment
Share on other sites

For kicks, This one doesn't need to validate or Incept errors no matter what you throw at it 
So long as the syntax is correctly put, .. Or if one finds logic to use this  otherwise  (didn't look too much further..)

 

#include <Date.au3>

;  _DateAdd() Like way
$Day = 6
$iInstance = 108
$fromDate = True ; will Start counting from the day number else for WeekDay "friday = 6" keep at default = false
$Date = _DateDayInMonth_Istance(@YEAR, @MON, $Day, $iInstance, $fromDate)
ConsoleWrite(_DateTimeFormat($Date, 1) & @LF)

; Confirm
$Format = StringFormat("%04i/%02i/%02i", @YEAR, @MON, $Day)
ConsoleWrite(_DateTimeFormat(_DateAdd("w", $iInstance, $Format), 1) & @LF)

; Get first Instance of the WeekDay from the returned year and month
$iInstance = 1
$SplitDate = StringSplit($Date, "/")
$Day = _DateToDayOfWeek($SplitDate[1], $SplitDate[2], $SplitDate[3])

$Date = _DateDayInMonth_Istance($SplitDate[1], $SplitDate[2], $Day, $iInstance)
ConsoleWrite(_DateTimeFormat($Date, 1) & @LF)

Func _DateDayInMonth_Istance($iYear = @YEAR, $iMonth = @MON, $iWeekDay = @WDAY, $iInstance = 1, $iStartDay = False)
    Local $iInst = ($iInstance * 7) - 7 + 1, $iDay
    If $iStartDay Then
        $iInst += 6
    Else
        $iWeekDay -= _DateToDayOfWeek($iYear, $iMonth, 1)
        If $iWeekDay < 0 Then $iWeekDay += 7
    EndIf
    $iDay = $iWeekDay + $iInst
    While $iDay > _DateDaysInMonth($iYear, $iMonth)
        $iDay = Abs($iDay - _DateDaysInMonth($iYear, $iMonth))
        Switch $iMonth
            Case 12
                $iYear += 1
                $iMonth = 1
            Case Else
                $iMonth += 1
        EndSwitch
    WEnd
    Return StringFormat("%04i/%02i/%02i", $iYear, $iMonth, $iDay)
EndFunc

Deye

Edited by Deye
cleaning ..
Link to comment
Share on other sites

ha, thanks, got it, so, using true as fifth parameter then $day becomes the day of the month from wich starting counting the number of weeks ($iInstance) forward regardless of the week day.
hmmm, if you allow , even if the 2 things seems similar, are 2 quite different concepts. in this way you loose track of the Week Day. I would keep two separate functions for that..
remaining on topic (counting the number of a given weekday) I would instead implement the possibility of counting a given weekday starting from a specific date rather than from the beginning of the month, (maybe leaving as default the beginning of the month. parameters should be, and be used differently in that case)
sorry for saying, just a thought

 

image.jpeg.9f1a974c98e9f77d824b358729b089b0.jpeg Chimp

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Link to comment
Share on other sites

Thanks Chimp,

@ Sticking back to topic, This is should be my last version for this function
 

#include <Date.au3>

; 5 instances of Friday begining of the first instace DaysInMonth of February 2019 should return "Friday, March 1, 2019"

$YEAR = @YEAR
$February = 2
$Friday = 6
$Monday = 2

For $Instance = 1 To 5
    $Date = _DateDayInMonth_Istance($YEAR, $February, $Friday, $Instance)
    ConsoleWrite("Instance " & $Instance & " - " & _DateTimeFormat($Date, 1) & @LF)

    $Date = _DateDayInMonth_Istance($YEAR, $February, $Monday, $Instance)
    ConsoleWrite("Instance " & $Instance & " - " & _DateTimeFormat($Date, 1) & @LF)

    ConsoleWrite(@LF)
Next

Func _DateDayInMonth_Istance($iYear = @YEAR, $iMonth = @MON, $iWeekDay = @WDAY, $iInstance = 1)
    $iWeekDay -= _DateToDayOfWeek($iYear, $iMonth, 1) - 8
    Return _DateAdd("d", ($iInstance * 7) - 7, StringFormat("%04i/%02i/%02i", $iYear, $iMonth, $iWeekDay + ($iWeekDay > 7 ? -7 : 0)))
EndFunc   ;==>_DateDayInMonth_Istance

Deye

Edited by Deye
Revised Code
Link to comment
Share on other sites

@Deye, nice version, simple and concise.
... I post my last version too with some bonus extra: I've rearranged a bit the parameters sequence so to be able to pass not only the year and the month, but also optionally the day. In this way you can search for a weekday starting from any date. If you want to find the instance of a weekday starting from a given date instead of the beginning of the month, just pass also the optional starting day as fifth parameter. The function will return the date of nth weekday you requested. Note if you want you can also search backward instead of forward, just pass a negative instance. This can be useful to find the last weekday of a month for example, just pass the last day of the month as starting date and search backward by passing -1 as instance. I think can be of use sometime...

#include <date.au3>
Example()

Func Example()
    ; just to simplify the association between the weekdays and the corresponding ISO numbers
    Local Enum $Monday = 1, $Tuesday, $Wednesday, $Thursday, $Friday, $Saturday, $Sunday ; (1=monday ... 7=sunday)

    ConsoleWrite('The first Sunday of August is ')
    ConsoleWrite(_DateTimeFormat(_DateFindWeekDay($Sunday, 1, @YEAR, 8), 1) & @CRLF)

    ConsoleWrite('The last Sunday of the year is ')
    ConsoleWrite(_DateTimeFormat(_DateFindWeekDay($Sunday, -1, @YEAR, 12, 31), 1) & @CRLF)

    ; the following resulting date falls into the next month, so the @extended flag is setted to 1
    ConsoleWrite('The fifth Friday of February is ')
    Local $TargetDate = _DateFindWeekDay($Friday, 5, 2019, 2)
    Local $extended = @extended
    ConsoleWrite(_DateTimeFormat($TargetDate, 1) & @TAB & "@extended: " & $extended & @CRLF)

    ConsoleWrite('The last Monday of this century will be on ')
    ConsoleWrite(_DateTimeFormat(_DateFindWeekDay($Monday, -1, 2099, 12, 31), 1) & @CRLF)

    ConsoleWrite('The first Sunday of my life was on ')
    ConsoleWrite(_DateTimeFormat(_DateFindWeekDay($Sunday, 1, 1962, 5, 2), 1) & @CRLF)
EndFunc   ;==>Example

; #FUNCTION# ====================================================================================================================
; Name ..........: _DateFindWeekDay
; Description ...: Find a date by counting a number of weekdays forward or backward starting from passed date
; Syntax ........: _DateFindWeekDay([$iWeekDay = DOW_ISO[, $iInstance = 1[, $iYear = @YEAR[, $iMonth = @MON[, $iDay = 1]]]]])
; Parameters ....: $iWeekDay    - [optional] An integer value. the weekday you want to find (1=Monday...7=Sunday) default is doday's weekday
;                  $iInstance   - [optional] An integer value. the wanted instance of WeekDay. Default is 1.
;                  $iYear       - [optional] An integer value. the year from which to start counting. Default is @YEAR.
;                  $iMonth      - [optional] An integer value. the month from which to start counting @MON.
;                  $iDay        - [optional] An integer value. the day from which to start counting. Default is 1 (starting of month).
; Return values .: The target date in the format "YYYY/MM/DD"
;                  Remark: If target date falls outside the starting month, the @extended flag is setted to 1
; Author ........: Chimp
; Modified ......:
; Remarks .......:
; Related .......:
; Link ..........:
; Example .......: _DateFindWeekDay(7, 1) ; Finds the first Sunday of current Month
; ===============================================================================================================================
Func _DateFindWeekDay($iWeekDay = _DateToDayOfWeekISO(@YEAR, @MON, @MDAY), $iInstance = 1, $iYear = @YEAR, $iMonth = @MON, $iDay = 1)
    Local $iDirection = ($iInstance < 0 ? -1 : 1), $aTarget, $aDummyTime
    Local $iWeekDayOfDate = _DateToDayOfWeekISO($iYear, $iMonth, $iDay) - 1 ; WeekDay (0 to 6 where 0=Monday)
    Local $iDaysToSkip = Mod($iDirection * 7 + ($iWeekDay - 1 - $iWeekDayOfDate), 7) + (($iInstance + -1 * $iDirection) * 7)
    _DateTimeSplit(_DateAdd('D', $iDaysToSkip, $iYear & '/' & $iMonth & '/' & $iDay), $aTarget, $aDummyTime)
    Return SetExtended($iYear <> $aTarget[1] Or $iMonth <> $aTarget[2], StringFormat("%04i/%02i/%02i", $aTarget[1], $aTarget[2], $aTarget[3]))
EndFunc   ;==>_DateFindWeekDay

 

 

image.jpeg.9f1a974c98e9f77d824b358729b089b0.jpeg Chimp

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...