I am aware that this question has been asked in many different forms, but I would like to show my case as I have not found the perfect solution for it.
So, what I need to do is divide every month in 4 or 5 weeks, and type it into the corresponding cells.
Example :
I have tried this sample code written by User : danieltakeshi in this thread :
https://stackoverflow.com/a/47393516/11969596
But it has a flaw in it, for example if you type a date from October 2021 the result outputs 6 weeks which is impossible :
Sub WeeksInMonth()
Dim MonthYear As String, txt As String
Dim InputDate As Date, MonthYearDay As Date
Dim i As Long, intDaysInMonth As Long, j As Long
Dim MyArray As Variant
Dim arr As New Collection, a
ReDim MyArray(0 To 31)
j = 0
InputDate = ("1 / 10 / 2021") ' Date from October
MonthYear = Month(InputDate) & "/" & Year(InputDate)
intDaysInMonth = Day(DateSerial(Year(MonthYear), Month(MonthYear) + 1, 0))
For i = 1 To intDaysInMonth
MonthYearDay = DateSerial(Year(InputDate), Month(InputDate), i)
MyArray(j) = Application.WorksheetFunction.WeekNum(MonthYearDay)
j = j + 1
Next i
ReDim Preserve MyArray(0 To j - 1)
On Error Resume Next
For Each a In MyArray
arr.Add a, CStr(a)
Next
For i = 1 To arr.Count
Debug.Print arr(i)
Next
End Sub
Please help me find a solution, or tell me how I can adapt it to my current situation.
Cordially,
This routine checks for the first and last workingday (monday to friday) and then gives the calendar weeks for that date range
Option Explicit
Public Sub test_getWeeknumbersForMonth()
Dim arr As Variant
arr = getWeekNumbersForMonth("1.10.2021")
Debug.Print "1.10.2021: ", Join(arr, " - ")
arr = getWeekNumbersForMonth("1.1.2022")
Debug.Print "1.1.2022: ", Join(arr, " - ")
End Sub
Public Function getWeekNumbersForMonth(inputDate As Date) As Variant
Dim datStart As Date
datStart = getFirstWorkingDayOfMonth(inputDate)
Dim datEnd As Date
datEnd = getLastWorkingDayOfMonth(inputDate)
Dim arrWeekNumbers As Variant
ReDim arrWeekNumbers(1 To 6) 'max 6 weeks can be returned
Dim i As Long: i = 1
Dim dat As Date
dat = datStart
While dat <= datEnd
arrWeekNumbers(i) = getCalendarWeek(dat)
i = i + 1
dat = DateAdd("ww", 1, dat)
Wend
ReDim Preserve arrWeekNumbers(i - 1)
getWeekNumbersForMonth = arrWeekNumbers
End Function
Private Function getFirstWorkingDayOfMonth(inputDate As Date) As Date
Dim datToCheck As Date: datToCheck = DateSerial(Year(inputDate), Month(inputDate), 1) - 1
Dim isWorkingday As Boolean
Do
datToCheck = datToCheck + 1
isWorkingday = Weekday(datToCheck, vbMonday) <= 5
Loop Until isWorkingday = True
getFirstWorkingDayOfMonth = datToCheck
End Function
Private Function getLastWorkingDayOfMonth(inputDate As Date) As Date
Dim datToCheck As Date: datToCheck = DateSerial(Year(inputDate), Month(inputDate) + 1, 1)
Dim isWorkingday As Boolean
Do
datToCheck = datToCheck - 1
isWorkingday = Weekday(datToCheck, vbMonday) <= 5
Loop Until isWorkingday = True
getLastWorkingDayOfMonth = datToCheck
End Function
Private Function getCalendarWeek(inputDate As Date) As Long
'european iso week - CW 1 = week with first thursday
getCalendarWeek = Application.WorksheetFunction.IsoWeekNum(inputDate)
'use weeknum-function -adjust second parameter to your needs
'https://support.microsoft.com/en-us/office/weeknum-function-e5c43a03-b4ab-426c-b411-b18c13c75340
'getCalendarWeek = Application.WorksheetFunction.WeekNum(inputDate, 2)
End Function
First, some months have dates in six weeks.
Next, VBA natively can't return the correct ISO 8601 weeknumbers:
How to get correct week number in Access
Finally, week numbers don't care about workdays or weekends. If you wish to exclude weeks that don't include specific weekdays, filter on the dates of these.
Related
I need to automatically calculate a Start Date (aka QRT_START) which is 5 years of Quarters back. A Quarter is 3 months. For example, there are 4 Quarters in a Year: March 31st, June 30th, September 30th and December 31st.
Since we are currently in November 16th 2022, the Start Date would be December 31st 2017. So depending on whatever the current date is, the Start Date needs to go back 5 years worth of Quarters.
I also need to automatically calculate the most recent End Date (aka QRT_END). So since, we are in November 16th 2022, the End Date would be the previous quarter end before today which is September 30th 2022. I have the VBA code written below, please help me fix.
Private Function getQRT_END() As String
Dim endmonth As Variant
Dim endyear As Variant
Dim Day As Variant
endmonth = Month(Date) - 1
If endmonth = 0 Then
endyear = Year(Date) - 1
endmonth = 12
day = 31
Else
endyear = Year(Date)
If endmonth = 3 Then
day = 31
Else
day = 30
End if
endmonth = “0” & endmonth
End If
getQRT_END = endyear & endmonth & day
End Function
Private Function getQRT_START() As String
Dim startmonth As Variant
Dim startyear As Variant
Dim Day As Variant
startyear = Year(Date) - 5
startmonth = Month(Date) + 2
If startmonth <10 Then
If startmonth = 3 Then
day = 31
Else
day = 30
End if
startmonth = “0” & startmonth
Else
day = 30
End If
getQRT_START = startyear & startmonth & day
End Function
Function GetQuartal(years, data)
d = DateAdd("yyyy", years, data)
q = (Month(d) + 2) \ 3
qstart = DateSerial(Year(d), (q - 1) * 3 + 1, 1)
qend = DateSerial(Year(d), q * 3 + 1, 1) - 1
GetQuartal = Array(data, d, qstart, qend)
End Function
Sub test()
Debug.Print "Date", "Date-5Y", "QY-5 Start", "QY-5 End"
For Each d In Array(Date, #2/29/2000#, #12/1/2021#, #5/5/1992#)
q = GetQuartal(-5, d)
Debug.Print q(0), q(1), q(2), q(3)
Next
End Sub
Date Date-5Y QY-5 Start QY-5 End
17.11.2022 17.11.2017 01.10.2017 31.12.2017
29.02.2000 28.02.1995 01.01.1995 31.03.1995
01.12.2021 01.12.2016 01.10.2016 31.12.2016
05.05.1992 05.05.1987 01.04.1987 30.06.1987
You can use two functions found in my library at GitHub: VBA.Date.
? DateThisQuarterUltimo(DateAdd("yyyy", -5, Date))
2017-12-31
? DatePreviousQuarterUltimo(DateAdd("yyyy", -5, Date))
2017-09-30
They are simple high-level functions:
' Returns the ultimo date of the quarter of the date passed.
'
' 2016-01-13. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function DateThisQuarterUltimo( _
ByVal DateThisQuarter As Date) _
As Date
Dim Interval As String
Dim Number As Double
Dim ResultDate As Date
Number = 0
Interval = IntervalSetting(DtInterval.dtQuarter)
ResultDate = DateIntervalUltimo(Interval, Number, DateThisQuarter)
DateThisQuarterUltimo = ResultDate
End Function
' Returns the ultimo date of the quarter preceding the quarter of the date passed.
'
' 2016-01-13. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function DatePreviousQuarterUltimo( _
ByVal DateThisQuarter As Date) _
As Date
Dim Interval As String
Dim Number As Double
Dim ResultDate As Date
Number = -1
Interval = IntervalSetting(DtInterval.dtQuarter)
ResultDate = DateIntervalUltimo(Interval, Number, DateThisQuarter)
DatePreviousQuarterUltimo = ResultDate
End Function
Haven't tested it completely
You can use DateSerial, DateAdd and DatePart to achieve what you want...
Option Explicit
Sub Sample()
Dim D As Date
Dim prevD As Date
D = DateSerial(2022, 11, 16)
'~~> Date 5 years years ago
prevD = DateAdd("q", -(4 * 5), D)
'~~> Last date of the quarter for a specific date
Debug.Print DateAdd("q", DatePart("q", prevD), DateSerial(Year(prevD), 1, 1)) - 1
'OUTPUT : 31-12-2017
'~~> Last date of previous quarter for a specific date
Debug.Print DateAdd("q", DatePart("q", D) - 1, DateSerial(Year(D), 1, 1)) - 1
'OUTPUT : 30-09-2022
End Sub
Changed string part to a proper date.
I would like to create a date column formatted via "mmm-yyyy" but starting with next year's date, i.e.
Jan-2020
Feb-2020
Mar-2020
Apr-2020
May-2020
Jun-2020
Jul-2020
Aug-2020
Sep-2020
Oct-2020
Nov-2020
Dec-2020
My code only created the same month for 12 times. Can any one help me with this?
My current code
Sub demo()
'month recurring till dec
Dim x As Integer
Dim i As Integer
For x = 1 To 12
For i = 1 To 12
StartDate = (month(x + 1)) & "-" & (Year(Now())) + 1
Cells(i, 1).Value = StartDate
Cells(i, 1).NumberFormat = "mmm-yyyy"
Next i
Next x
End Sub
Write month dates into column
You have several issues here. Basically you aren't incrementing the year (correct: Year(Now) + increment) and you are overwriting each target cell 12-times with the last calculated value.
Working example procedure
Option Explicit ' declaration head of your code module
Sub demo()
With Sheet1 ' << Reference the sheet's CodeName, e.g. Sheet1
Dim repetition As Long ' << Long, provide for counters greater than ~65K
For repetition = 1 To 10 ' << change to ..= 1 to 1 if only one column :-)
Dim mon As Long
For mon = 1 To 12
' calculate month date via DateSerial function (arguments year,month,day)
Dim StartDate As Date
StartDate = DateSerial(Year(Now) + repetition, mon, 1)
' write to fully referenced range - note the prefixed "." referring to the sheet object
.Cells((repetition - 1) * 12 + mon, 1).Value = StartDate
.Cells((repetition - 1) * 12 + mon, 1).NumberFormat = "mmm-yyyy"
Next mon
Next repetition
End With
End Sub
Problem
The following [mcve] will output an array of arrays of week numbers between two dates. It works when both dates are on the same year, however, some years have 52 weeks and start within the last days of the last year. And others have 53 weeks.
An example of 52 weeks is the 2020 calendar:
Where the first week begins on Dec 30.
And the example of 53 weeks is the 2016 calendar:
That begins only on Jan 4th.
Code
The following code is commented and outputs an array of arrays with the week numbers.
Sub w_test()
Dim Arr() As Variant, ArrDateW() As Variant
'Initial Date
DateI = DateSerial(2015, 5, 5)
'Final Date
DateF = DateSerial(2017, 9, 20)
'Difference in weeks between DateI and DateF
weekDif = DateDiff("ww", DateI, DateF) + k - 1
i = Weekday(DateI)
d = DateI
'If not Sunday, go back to last week, to start the loop
If i <> 1 Then
d = DateAdd("d", -(i - 1), d)
End If
ReDim ArrDateW(weekDif)
ReDim Arr(2)
'Loop on all weeks between two dates to populate array of arrays
For i = 0 To weekDif
'Date
Arr(0) = d
'Trying to solve problem with New Year
If Application.WorksheetFunction.WeekNum(d) = 53 Then
flag = True
End If
If flag = False Then
Arr(1) = Application.WorksheetFunction.WeekNum(d)
Else
Arr(1) = Application.WorksheetFunction.WeekNum(DateSerial(Year(d) + 1, 1, 1))
flag = False
End If
'Year
Arr(2) = Year(d)
'Populate array of arrays
ArrDateW(i) = Arr
'Next Week Number
d = DateAdd("ww", 1, d)
Next i
'To stop with Ctrl+F8
Debug.Print d
End Sub
Question
2015 had 53 weeks, however the program outputs the following:
And between 2016 and 2017, the output is a mess:
How to fix the program to output these week numbers correctly?
I went about it somewhat differently, relying on built-in VBA functions to correctly calculate the week numbers. Read about ISO week numbers is this answer and see how I'm using the DataPart function -- though you can substitute your own version of Ron de Bruin's ISO week number function if you feel it's warranted.
A couple of quick side notes:
Always use Option Explicit
Try to use more descriptive variable names. YOU know what you're talking about NOW. In a few months, you'll struggle to remember what d and Arr mean (even if it seems obvious now). It's just a good habit and makes the code self-documenting.
My example below breaks the logic into a separate function with an optional parameter (just for fun) that would allow the caller to change the start of the week to a different day.
Code module:
Option Explicit
Sub w_test()
Dim initialDate As Date
Dim finaldate As Date
initialDate = #5/5/2015#
finaldate = #9/29/2017#
Dim weeks As Variant
weeks = WeekNumbers(initialDate, finaldate)
Debug.Print "There are " & UBound(weeks, 1) & " weeks between " & _
Format(initialDate, "dd-mmm-yyyy") & " and " & _
Format(finaldate, "dd-mmm-yyyy")
End Sub
Private Function WeekNumbers(ByVal initialDate As Date, _
ByVal finaldate As Date, _
Optional ByVal weekStart As VbDayOfWeek = vbSunday) As Variant
Dim numberOfWeeks As Long
numberOfWeeks = DateDiff("ww", initialDate, finaldate, weekStart, vbFirstFullWeek)
Dim startOfWeek As Date
If Weekday(initialDate) <> vbSunday Then
Dim adjustBy As Long
If Weekday(initialDate) > weekStart Then
adjustBy = Weekday(initialDate) - weekStart
Else
adjustBy = (Weekday(initialDate) + 7) - weekStart
End If
startOfWeek = DateAdd("d", -adjustBy, initialDate)
End If
Dim allTheWeeks As Variant
ReDim allTheWeeks(1 To numberOfWeeks)
Dim weekInfo As Variant
ReDim weekInfo(1 To 3)
Dim i As Long
For i = 1 To numberOfWeeks
weekInfo(1) = startOfWeek
weekInfo(2) = DatePart("ww", startOfWeek, weekStart, vbFirstFourDays)
weekInfo(3) = Year(startOfWeek)
allTheWeeks(i) = weekInfo
startOfWeek = DateAdd("ww", 1, startOfWeek)
Next i
WeekNumbers = allTheWeeks
End Function
Hi (please excuse the bad formatting and conventions I am not a coder)
I have a VBA function that calculates the time weighted average given a table of dates and values. The problem is that I cannot pass the iterator through one of the excel functions.
The issue arises in the following line:
totalWeighting = totalWeighting + Cells(Application.Match(start_date, dateRange, 1), userCodeColumn)
When using start_date the function works fine (note that I have a bunch of other code to Dim variables which I have excluded). However, when you replace start_date with "d", it returns #value.
Function TimeWeightedAverage(start_date As Date, end_date As Date, user_code) As Double
Dim d As Date
Dim totalWeighting As Double
Dim userCodeColumn As Integer
Dim dateRange As Range
Dim denominator As Integer
Dim startRow As Integer
If Mid(user_code, 3, 1) = 2 Then
Set dateRange = ActiveSheet.Range("A:A")
Else
Set dateRange = ActiveSheet.Range("H:H")
End If
totalWeighting = 0
denominator = WorksheetFunction.NetworkDays(start_date, end_date)
Let userCodeColumn = user_code.Column
Let startRow = Application.Match(start_date, dateRange, 1)
For d = start_date To end_date
If WorksheetFunction.Weekday(d, 11) < 6 Then
totalWeighting = totalWeighting + Cells(Application.Match(d, dateRange, 1), userCodeColumn)
End If
Next
TimeWeightedAverage = totalWeighting / denominator
Any thoughts would be greatly appreciated.
Data:
Ok I think I sorted out your problem.
First of all, a loop like the one you used works with integer/longs/singles/doubles... not dates, so you can't use d as a date, because it is not.
On the other hand you were declarin a startrow which wasn't being used like denominator which was calculating the working days between the 2 dates. Instead, calculate all the days between the 2 dates and loop through all of them increasing 1 day to startdateat a time and giving that day to a Date variable, your Ifstatement will handle if it's a working day or not.
Option Explicit
Function TimeWeightedAverage(start_date As Date, end_date As Date, user_code As Range) As Double
Dim d As Long
Dim userCodeColumn As Long
Dim dateRange As Range
Dim denominator As Long
Dim CheckDate As Date
If Mid(user_code, 3, 1) = 2 Then
Set dateRange = ActiveSheet.Range("A:A")
Else
Set dateRange = ActiveSheet.Range("H:H")
End If
denominator = DateDiff("d", start_date, end_date)
Let userCodeColumn = user_code.Column
For d = 1 To denominator
If d = 1 Then
CheckDate = start_date
Else
CheckDate = start_date + d
End If
If WorksheetFunction.Weekday(CheckDate, 11) < 6 Then
TimeWeightedAverage = TimeWeightedAverage + ActiveSheet.Cells(Application.Match(CheckDate, dateRange, 1), userCodeColumn)
End If
Next
End Function
I would like a textbox, "txtWeek," to show the number of Fridays or Thursdays between the beginning of the month to the current date, IE I have started with
Dim MyDate, MyStr
MyDate = Format(Now, "M/d/yy")
Me.txtDate.Value = MyDate
Dim Day As Variant
ReDim Day(2)
Day = Array("Thursday", "Friday")
ComboBox1.ColumnCount = 1
ComboBox1.List() = Day
Dim X, AsDate
X = Format(Now, "M/1/yy")
If Me.ComboBox1.Text = "Friday" Then
Me.txtWeek.Value = Int((Weekday(X - 6) - X + Me.txtDate.Value) / 7)
Else
End If
End Sub
Requirements:
To show in Textbox txtDate the date of the machine
To calculate the number of Fridays or Thursdays in the month of txtDate till the date of the machine
To show in Textbox txtWeek the number of Fridays or Thursdays as per prior point
Assumptions:
The Sheet1 of the workbook containing the procedures has two TextBoxes and one ComboBox
The Procedures will be triggered by the change events of the ComboBox, when user select the weekday to count
Copy this procedure in the Code Module of Sheet1 - Change Event for the ComboBox
Private Sub CmbBox1_Change()
Dim sWkDy As String
Dim dDte1 As Date
Dim bDayC As Byte
Dim bThu As Boolean, bFri As Boolean
Rem Set Weekday
sWkDy = Me.CmbBox1.Value
Select Case sWkDy
Case "Thursday": bThu = True
Case "Friday": bFri = True
Case Else: Exit Sub
End Select
Rem Set First date of the current month
dDte1 = 1 + WorksheetFunction.EoMonth(Date, -1)
Rem Counts the weekdays
bDayC = Dte_Days_Count_To_Today(dDte1, blThu:=bThu, blFri:=bFri)
Rem Set Current Date in `txtDate`
'Using format `mmm-dd-yyyy` to ease reading of the date independently of the format (American or International)
Me.TxtDate.Value = Format(Date, "mmm-dd-yyyy") 'change as required
Rem Set count of weekdays `txtWeek`
'Using this format to directly show the weekdays counted
Me.TxtWeek.Value = "Count of " & sWkDy & "s: " & bDayC 'change as required
End Sub
Copy these procedures in a standard module
'Ensure these Keywords are at the top of the module
Option Explicit
Option Base 1
This procedure sets the available options in the Combobox – Run this first, need to run only once
Private Sub CmbBox1_Set()
Dim aWkDys As Variant
aWkDys = [{"Thursday", "Friday"}]
With Me.CmbBox1
.ColumnCount = 1
.List() = aWkDys
End With
End Sub
This Function counts the numbers of days from the date entered as input date dDteInp to the actual date of the machine TODAY. The results are generated using arithmetic calculus and avoids the loop trough each of the dates in the range. It also gives the option to count various weekdays at once e.g.: to count Thursdays and Fridays from a given date till today call it this way Call Dte_Days_Count_To_Today(dDteInp, blThu:=True, blFri:=True)
Public Function Dte_Days_Count_To_Today(dDteInp As Date, _
Optional blSun As Boolean, Optional blMon As Boolean, _
Optional blTue As Boolean, Optional blWed As Boolean, _
Optional blThu As Boolean, Optional blFri As Boolean, _
Optional blSat As Boolean)
Dim aDaysT As Variant, bDayT As Byte 'Days Target
Dim bDayI As Byte 'Day Ini
Dim iWeeks As Integer 'Weeks Period
Dim bDaysR As Byte 'Days Remaining
Dim bDaysA As Byte 'Days Additional
Dim aDaysC(7) As Integer 'Days count
Rem Set Days Base
aDaysT = Array(blSun, blMon, blTue, blWed, blThu, blFri, blSat)
bDayI = Weekday(dDteInp, vbSunday)
iWeeks = Int((Date - dDteInp + 1) / 7)
bDaysR = (Date - dDteInp + 1) Mod 7
Rem Set Day Target Count
For bDayT = 1 To 7
bDaysA = 0
aDaysC(bDayT) = 0
If aDaysT(bDayT) Then
If bDaysR = 0 Then
bDaysA = 0
ElseIf bDayI = bDayT Then
bDaysA = 1
ElseIf bDayI < bDayT Then
If bDayI + bDaysR - 1 >= bDayT Then bDaysA = 1
Else
If bDayI + bDaysR - 8 >= bDayT Then bDaysA = 1
End If
Rem Target Day Total
aDaysC(bDayT) = iWeeks + bDaysA
End If: Next
Rem Set Results - Total Days
Dte_Days_Count_To_Today = WorksheetFunction.Sum(aDaysC)
End Function
Suggest to read the following pages to gain a deeper understanding of the resources used:
Option keyword,
Variables & Constants,
Data Type Summary,
Optional keyword,
Function Statement,
For...Next Statement,
If...Then...Else Statement,
Control and Dialog Box Events,
Select Case Statement,
WorksheetFunction Object (Excel)
This UDF will count the number of whatever day you pass into it, between two dates passed as longs.
Public Function HowManyDays(Sdate As Long, Edate As Long, Wday As Long)
Dim i
Dim MyCount As Long
For i = Sdate To Edate
If Weekday(i) = Wday Then MyCount = MyCount + 1
Next i
HowManyDays = MyCount
End Function
Wday represents the day of the week, eg. sunday=1, monday=2... etc.
I don't know if it changes to monday=1, tuesday=2 etc. on other systems, or if it's always sunday=1.
With this UserForm code, a textbox will show the number of anyday depending on the value in a combobox:
Private Sub CommandButton1_Click()
Dim Sdate As Long, Edate As Long, Wday As Long
Sdate = CLng(DateSerial(Format(Now, "yy"), Format(Now, "mm"), 1))
Edate = CLng(Now)
Select Case ComboBox1.Value
Case "Sunday"
Wday = 1
Case "Monday"
Wday = 2
Case "Tuesday"
Wday = 3
Case "Wednesday"
Wday = 4
Case "Thursday"
Wday = 5
Case "Friday"
Wday = 6
Case "Saturday"
Wday = 7
End Select
TextBox1.Value = HowManyDays(Sdate, Edate, Wday)
End Sub
Private Sub UserForm_Initialize()
Dim Day As Variant
ReDim Day(7)
Day = Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")
ComboBox1.ColumnCount = 1
ComboBox1.List() = Day
End Sub
The start date is currently set to the first of the current month.
If you don't want to click a button to perform the action you can take the code from the CommandButton1_Click() and put it in ComboBox1_Change(), that way it will update the textbox whenever the combobox changes.