Jump to content
ces1a

Finding Nth weekday of any month

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

Share this post


Link to post
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

Share this post


Link to post
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

Thoughts:

  • I will always thank you for the time you spent for me.
    I'm here to ask, and from your response, I'd like to learn.
    By my knowledge, I can help someone else, and "that someone" could help in turn another, and so on.

/*--------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/

ALWAYS GOOD TO READ:

 

Share this post


Link to post
Share on other sites
Posted (edited)

@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

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

Share this post


Link to post
Share on other sites
Posted (edited)

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 ..

Share this post


Link to post
Share on other sites
Posted (edited)

Hi Chimp,

Just Added that for the small test, So using that switch will only mimic DateAdd() 

Deye

Edited by Deye

Share this post


Link to post
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


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

Share this post


Link to post
Share on other sites
Posted (edited)

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

Share this post


Link to post
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

 


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

Share this post


Link to post
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

  • Similar Content

    • By kawliga751
      I am attempting to run an Autoit.exe with schtasks every weekday @ 9:05 am.
      I have the below
      C:\>SchTasks /Create /TN "P10Run" /TR "O:\AUTOIT\P40 Run.au3" /SC WEEKLY /D MON, TUE,WED,THU,FRI /ST 09:05:00 SUCCESS: The scheduled task "P10Run" has successfully been created. I also did a "test" run :
      C:\>Schtasks /Run /TN "P10Run SUCCESS: Attempted to run the scheduled task "P10Run". But when I run my query to verify success my "Last Result" seems to indicate that the script did not run:
      C:\>SCHTASKS /QUERY /FO LIST /V /TN "P10RUN" Folder: \ HostName: xxxxxxxxxxxxxxxx TaskName: \P10RUN Next Run Time: N/A Status: Could not start Logon Mode: Interactive only Last Run Time: 6/14/2017 9:08:56 AM Last Result: -2147024894 Author: xxxxxxx Task To Run: O:\AUTOIT\P40 Run.au3 Start In: N/A Comment: N/A Scheduled Task State: Enabled Idle Time: Disabled Power Management: Stop On Battery Mode, No Start On Batterie s Run As User: xxxxxxxxx\xxxxxxxx Delete Task If Not Rescheduled: Enabled Stop Task If Runs X Hours and X Mins: 72:00:00 Schedule: Scheduling data is not available in this f ormat. Schedule Type: Weekly Start Time: 9:05:00 AM Start Date: 6/14/2017 End Date: N/A Days: MON, TUE, WED, THU, FRI Months: Every 1 week(s) Repeat: Every: Disabled Repeat: Until: Time: Disabled Repeat: Until: Duration: Disabled Repeat: Stop If Still Running: Disabled C:\> Is there something missing in my command? Also is there a better way to make sure that the script does in fact sun successfully?
      Thanks In Advance! 
    • By kawliga751
      I'm new to Auotit but I have built a simple script that "runs" a different "batch" file based on certain days of the workweek. The script works now, but I was wanting to eliminate the need for a manual date entry. For example "First Batch' needs to run every Tuesday thru Thursday however "Second Batch" needs to run only on Friday and "Third Batch" needs to run only on Monday. In addition the 1st batch file runs on Tuesday, say 06/06 (the "FW" section) but then needs to actually report (the F4 date) the next weekday so this Batch actually needs 2 dates verified. 
      What I'm trying to do is when the script is initiated it gets the date, verifies if and which weekday it is and in turn goes to and runs the appropriate "Batch' file.  
      I've found ways to verify weekdays but can't find anything to do all of the above.
      Any help is MUCH appreciated.
       
      ;P10
      ShellExecute("C:\Program Files (x86)\Ericom Software\PowerTerm Enterprise\Sessions\mir00p10.PTS")

      WinWait('(A) Soutwest P10 : PowerTerm Pro Enterprise Suite')
      WinActivate('(A) Soutwest P10 : PowerTerm Pro Enterprise Suite')
      Send('$Login)
      Sleep(3000)
      Send('{Enter}')
      Sleep(3000)
      Send($Password)
      Send('{Enter}')
      Sleep(3000)
      ; ****First Batch file run
      Send('Batch')
      Sleep(3000)
      Send('{Enter}')
      Send('FW')
      Send('{Enter}')
      Send('{DOWN}')
      Send($Date)
      Send('{Enter}')
      Send('{Enter}')
      Send($Date)
      Send('{F9}')
      Send('Y')
      Sleep(3000)
      Send('{Enter}')
      Send('{F4}')
      Send('Y')
      Sleep(3000)
      Send('{Enter}')
      Send($Date)
      Send('{Enter}')
      Send('0620')
      Send('{Enter}')
      SEND('{!}SW0410PM.FWR')
      Send('{Enter}')
      Sleep(3000)
      Send('Y')
      Send('{Enter}')
      Sleep(3000)
      Send('{F9}')
      Sleep(3000)
      ; ****Second Batch file run
      Send('Batch')
      Sleep(3000)
      Send('{Enter}')
      Send('FW')
      Send('{Enter}')
      Send('{DOWN}')
      Send($Date)
      Send('{Enter}')
      Send('{Enter}')
      Send($Date)
      Send('{F9}')
      Send('Y')
      Sleep(3000)
      Send('{Enter}')
      Send('{F4}')
      Send('Y')
      Sleep(3000)
      Send('{Enter}')
      Send($Date)
      Send('{Enter}')
      Send('0620')
      Send('{Enter}')
      SEND('{!}SO0411AM.FWR')
      Send('{Enter}')
      Sleep(3000)
      Send('Y')
      Send('{Enter}')
      Sleep(3000)
      Send('{F9}')
      Sleep(3000)
      ; ****Third Batch file run
      Send('Batch')
      Sleep(3000)
      Send('{Enter}')
      Send('FW')
      Send('{Enter}')
      Send('{DOWN}')
      Send($Date)
      Send('{Enter}')
      Send('{Enter}')
      Send($Date)
      Send('{F9}')
      Send('Y')
      Sleep(3000)
      Send('{Enter}')
      Send('{F4}')
      Send('Y')
      Sleep(3000)
      Send('{Enter}')
      Send($Date)
      Send('{Enter}')
      Send('0620')
      Send('{Enter}')
      SEND('{!}SW0411AM.LOA')
      Send('{Enter}')
      Sleep(3000)
      Send('Y')
      Send('{Enter}')
      Sleep(3000)
      Send('{F9}')
      Sleep(3000)
      Send('EXIT')
       
       
×
×
  • Create New...