Use of NETWORKDAYS function - excel

I have to find the number of working days between two dates which should exclude weekends and National Holidays.
I am using function NETWORKDAYS in vba, this excludes weekends but I want to exclude Some National Holidays as well.
How to use this function NETWORKDAYS(startDate, endDate, [holidays]) for National holidays. It says it accepts [holidays] as a list. I have all the national holidays in an array. how can I use it with this function via VBA ??
Please find the code snippet.
Public Function dataFromInputSheetHolidayDates() As Variant
Dim holidayDates As Integer
Dim holidaydatesArray(20) As Variant
holidayDates = Sheets("InputSheet").Cells(Rows.Count, "D").End(xlUp).Row
For countDate = 0 To holidayDates - 1
holidaydatesArray(countDate) = Format(Sheets("InputSheet").Cells(countDate + 2, "D").Value, "DD-MMM-YY")
Next countDate
dataFromInputSheetHolidayDates = holidaydatesArray
End Function
holidayList = dataFromInputSheetHolidayDates()
Sheets("Estimation").Range("Z" & taskcounter).Formula = "=NETWORKDAYS(X" &
taskcounter & ",Y" & taskcounter &","& holidayList & ")"
Sheets("Estimation").Range("AB" & taskcounter).Formula = "=NETWORKDAYS(X" &
taskcounter & ",AA" & taskcounter &"," & holidayList & ")"

Change these 2 lines in your code:
Dim holidayDates As Long
holidaydatesArray(countDate) = Sheets("InputSheet").Cells(countDate + 2, "D")
In VBA Integer is up to 32767, which is not quite enough for dates. Furthermore, holidaydatesArray should have a numeric value and not some text format.
Pretty much similar problem as this one - workday holiday argument doesn't accept array
If you are trying to create a flexible formula through VBA, where the holidays are on different worksheet, try this solution:
Public Sub TestMe()
Dim holidayLists As Range
Set holidayLists = Worksheets(2).Range("D1:D10")
With Worksheets(1)
.Range("A1").Formula = "=NETWORKDAYS(B1, C1," & holidayLists.Parent.Name & "!" _
& holidayLists.Address & ")"
End With
End Sub
There the holidayLists.Parent.Name & "!" & holidayLists.Address would refer correctly to the worksheet's name of the holidays.

Related

Current date displayed in US rather than expected format as it was previously

This Excel VBA code does not work anymore.
Private Sub cmdAjouter_Click()
Dim ws As Worksheet
Dim lr As Integer
Set ws = ThisWorkbook.Sheets("Achats"
lr = ws.Range("A" & Rows.Count).End(xlUp).Row + 1
ws.Range("M" & lr).Value = Format(Date, "dd/mm/yyyy")
'[...]
End Sub
Today we are the 11th February 2020 , it should return the date as "11/02/2020" (French format) but it returns "02/11/2020" (US format).
It would be better to store it as the actual Date instead of a String and just use the .NumberFormat property instead to format it, that way is doesn't try to convert it again based on your regional settings.
ws.Range("M" & lr).NumberFormat = "dd/mm/yyyy"
ws.Range("M" & lr).Value = Date
Your way would probably work fine if the date was something like February 13, since then there is no way it would get confused about 13 being a month.

Sequencing a part number using User Form

I am completely new in VBA or programming. Right now I am developing a macro for a manufacturing site that inputs process data using Excel's User Forms. One of the things I want this macro to do is to automatically create run numbers for each process. The run number syntax we use is as follows:
V1.yy.mm.dd-1
V1.yy.mm.dd-2
V1.yy.mm.dd-3
Ex V1.20.04.29-1
The way I am trying to set up the run number creation is that when I select an item from a ComboBox the part number gets created into a TextBox to later be submitted into the corresponding database. I am not sure how to create a sequence after the Prefix = V1.yy.mm.dd-, I tried to use a CountIf application that would count the number of Prefixes with the same date in the spreadsheet for sequencing, but it seems the function does not work for partial matches. I tried to use the following but I can't get it to work. I am sure there are simpler ways to do this, can you give me a few suggestions? Thanks
This is the code I wrote so far:
Private Sub ComboBox1_Change()
If Me.ComboBox1.Value <> "" Then
Dim Prefix As String
Dim mm, dd, yy As String
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("2- V1 Loading (2)")
Dim s As Long
s = 1 + sh.Application.Count(Application.Match(Prefix, Range("B:B"), 0))
mm = Format(Date, "mm")
dd = Format(Date, "dd")
yy = Format(Date, "yy")
Prefix = "V1." & yy & "." & mm & "." & dd & "-"
v1 = "V1." & yy & "." & mm & "." & dd & "-" & "" & s
Me.TextBox6.Value = v1
End If
Maybe something like this ?
Private Sub ComboBox1_Change()
If Me.ComboBox1.Value <> "" Then
Set sh = ThisWorkbook.Sheets("2- V1 Loading (2)")
oDate = Format(Date, "yy.mm.dd")
oConst = "V1." & oDate & "-"
Range("B1:B10000").Copy Destination:=Range("zz1") 'copy all the item to helper column
Range("zz:zz").Replace What:=oConst, Replacement:="" 'get only the number from all the items with the current date
nextNum = Application.Max(Range("zz:zz")) + 1 'get the next number
MsgBox oConst & CStr(nextNum) 'this line only for checking
Range("zz:zz").ClearContents 'clear the helper column
Me.TextBox6.Value = oConst & CStr(nextNum)
End If
But this assuming that the item number in columns B is only at the same day.
If for example there is a forgotten data from any day before the current day, and this want to be inputted with that day and the next number, it need an input box or maybe a cell in sheet where the value is that day, then it will give the last number of that day.
Suppose the data in column B is something like below:
If the code is run today, it will show V1.20.04.30-4 as the next number. With the same data like above, if the code is run tomorrow, it will give V1.20.05.01-1.
To get the next number from yesterday (29 Apr 2020), the code need more line - which is to know on what day the code must get the next number.
Or this kind of line maybe is shorter:
oConst = "V1." & Format(Date, "yy.mm.dd") & "-"
nextNum = oConst & Application.WorksheetFunction.CountIf(Range("B:B"), "*" & oConst & "*") + 1
MsgBox nextNum
There are a few ways you could go about this but I'd say the easiest would be to put the incrementing run number in a separate cell somewhere on your worksheet (or another one if you want) to reference each time.
For example:
When the data is entered onto your 'database' sheet, write the run value to ThisWorkbook.Sheets("2- V1 Loading (2)").Range("AZ1").
Then in your code check that value like so:
Private Sub ComboBox1_Change()
If Me.ComboBox1.Value <> "" Then
Dim Prefix As String
Dim mm, dd, yy As String
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("2- V1 Loading (2)")
Dim s As Long
s = 1 + sh.Range("AZ1").Value
mm = Format(Date, "mm")
dd = Format(Date, "dd")
yy = Format(Date, "yy")
Prefix = "V1." & yy & "." & mm & "." & dd & "-"
v1 = "V1." & yy & "." & mm & "." & dd & "-" & s
Me.TextBox6.Value = v1
Presuming that the reference numbers are written to column B of the 2- V1 Loading (2) tab then the next number must always be the one found at the bottom of the column + 1. If there is no number for that date than the new sequential number should be 1. The code below implements that method
Function NextRef() As String
' 016
Dim Fun As String
Dim Counter As Integer
Dim Rng As Range
Dim Fnd As Range
Dim Sp() As String
Fun = Format(Date, """V1.""yy.mm.dd")
With ThisWorkbook.Worksheets("2- V1 Loading (2)")
' start in row 2 (row 1 holding column captions)
Set Rng = .Range(.Cells(2, "B"), .Cells(.Rows.Count, "B").End(xlUp))
End With
If Rng.Row > 1 Then ' skip, if the column is empty
' finds the first occurrence of Ref from the bottom
Set Fnd = Rng.Find(What:=Fun, _
After:=Rng.Cells(1), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchDirection:=xlPrevious)
If Not Fnd Is Nothing Then
Sp = Split(Fnd.Value, "-")
If UBound(Sp) Then Counter = Val(Sp(1))
End If
End If
NextRef = Fun & -(Counter + 1)
End Function
You can use the function simply like ComboBox1.Value = NextRef. However when and how to call that line of code is a bit unclear in your design as published. Especially, it's not clear why you would want it in a ComboBox at all, given that the box might also contain other information. Your idea to use the Change event may not work as intended because that event occurs with every letter the user types. I have tested this:-
Private Sub ComboBox1_GotFocus()
' 016
With ComboBox1
If .Value = "" Then .Value = NextRef
End With
End Sub
The next reference number is inserted as soon as you click on the ComboBox. It works but it doesn't make sense. I think now that you have the function that does the work you will find a way to deploy it. Good luck.

Identify a date as Thursday and sum the last 6 days

I have a table with several operation codes and its hours, and I need to sum every Thursday the hours spent on each code.
Despite being able to figure it out an IF formula would do the job I got stuck with the sum of the ranges, I could get it working via VBA but I can´t apply that same solution on Formula:
WorksheetFunction.Sum(Range("E" & cCell.Row & ":E" & cCell.Row - 6))
Dim Counter As Integer
Dim cCell As Range
Dim intToday As Integer
Dim CountDate As Integer
Dim strWsName As String
strWsName = ActiveSheet.Name
Dim xWs As Worksheet
Set xWs = Worksheets(strWsName)
'Clause 101
For Counter = 4 To 34
Set cCell = xWs.Cells(Counter, 4)
If WorksheetFunction.WeekDay(cCell.Value) = 5 Then
If cCell.Row = 4 Then
xWs.Range("Q" & cCell.Row) = WorksheetFunction.Sum(Range("E" & cCell.Row & ":E" & cCell.Row))
Else
If cCell.Row >= 34 Then
xWs.Range("Q" & cCell.Row) = WorksheetFunction.Sum(Range("E" & cCell.Row & ":E" & cCell.Row))
Else
If cCell.Row - 6 <= 0 Then
xWs.Range("Q" & cCell.Row) = WorksheetFunction.Sum(Range("E" & cCell.Row & ":E4"))
Else
xWs.Range("Q" & cCell.Row) = WorksheetFunction.Sum(Range("E" & cCell.Row & ":E" & cCell.Row - 6))
End If
End If
End If
End If
Next Counter
End Sub
I would like to know how I could transform that piece of code to a formula on Excel.
After doing a research on formulas and variables ranges I have managed to set this formula and it is working like a charm:
=IF(WEEKDAY($D9)=5;IF(ROW(E9)<=6;SUM(OFFSET(E9;;;-ROW()));SUM(OFFSET(E9;;;-7)));"")
NOTE: My locale settings uses ";" instead of "," on formulas, so bear in mind you must change it to your locale settings.
Explanation:
The "=IF(WEEKDAY($D9)=5" formula will assess if the date is a Thursday, if it is not it will exhbit "" on the column;
The IF(ROW(E9)<=6 clause will serve to identify the limits to the top of my spreadsheet, depding on its value it will SUM diferent ranges:
If the date´s row is smaller than 6 it will run this:
SUM(OFFSET(E9;;;-ROW())). The offset will be the same as the row where the
date is.
Now if the row is bigger than 6 then I will be able to set a fixed
offset of 7 rows: SUM(OFFSET(E9;;;-7))
If one needs to change the calculation to Wednesday all you have to do is to change the number "5" on "=IF(WEEKDAY($D9)=5" numbers of the weekdays intended, here is the list:
1 to Sundays
2 to Mondays
3 to Tuesdays
4 to Wednesdays
5 to Thursdays
6 to Fridays
7 to Saturdays
Thanks to articles found on Excel Jet and Extend Office I managed to build this solution!
I hope more people can use this solution!
The best way to solve this was by creating a UDF, I had the help from WideBoyDixon at ExcelForum:
Public Function SumWeek(sumRange As Range, dateRange As Range, endDate)
Application.Volatile
Dim prevSheetName As String
Dim prevSheet As Worksheet
SumWeek = Application.WorksheetFunction.SumIfs(sumRange, dateRange, "<=" & CStr(CLng(endDate)), dateRange, ">" & CStr(CLng(endDate - 7)))
prevSheetName = Mid("DecJanFebMarAprMayJunJulAugSepOctNov", Month(endDate) * 3 - 2, 3) & CStr(Year(endDate) - IIf(Month(endDate) = 1, 1, 0))
On Error Resume Next
Set prevSheet = Worksheets(prevSheetName)
If Not (prevSheet Is Nothing) Then
SumWeek = SumWeek + Application.WorksheetFunction.SumIfs(prevSheet.Range(sumRange.Address), prevSheet.Range(dateRange.Address), "<=" & CStr(CLng(endDate)), prevSheet.Range(dateRange.Address), ">" & CStr(CLng(endDate - 7)))
End If
End Function

Adding freshly created formula into new module

I've just created a brand new macro. Took function down below from internet (all credits goes to trumpexcel.com), code down below
Function CONCATENATEMULTIPLE(Ref As Range, Separator As String) As String
Dim Cell As Range
Dim Result As String
For Each Cell In Ref
Result = Result & Cell.Value & Separator
Next Cell
CONCATENATEMULTIPLE = Left(Result, Len(Result) - 1)
End Function
Then I proceed to extract data from various columns and into the one (my table is 20 rows x 10 columns)
Sub conact_data()
Dim i As Integer
For i = 2 To Cells(Rows.Count, "A").End(xlUp).Row
Cells(i, "M").Value = Cells(i, "A").Value & " " & _
Cells(i, "B").Value & " / " & Cells(i, "D").Value & "; "
Next i
End Sub
Thanks to that I've got combined data from column A, B and D, so its 20 rows. All I want to do now is to concatenate data from M2:M21 using CONCATENATEMULTIPLE function therefore I try various approach (I want this huge line in P2 cell) like :
Cells(2, 16).Value = CONCATENATEMULTIPLE (M2:M21, " ")
or
Range("P2") = "CONCATENATEMULTIPLE (M2:M21, " ")"
I don't really know how to apply that
Secondly, I'd like withdraw the Cells(i, "B").Value as percentage. Can I do that in one line like Cells(i, "B").NumberFormat="0.00%".Value (which is not working for me obviously) else I need to copy column B into another column with number format and then combine the new column, properly formatted instead of column B?
Thanks in advance
Percent format: Range("B" & i).NumberFormat = "0.00%"
CONCATENATEMULTIPLE
In VBA, CHR(32) = " "
In Excel, CHAR(32) = " "
With that being said...
'Value
Range("P2").Value = CONCATENATEMULTIPLE(Range("M2:M21"), CHR(32))
'Formula
Range("P2").Formula = "=CONCATENATEMULTIPLE(M2:M21, CHAR(32))"
You should really qualify all of your ranges with a worksheet
Say your workbook has 10 sheets. When you say Range("P2"), how do we (VBE) know what sheet you mean? Objects need to be properly qualified. Sometimes this is not a huge issue, but when you are working across multiple sheets, not qualifying ranges can lead to some unexpected results.
You can qualify with a worksheet a few ways.
Directly: ThisWorkbook.Sheets("Sheet1").Range("P2").Copy
Or use a variable like so
Dim ws as Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
ws.Range("P2").Copy
Now there is no room for ambiguity (potential errors) as to the exact location of Range("P2")
First of all, remove your ConcatenateMultiple() code, and instead use Excel worksheet function CONCAT(), which takes a range and a delimiter as parameters.
Here is how you can handle the percentage issue and supply a default for non-numeric items. I've also cleaned up the way you reference your data range.
Sub concat_data()
Dim rngRow As Range, vResult As Variant
Const DEFAULT = 0 'Can also be set to a text value, eg. "Missing"
For Each rngRow In [A2].CurrentRegion.Rows
If IsNumeric(rngRow.Cells(, 4)) Then vResult = rngRow.Cells(, 4) * 100 & "%" Else vResult = DEFAULT
Range("M" & rngRow.Row) = rngRow.Cells(, 1) & rngRow.Cells(, 2) & "/" & vResult & ";"
Next
[M2].End(xlDown).Offset(1).Formula = "=CONCAT(M2:M" & [M2].End(xlDown).Row & ",TRUE,"" "")"
End Sub
I'm not a fan of hard-coding range references, like the [A2] or Range("M"), but will leave that for another time.

Excel VBA INDEX MATCH (not pasting a formula using the code)

I've seen several questions asked around using INDEX MATCH on multiple criteria within VBA, but they all seem to revolve around pasting a formula into a cell.
I'm looping thru a ListObject and trying to do a lookup on 3 criteria on a diff sheet, but I want the value pasted into the cell not the formula. I've tried using various combos of Application.Index, Application.WorksheetFunction.Index, Application.Match, Application.WorksheetFunction.Match and Evaluate() but I'm still getting #Value! (when I don't get a an Error). Some of what I've tried below (it's prob a very simple mistake I'm making).
wsSrc has the following ranges as part of a ListObject and I want to paste the result onto wsDest.
rngDate = Range("Table24[date]") 'Date
rngFrom = Range("Table24[from]") 'String
rngTo = Range("Table24[to]") 'String
rngLookup = Range("Table24[lookup]") 'Double
Based on a combination of a Date, From and To I want to lookup a value in rngLookup.
I've tried:
Application.Index(rngLookup.Address, _
Application.Match(strDate & "USD" & strTo, _
rngDate.Address & rngFrom.Address & rngTo.Address))
I've also tried:
x = wsSrc.Evaluate("INDEX(" & rngLookup.Address & _
",MATCH(" & strDate & "USD" & strTo & "," & _
rngDate.Address & "&" & rngFrom.Address & "&" & rngTo.Address)
I've even tried converting the date using CStr(CDate()) which works on in Excel, but not in the VBA.
No joy on either, any thoughts? Again, to confirm I want just the value not the formula pasted into a cell.
This worked for me using a table and columns of the same name:
Dim sht As Worksheet, dt, sFrom, sTo
Set sht = ActiveSheet ' e.g.
dt = DateSerial(2017, 12, 18)
sFrom = "B"
sTo = "C"
Debug.Print sht.Evaluate( _
"INDEX(Table24[lookup],MATCH(1," & _
"(Table24[date]=" & (dt * 1) & ")*" & _
"(Table24[from]=""" & sFrom & """)*" & _
"(Table24[to]=""" & sTo & """),0))")
Assumes your table's dates are actual dates and not strings.

Resources