Excel - across and down matches - vba

I have a workbook with two sheets in it, the first looks like this:
--------------------------------------------------------
Last Name | First Name | 1-Jan | 2-Jan | 3-Jan | 4-Jan | (continues on like this)
--------------------------------------------------------
SMITH | John | 1 | 1 | | |
--------------------------------------------------------
BOND | James | | | 1 | 1 |
--------------------------------------------------------
Second sheet
--------------------------------------------------------
| January | February | (continues on etc)
--------------------------------------------------------
Last Name | First Name | From | To | From | To |
--------------------------------------------------------
SMITH | John |1/1/18 | 2/2/18| | |
--------------------------------------------------------
BOND | James |3/1/18 |4/1/18 | | |
--------------------------------------------------------
This is a leave sheet and basically the user inputs on the first sheet a '1' in the day where they are taking leave. This is then automatically updated in the second sheet to reflect the inclusive dates of their leave for each month.
So in the first example, user enters 1 in 1-Jan and 2-Jan, this updates second sheet with leave for that employee from 1/1/18 to 2/1/18.
So far, I'm successful in being able to detect when a 1 is entered it grabs the name and date details, I've been using a msgbox to verify that I'm getting the right data.
The problem I'm having is that's as far as I can get, I can't work out how to search the second sheet to find the dates and update accordingly.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
' The variable KeyCells contains the cells that will
' cause an alert when they are changed.
Set KeyCells = Range("D6:OI53")
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
' If cell changed, do the below '
' Get name '
Dim lastName As String
Dim firstName As String
lastName = ActiveSheet.Cells(Target.Cells.Row, 1).Value
firstName = ActiveSheet.Cells(Target.Cells.Row, 2).Value
'Get date '
Dim leaveDate As String
leaveDate = ActiveSheet.Cells(5, Target.Cells.Column).Value
' Test lastname, firstname, date '
UpdateMonthlyLeave lastName, firstName, leaveDate
End If
End Sub
Sub UpdateMonthlyLeave(lastName As String, firstName As String, leaveDate As String)
MsgBox lastName & " " & firstName & " " & leaveDate
' Find employee on monthly leave sheet '
End Sub

This UDF will return a list of start OR end dates. Just make you you select Wrap Text for the columns in sheet 2. I think one advantage may be Excel would only update the cell with the formula if the ranges it specifies are changed.
Perhaps the code could be simplified further, but unfortunately you will have to enter the formula for each cell in sheet2.
Option Explicit
' ShowStartMonth: True If we need to return the start date of the holidays
' MonthRange: The WHOLE Column range of the Month
' RowRange: The Range of the person's row but only the holiday columns, not the name columns
' MonthNameRow: The entire row of where the Month name is
Public Function GetHoliday(ShowStartMonth As Boolean, iMonth As Integer, RowRange As Range, MonthNameRow As Range) As String
Dim MonthRange As Range
Set MonthRange = GetMonthRange(iMonth, MonthNameRow)
'Init variables
' Get the cells for the current month
Dim rRow As Range
Set rRow = Intersect(RowRange, RowRange.Worksheet.UsedRange, MonthRange)
Dim IsCurrentCellHoliday As Boolean
Dim IsLastCellHoliday As Boolean
Dim IsStartHolidayContinuation As Boolean
' If First Day of month is a holiday and last day of last month is a holiday then
' Holiday is continuation
IsStartHolidayContinuation = (rRow.Cells(1).Value = 1) And (rRow.Cells(1).Offset(0, -1).Value = 1)
IsLastCellHoliday = (rRow.Cells(1).Value = 1)
' These will hold the dates for start or end of a holiday
Dim StartDays() As String
Dim EndDays() As String
ReDim StartDays(0 To 255)
ReDim EndDays(0 To 255)
Dim SDIndex As Integer ' Index of the start day array
Dim EDIndex As Integer ' Index of the end day array
' If Start of month is start of a new holiday then set it
If (IsLastCellHoliday And Not IsStartHolidayContinuation) Then StartDays(0) = GetMonthName(rRow.Cells(1), MonthNameRow)
' If start of month is a holiday then set index to the second "StartHoliday" line
SDIndex = IIf(IsStartHolidayContinuation Or IsLastCellHoliday, 1, 0) ' Keep first row Empty if start of month is holiday
EDIndex = 0
' Loop through all cells in the month for the person
Dim i As Integer
For i = SDIndex + 1 To rRow.Columns.Count
Dim rCell As Range
Set rCell = rRow.Cells(i)
IsCurrentCellHoliday = rCell.Value = 1 'Check if current cell is a holiday
' If the current cell is different to the last cell then we need to do something
If IsCurrentCellHoliday <> IsLastCellHoliday Then
If IsCurrentCellHoliday Then
StartDays(SDIndex) = GetMonthName(rCell, MonthNameRow)
SDIndex = SDIndex + 1
' Check if the first day of the next month is a holiday, if not then today is the last day
If rCell.Column = MonthRange.Columns(MonthRange.Columns.Count).Column And rCell.Offset(0, 1).Value <> 1 Then
EndDays(EDIndex) = GetMonthName(rRow.Cells(i), MonthNameRow)
EDIndex = EDIndex + 1
End If
Else
EndDays(EDIndex) = GetMonthName(rRow.Cells(i - 1), MonthNameRow)
EDIndex = EDIndex + 1
End If
End If
IsLastCellHoliday = IsCurrentCellHoliday
Next
Dim ReturnStrings() As String
Dim ReturnIndex As Integer
If (ShowStartMonth) Then
ReturnStrings = StartDays
ReturnIndex = SDIndex
Else
ReturnStrings = EndDays
ReturnIndex = EDIndex
End If
Dim returnString As String
returnString = IIf(Len(ReturnStrings(0)) = 0, " - ", ReturnStrings(0))
Dim j As Integer
For j = LBound(ReturnStrings) + 1 To ReturnIndex - 1
returnString = returnString & vbNewLine & ReturnStrings(j)
Next
GetHoliday = returnString
End Function
Private Function GetMonthName(cell As Range, MonthRow As Range) As String
Dim rMonth As Range
Set rMonth = Intersect(cell.EntireColumn, MonthRow.EntireRow)
End Function
Public Function GetMonthRange(iMonth As Integer, MonthNameRow As Range) As Range
Set MonthNameRow = Intersect(MonthNameRow.EntireRow, MonthNameRow.Worksheet.UsedRange)
Dim startCell As Range
Dim endCell As Range
Dim rCell As Range
For Each rCell In MonthNameRow.Cells
If IsDate(rCell.Value) Then
If month(CDate(rCell.Value)) = iMonth Then
If startCell Is Nothing Then
Set startCell = rCell
ElseIf rCell.Column < startCell.Column Then
Set startCell = rCell
End If
If endCell Is Nothing Then
Set endCell = rCell
ElseIf rCell.Column > endCell.Column Then
Set endCell = rCell
End If
End If
End If
Next
Set GetMonthRange = Range(startCell.Address & ":" & endCell.Address).EntireColumn
Dim sAddress As String
sAddress = GetMonthRange.Address
End Function

Related

VBA/Formula, Mapping among sheets

I have a code that I am having trouble running on excel 2013. 2010 works fine.
I've been contemplating just doing formulas because I cannot get this to work.
Here is the logic
Only fill values in sheet X if this condition exists: In Sheet A , If column a = value 1 , value 2, or value 3
and column b <> value 4, <> value 5
Then lookup headers from sheet X into sheet Y. These headers will be in sheet Y column c.
for the headers that are matched to sheet Y col c, find like data of sheet X. column c, and sheet Y. column d. Going to use these as lookup for next column in sheet Y. For where there are mismatches use 'OTHERS' as value.
for matched headers/columns return sheet Y column e (value) and multiply by sheet X. column d. minus one.
return all these values to sheet a where the headers are like.
Sheet X (below formulas in stack and overflow cols would actually be calculated)
+-------------+-------------+------------+-------+-----------------+-------------+
| conditions | condition 2 | currency | value | stack | overflow |
+-------------+-------------+------------+-------+-----------------+-------------+
| value 1 | value 10 | USD | 100 | 100 * (.75 - 1) | |
| value 2 | value 7 | XRP | 200 | 200 * (.50 - 1) | |
| value 3 | value 8 | USD | 300 | | 300*(.65-1) |
| value 1 | value 9 | XRP | 400 | | 400*(.24-1) |
+-------------+-------------+------------+-------+-----------------+-------------+
Sheet Y
+----------+----------+--------+
| header | currency | value |
+----------+----------+--------+
| stack | USD | .75 |
| stack | OTHER | .50 |
| overflow | USD | .65 |
| overflow | OTHER | .24 |
+----------+----------+--------+
This code gets slow at the for loop at the bottom of the code.
Here is my code:
Public Sub calc()
Application.ScreenUpdating = False
Dim i As Long, thisScen As Long, nRows As Long, nCols As Long
Dim stressWS As Worksheet
Set stressWS = Worksheets("EQ_Shocks")
Unprotect_Tab ("EQ_Shocks")
nRows = lastWSrow(stressWS)
nCols = lastWScol(stressWS)
Dim readcols() As Long
ReDim readcols(1 To nCols)
For i = 1 To nCols
readcols(i) = i
Next i
Dim eqShocks() As Variant
eqShocks = colsFromWStoArr(stressWS, readcols, False)
'read in database columns
Dim dataWs As Worksheet
Set dataWs = Worksheets("database")
nRows = lastrow(dataWs)
nCols = lastCol(dataWs)
Dim dataCols() As Variant
Dim riskSourceCol As Long
riskSourceCol = getWScolNum("condition 2", dataWs)
ReDim readcols(1 To 4)
readcols(1) = getWScolNum("value", dataWs)
readcols(2) = getWScolNum("currency", dataWs)
readcols(3) = getWScolNum("condition", dataWs)
readcols(4) = riskSourceCol
dataCols = colsFromWStoArr(dataWs, readcols, True)
'read in scenario mappings
Dim mappingWS As Worksheet
Set mappingWS = Worksheets("mapping_ScenNames")
Dim stressScenMapping() As Variant
ReDim readcols(1 To 2): readcols(1) = 1: readcols(2) = 2
stressScenMapping = colsFromWStoArr(mappingWS, readcols, False, 2) 'include two extra columns to hold column number for IR and CR shocks
For i = 1 To UBound(stressScenMapping, 1)
stressScenMapping(i, 3) = getWScolNum(stressScenMapping(i, 2), dataWs)
If stressScenMapping(i, 2) <> "NA" And stressScenMapping(i, 3) = 0 Then
MsgBox ("Could not find " & stressScenMapping(i, 2) & " column in database")
Exit Sub
End If
Next i
ReDim readcols(1 To 4): readcols(1) = 1: readcols(2) = 2: readcols(3) = 3: readcols(4) = 4
stressScenMapping = filterOut(stressScenMapping, 2, "NA", readcols)
'calculate stress and write to database
Dim thisEqShocks() As Variant
Dim keepcols() As Long
ReDim keepcols(1 To UBound(eqShocks, 2))
For i = 1 To UBound(keepcols)
keepcols(i) = i
Next i
Dim thisCurrRow As Long
For thisScen = 1 To UBound(stressScenMapping, 1)
thisEqShocks = filterIn(eqShocks, 2, stressScenMapping(thisScen, 1), keepcols)
If thisEqShocks(1, 1) = "#EMPTY" Then
For i = 2 To nRows
If dataCols(i, 4) <> "value 4" And dataCols(i, 4) <> "value 5" And (dataCols(i, 1) = "value 1" Or dataCols(i, 1) = "value 2") Then
dataWs.Cells(i, stressScenMapping(thisScen, 3)).value = "No shock found"
End If
Next i
Else 'calculate shocks
Call quicksort(thisEqShocks, 3, 1, UBound(thisEqShocks, 1))
For i = 2 To nRows
If dataCols(i, 4) <> "value 5" And dataCols(i, 4) <> "value 6" And (dataCols(i, 1) = "value 1" Or dataCols(i, 1) = "value 2" Or dataCols(i, 1) = "value 3") Then
thisCurrRow = findInArrCol(dataCols(i, 3), 3, thisEqShocks)
If thisCurrRow = 0 Then 'could not find currency so use generic shock
thisCurrRow = findInArrCol("OTHERS", 3, thisEqShocks)
End If
If thisCurrRow = 0 Then
dataWs.Cells(i, stressScenMapping(thisScen, 3)).value = "No shock found"
Else
dataWs.Cells(i, stressScenMapping(thisScen, 3)).value = Replace(dataCols(i, 2), "-", 0) * (thisEqShocks(thisCurrRow, 4) - 1)
End If
End If
Next i
End If
Next thisScen
Application.ScreenUpdating = True
End Sub
I read a rubber duck post and was inspired to turn this from script like code into code like code. (i have use type instead of private pVar sorry ducky for failing you in this one LOL) My comment below still stands though. I tested on 5000 cells and this coded executed in under a second on average.
INSIDE THIS WORKBOOK:
Option Explicit
Sub main()
Dim startTime As Long
startTime = Tests.GetTickCount
Dim ws As Worksheet
Set ws = Sheets("Sheet1")
Dim lastRow As Integer
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).row
With ws.Sort
.SortFields.Clear
.SortFields.Add Key:=Range("A4:A" & lastRow), Order:=xlAscending
.SortFields.Add Key:=Range("B4:B" & lastRow), Order:=xlAscending
.Header = xlYes
.SetRange Range("A4:F" & lastRow)
.Apply
End With
Dim colOfItems As Collection
Set colOfItems = New Collection
Dim cell As Range
For Each cell In ws.Range("A4:A" & lastRow)
Dim item As Items
If cell.value <> 1 And cell.value <> 2 And cell.value <> 3 Then
Exit For
Else
Set item = Factories.newItem(ws, cell.row)
colOfItems.Add item
Set item = Nothing
End If
Next cell
Set ws = Nothing
Dim wsTwo As Worksheet
Set wsTwo = Sheets("Sheet2")
Dim row As Integer
row = 4
Dim itemcheck As Items
For Each itemcheck In colOfItems
If Tests.conditionTwoPass(itemcheck) Then
With wsTwo
.Range("A" & row) = itemcheck.conditionOne
.Range("B" & row) = itemcheck.conditionTwo
.Range("C" & row) = itemcheck.CurrencyType
.Range("D" & row) = itemcheck.ValueAmount
.Range("E" & row) = itemcheck.Stack
.Range("F" & row) = itemcheck.OverFlow
End With
row = row + 1
End If
Next itemcheck
Dim endTime As Long
endTime = Tests.GetTickCount
Debug.Print endTime - startTime
End Sub
INSIDE MODULE NAMED FACTORIES:
Public Function newItem(ByRef ws As Worksheet, ByVal row As Integer) As Items
With New Items
.conditionOne = ws.Range("A" & row)
.conditionTwo = ws.Range("B" & row)
.CurrencyType = ws.Range("C" & row)
.ValueAmount = ws.Range("D" & row)
.Stack = ws.Range("E" & row)
.OverFlow = ws.Range("F" & row)
Set newItem = .self
End With
End Function
INSIDE MODULE NAMED TESTS:
Public Declare Function GetTickCount Lib "kernel32" () As Long
Function conditionTwoPass(ByVal itemcheck As Items) As Boolean
conditionTwoPass = False
If itemcheck.conditionTwo <> 4 And itemcheck.conditionTwo <> 5 Then
conditionTwoPass = True
End If
End Function
INSIDE CLASS MODULE NAMED ITEMS:
Private pConditionOne As Integer
Private pConditionTwo As Integer
Private pCurrencyType As String
Private pValueAmount As Integer
Private pStack As String
Private pOverflow As String
Public Property Let conditionOne(ByVal value As Integer)
pConditionOne = value
End Property
Public Property Get conditionOne() As Integer
conditionOne = pConditionOne
End Property
Public Property Let conditionTwo(ByVal value As Integer)
pConditionTwo = value
End Property
Public Property Get conditionTwo() As Integer
conditionTwo = pConditionTwo
End Property
Public Property Let CurrencyType(ByVal value As String)
If value = "USD" Then
pCurrencyType = value
Else
pCurrencyType = "OTHER"
End If
End Property
Public Property Get CurrencyType() As String
CurrencyType = pCurrencyType
End Property
Public Property Let ValueAmount(ByVal value As Integer)
pValueAmount = value
End Property
Public Property Get ValueAmount() As Integer
ValueAmount = pValueAmount
End Property
Public Property Let Stack(ByVal value As String)
pStack = value
End Property
Public Property Get Stack() As String
Stack = pStack
End Property
Public Property Let OverFlow(ByVal value As String)
pOverflow = value
End Property
Public Property Get OverFlow() As String
OverFlow = pOverflow
End Property
Public Property Get self() As Items
Set self = Me
End Property
Here is a formula only solution, using a helper column to lookup 2 criteria (header & column) at once:
Add a helper column in Sheet Y column E like shown below. Use the following formula in E:
=C:C&D:D
Use the following formula in E2 and copy it down and right:
=IF(AND(OR($A:$A="value 1",$A:$A="value 2",$A:$A="value 3"),$B:$B<>"value 4",$B:$B<>"value 5"),$D:$D*(IFNA(VLOOKUP(E$1&$C:$C,'Sheet Y'!$E:$F,2,FALSE),VLOOKUP(E$1&"OTHER",'Sheet Y'!$E:$F,2,FALSE))-1),"")
The calculation part of the formula
$D:$D*(IFNA(VLOOKUP(E$1&$C:$C,'Sheet Y'!$E:$F,2,FALSE),VLOOKUP(E$1&"OTHER",'Sheet Y'!$E:$F,2,FALSE))-1)
looks up a combination of "header" and column C in the helper column. If it finds the combination it returns its value if not it looks up a combination of "header" and "OTHER" and returns its value to perform the calculation.
The IF(AND(OR part is the condition of your point 1 in your question.
the loop gets slow because it's too much interaction between excel and VBA. Put the entire loop within the VBA , filling in the 2D array and dump the result out like so:
Sheets(1).cells(1,1).Resize(Ubound(arr2D),Ubound(arr2D,2)).value2 = arr2D
on the contrary, quicksort call is probably slow in VBA, so it may make sense to sort in Excel AFTER the array is pasted back to a sheet using native Range.Sort method.

VBA Script takes 1000X longer to run when a second work book isopen (yet is not used)

All -
I am writing a script to help me process some data. I am testing the code below in a workbook, called "smallWorkbook" that has only a couple thousand rows of data. I run the code from smallWorkbook on data that is in smallWorkbook. The code runs in .06 seconds (after selecting ranges).
I also have a 50 MB workbook with hundres of thousands of rows across 30 worksheets called "largeWorkbook". If I simply have largeWorkbook open at the same time as smallWorkbook, and run the code exactly as described above, the code takes 60 seconds to run!
Can someone describe why running the code from smallWorkbook on data in smallWorkbook while largeWorkBook is open would slow the run time so badly? Does it have something to do with the way my code is written or my ranges are qualified? I am not so good with these subtleties.
Thanks for reading.
Here is the code:
Option Explicit
'*************************************************
'*************************************************
Sub MinInGroup()
'MIN IN GROUP
'Use this sub to add a table that contains unique IDs as rows
'and min quantities for each group that corresponds to unique ID
'Table is added to worksheet named sandbox" hard-coded in this routine
'Future work:
'1)add ability to selct from another workbook to create
'standalone app (selecting a range in another wrkbk requires custom user form, spaces in wrkbk or sheet
'names makes trouble).
'2) dont hard code headers
'3) array sizing - 1000 OK for this use case
'4) Remove lines that get minimum of columnn next to one choosen as this is not nominal case
'EXAMPLE:
'calling MinInGroup on range GRP range and QTY range:
' GRP|QTY
' AA | 5
' AA | 9
' AA | 2
' BB | 1
' BB | 5
' CC | 26
' CC | 70
' Returns
' GRP|MIN
' AA | 2
' BB | 1
' CC | 26
Dim grpNameRng As Range 'range containing sub groups (ie AA,BB,CC)
Dim valRng As Range 'range containing values to find min on
Set grpNameRng = Application.InputBox("Select group range (no headers)", "Select Group Range", Type:=8)
Set valRng = Application.InputBox("Select value range (no headers)", "Select Value Range", Type:=8)
'***************************************
'For dev only, delete when done
Dim StartTime As Double
Dim SecondsElapsed As Double
StartTime = Timer
'***************************************
'Make sure same length
If grpNameRng.Count <> valRng.Count Then
MsgBox ("Ranges must have same number of elements. Exiting.")
Exit Sub
End If
Dim i As Long, j As Long
Dim grpStartIndex As Long 'row index of start of new group
Dim grpNameArry(1000) As Variant 'Array of unique grps. In EX above grpNameArry = {AA, BB, CC}
Dim minValArry(1000) As Variant 'Array of min vals each group. minValArry = {2, 1, 26}
'Turn off screen updates
Application.ScreenUpdating = False
j = 1
grpStartIndex = 1
grpNameArry(j) = grpNameRng(1)
For i = 1 To grpNameRng.Count
If (grpNameRng(i)) <> grpNameRng(i + 1) Then 'i is end of current group
'calc MIN val for current group
minValArry(j) = Application.WorksheetFunction.Min(Range(valRng.Cells(grpStartIndex), valRng.Cells(i)))
grpStartIndex = i + 1
j = j + 1
grpNameArry(j) = grpNameRng(i + 1)
End If
Next i
'Write results to a range in "sandbox" sheet
With Worksheets("sandbox")
'Write headers
.Range("D3").Value = "LC"
.Range("D3").Font.Bold = True
.Range("E3").Value = "Min Msy"
.Range("E3").Font.Bold = True
.Range("F3").Value = "Min Msu"
.Range("F3").Font.Bold = True
For i = 1 To j - 1
.Range(.Cells(3 + i, 5), .Cells(3 + i, 5)).Value = minValArry(i)
.Range(.Cells(3 + i, 4), .Cells(3 + i, 4)).Value = grpNameArry(i)
Next i
End With
'Turn off screen updates
Application.ScreenUpdating = True
'***************************************
'For dev only, delete when done
'Determine how many seconds code took to run
SecondsElapsed = Round(Timer - StartTime, 2)
MsgBox ("Code ran in " & SecondsElapsed & " seconds.")
'***************************************
End Sub

Excel VBA - Week Ending Date

Column B is my data - if there is a date value in column B please return week ending date in column C. Need a VBA code to accomplish this
Column B Column C
11/9/2016 11/11/2016
11/8/2016 11/11/2016
4/4/2017 4/7/2017
(blank) (blank)
3/28/2017 3/31/2017
Below is all I could get, but it's not any good.
Dim FirstDayInWeek, LastDayInWeek As Variant
Dim dtmDate As Date
dtmDate = Range("B2:B")
LastDayInWeek = dtmDate - Weekday(dtmDate, vbUseSystem) + 7
MsgBox LastDayInWeek
I replied to your comment on how to find the start date of week from a given date?, but here it is as an answer:
Function ReturnDate(DateRange As Date, Optional DayInWeek = 1) As Date
ReturnDate = DateRange - Weekday(DateRange, vbUseSystem) + DayInWeek
End Function
=ReturnDate(A1) gives Monday
=ReturnDate(A1,2) gives Tuesday
.
=ReturnDate(A1,5) gives Friday < --- This is the one you're after.
=ReturnDate(A1,7) gives Sunday.
A blank cell will give 01/01/1900, but you could add a check for that or format the cell not to show 0.
Perhapse you could take an approach like the one below
Sub ReturnWeekEndDate()
Dim InpRng As Range
Dim i As Long
Set InpRng = ActiveSheet.Range("A2:B5")
For i = 1 To InpRng.Rows.Count
If IsDate(InpRng.Cells(i, 1).Value) And IsDate(InpRng.Cells(i, 2).Value) Then
InpRng.Cells(i, 1).Offset(0, 2) = InpRng.Cells(i, 1).Value - Weekday(InpRng.Cells(i, 1).Value, vbUseSystem) + 7
End If
Next i
End Sub
Give this a try:
Sub INeedADate()
Dim i As Long, N As Long, r As Range, Bigr As Range
N = Cells(Rows.Count, "B").End(xlUp).Row
For i = 1 To N
Set r = Cells(i, "B")
If IsDate(r.Value) Then
addy = r.Address
r.Offset(0, 1).Value = Evaluate(addy & "-WEEKDAY(" & addy & ",3)+IF(WEEKDAY(" & addy & ",3)>4,11,4)")
End If
Next i
End Sub
This is similar to using the worksheet formula:
=B1-WEEKDAY(B1,3)+IF(WEEKDAY(B1,3)>4,11,4)
Or try this...
Sub GetFridayDate()
Dim LastDayInWeek As Date
Dim Rng As Range, Cell As Range
Dim lr As Long
lr = Cells(Rows.Count, 2).End(xlUp).Row
Set Rng = Range("B2:B" & lr)
For Each Cell In Rng
If IsDate(Cell.Value) Then
LastDayInWeek = Cell + 8 - Weekday(Cell, vbFriday)
Cell.Offset(0, 1) = LastDayInWeek
End If
Next Cell
End Sub
You said that this would be part of a process...so, just call the function as I have shown, and you're golden! BOOM!
Sub FindEndOfWeek_Test()
Call FindEndOfWeek(ActiveSheet, 1, 2, 6, 1)
End Sub
Function FindEndOfWeek(Sht As Worksheet, KnownDate_Column As Integer, _
EndOfWeek_Column, EndOfWeek As Integer, _
StartingRow As Long)
' This function takes in a spreadsheet, and and determines the date at the end
' of the week, based on known parameters being passed into the function.
'
Dim a As Long
Dim LastRow As Long
Dim EvalDate As Date
Dim NewDate As Date
' Determine the last row of the column you are working with
LastRow = Sht.Cells(Sht.Rows.Count, KnownDate_Column).End(xlUp).Row
' Loop through your entire spreadsheet to determine the end of the week for all rows
For a = StartingRow To LastRow
If IsDate(Sht.Cells(a, KnownDate_Column).Value) = True Then
NewDate = Sht.Cells(a, KnownDate_Column).Value
EvalDay = Weekday(NewDate)
' Determine the known date day of the week, and add accordingly.
If EvalDay < EndOfWeek Then
Sht.Cells(a, EndOfWeek_Column).Value = NewDate + (EndOfWeek - EvalDay)
ElseIf EvalDay > EndOfWeek Then
Sht.Cells(a, EndOfWeek_Column).Value = NewDate + (7 - EvalDay + EndOfWeek)
Else
Sht.Cells(a, EndOfWeek_Column).Value = NewDate
End If
End If
Next a
End Function
I think no need for vba, you use below formula:
=IF(B2<>"",B2+(7-WEEKDAY(B2,16)),"")
If you really need VBA code for this problem, which I did, you can convert the excel formula into a one-line solution like so:
WeekendingDate = Date + 7 - WorksheetFunction.Weekday(Date + 7 - 6)

Select Cells in a range without deselecting other cells based on a value

I want to select cells on a range based on a value just like (ctrl + mouse click)
If the values of column G contains the word Wage and Sum it will be selected. In the picture's case, 5/15 WAGES SUMMARY and 4/15 WAGES SUMMARY will be selected.
If the count of selection is only 1, then its amount(column j) will be displayed.
If the count of selection are 2 or more, then the dates will be compared. In the picture's case, it is (5/15 and 4/15). The dates will be compared to get the highest. If there are two or more highest date, the amount will be added, if there's only one, then it will be displayed. Here's my code;
Dim ws1 As Worksheet, wsm As Worksheet
Dim wb1 As Workbook
Dim Loc As Range
Dim crt As Integer, r As Long, c As Long, last As Long
ctr = 0
i = 3
Set wb1 = ThisWorkbook
Set ws1 = wb1.Sheets("SHIPNET102")
Set wsm = wb1.Sheets("MACRO TEMPLATE")
last = ws1.Cells(Rows.Count, "A").End(xlUp).Row
Do Until ws1.Cells(i, 7) = ""
Set Loc = ws1.Cells(i, 7).Find(What:="*WAGE*SUM*")
If Loc Is Nothing Then
i = i + 1
Else
ctr = ctr + 1
i = i + 1
End If
Loop
If ctr > 1 Then
'?
Else
r = Loc.Row
c = Loc.Column + 3
wsm.Cells(5, 3) = ws1.Cells(r, c)
End If
So far that's my outcome. It only counts number of cell that contains the word Wage and Sum and display the amount if the count is only 1. Any help would be appreciated.
I added some lines for you. Please let me know if it doesn't work.
Dim ws1 As Worksheet, wsm As Worksheet
Dim wb1 As Workbook
Dim Loc As Range
Dim crt As Integer, r As Long, c As Long, last As Long
Dim CellsToSelect As String
ctr = 0
i = 3
Set wb1 = ThisWorkbook
Set ws1 = wb1.Sheets("sheet2")
Set wsm = wb1.Sheets("Sheet1")
last = ws1.Cells(Rows.Count, "A").End(xlUp).Row
Do Until ws1.Cells(i, 7) = ""
Set Loc = ws1.Cells(i, 7).Find(What:="*WAGE*")
If Loc Is Nothing Then
i = i + 1
Else
If CellsToSelect = Empty Then
CellsToSelect = ws1.Cells(i, 7).Address
Else
CellsToSelect = CellsToSelect & "," & ws1.Cells(i, 7).Address
End If
ctr = ctr + 1
i = i + 1
End If
Loop
If ctr > 1 Then
ws1.Range(CellsToSelect).Select
Else
r = Loc.Row
c = Loc.Column + 3
wsm.Cells(5, 3) = ws1.Cells(r, c)
End If
Use the following codes to compare the dates:
Sub Program()
Dim Str As String, str2 As String
Dim MonthS As String, YearS As String
Dim DateS As Date
Str = "12/18 WAGES SUMMARY "
str2 = Trim$(Left$(Str, InStr(Str, " ") - 1))
MonthS = Trim$(Left$(str2, InStr(str2, "/") - 1))
YearS = Trim$(Right$(str2, Len(str2) - InStr(str2, "/")))
DateS = "1/" & MonthS & "/" & YearS
If DateS > "5/8/16" Then
a = 1
Else
a = 2
End If
End Sub
If your column G values can be in a variety of formats ("4/15 WAGE SUMMARY", "WAGE SUMMARY 4/15", etc), then you might want to consider using regular expressions to pull the date from the text.
In VBA, regular expressions can be found in the Microsoft VBScript Regular Expressions library, which you can reference from your project by going to Tools > References in the VB editor.
How to use Regular Expressions (Regex) in Microsoft Excel both in-cell and loops
http://www.regular-expressions.info/vb.html
Once you have the date part of the string extracted, you can convert to a date with the built-in CDate function, and compare two dates to get the newest.
Another issue with the general approach you are using, is that when you repeatedly use Find, you don't save state about cells that were previously examined, which will be necessary for comparing the parsed dates.
I would suggest creating a Function (let's call it GetDate(str as String)) that will take a string parameter and if the string has "WAGE" and "SUMMARY" and a parse-able date, returns the date, otherwise it returns Nothing if the input string does not match.
Insert a column at column H that is filled with the formula =GetDate(G2). Then sort the data by the date in column H descending. Now you can iterate through the rows that match your criteria and sum until no date is found, or the date gets older.

Creating a unique entry for each line item in Excel

I need help in creating a macro in Excel wherein it grabs a certain cell and copies the entire row x number of times depending on the cell's contents.
To make it clear, let's say I have 2 rows:
| Order # | Item | Qty |
| 30001 | bag | 3 |
| 30002 | pen | 1 |
What I want the macro to do is grab the number under the Qty column and copy the entire row and insert a new line with the exact same contents under it. The number of times it does this depends on the number in the Qty cell. Also, it appends a three digit number in the Order # cell to make it a unique reference point. What the end-result should be:
| Order # | Item | Qty |
| 30001-001 | bag | 1 |
| 30001-002 | bag | 1 |
| 30001-003 | bag | 1 |
| 30002-001 | pen | 1 |
It's hard to explain it here but I hope you get the point. Thanks in advance, gurus!
The following code supports blank lines in the middle of the data.
If Qty = 0, it won't write the Item in the output table.
Please insert at least 1 row of data, because it won't work if there is no data :)
Option Explicit
Sub caller()
' Header at Row 1:
' "A1" = Order
' "B1" = Item
' "C1" = Qty
'
' Input Data starts at Row 2, in "Sheet1"
'
' Output Data starts at Row 2, in "Sheet2"
'
' Sheets must be manually created prior to running this program
Call makeTheThing(2, "Sheet1", "Sheet2")
End Sub
Sub makeTheThing(lStartRow As Long, sSheetSource As String, sSheetDestination As String)
Dim c As Range
Dim rOrder As Range
Dim sOrder() As String
Dim sItem() As String
Dim vQty As Variant
Dim sResult() As String
Dim i As Long
' Reads
With ThisWorkbook.Sheets(sSheetSource)
Set rOrder = .Range(.Cells(lStartRow, 1), .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, 1)) ' It will work if there are blank lines in the middle!
i = rOrder.Rows.Count
ReDim sOrder(1 To i)
ReDim sItem(1 To i)
ReDim vQty(1 To i)
i = 1
For Each c In rOrder
sOrder(i) = Trim(c.Text)
sItem(i) = Trim(c.Offset(0, 1).Text)
vQty(i) = c.Offset(0, 2).Value
i = i + 1
Next c
End With
' Processes
sResult = processData(sOrder, sItem, vQty)
' Writes
ThisWorkbook.Sheets(sSheetDestination).Range("A" & lStartRow).Resize(UBound(sResult, 1), UBound(sResult, 2)).Value = sResult
End Sub
Function processData(sOrder() As String, sItem() As String, vQty As Variant) As String()
Dim i As Long
Dim j As Long
Dim k As Long
Dim sResult() As String
j = WorksheetFunction.Sum(vQty) ' That's why vQty had to be Variant!
ReDim sResult(0 To j, 1 To 3)
k = 0
For i = 1 To UBound(sOrder)
For j = 1 To vQty(i)
sResult(k, 1) = sOrder(i) & "-" & Format(j, "000")
sResult(k, 2) = sItem(i)
sResult(k, 3) = "1"
k = k + 1
Next j
Next i
processData = sResult
End Function
I hope it helps you. I had fun making it!
One way: Walk down the qty column inserting as needed then jumping to the next original row;
Sub unwind()
Dim rowCount As Long, cell As Range, order As String, i As Long, r As Long
Set cell = Range("C1")
rowCount = Range("C" & rows.Count).End(xlUp).Row
For i = 1 To rowCount
order = cell.Offset(0, -2).Value
For r = 0 To cell.Value - 1
If (r > 0) Then cell.Offset(r).EntireRow.Insert
cell.Offset(r, 0).Value = 1
cell.Offset(r, -1).Value = cell.Offset(0, -1).Value
cell.Offset(r, -2).Value = order & "-" & Format$(r + 1, "000")
Next
Set cell = cell.Offset(r, 0)
Next
End Sub