I have a sheet where all dates are in the wrong timezone. I need to add one hour to all cells formated as dates, but leave the rest intact.
I found this:
Public Function AddHour(ByVal sTime As String) As String
Dim dt As Date
dt = CDate(sTime)
dt = DateAdd("h", 1, dt)
AddHour = Format(dt, "mm/dd/yy h:nnam/pm")
End Function
Now, how do I find the cells with dates in them?
Sub AddHour(ByVal ThisSheet As Worksheet)...
The code below was modified to apply additional information you provided in your comment below.
Option Explicit
Public Sub AddHour()
' 17 Dec 2017
Const FirstColumn As String = "A" ' set as required
Const LastColumn As String = "AV" ' set as required
Dim Ws As Worksheet
Dim Cf As Long, Cl As Long ' first / last column
Dim Dt As Double
Dim Rl As Long ' last used row (in column C)
Dim R As Long, C As Long
Set Ws = Worksheets("AddHour") ' replace with your sheet's name
Application.ScreenUpdating = False
With Ws
Cf = Columns(FirstColumn).Column
Cl = Columns(LastColumn).Column
For C = Cf To Cl
Application.StatusBar = Cl - C + 1 & " columns remaining"
Rl = .Cells(.Rows.Count, C).End(xlUp).Row
For R = 1 To Rl ' start looking in row 1 (amend if necessary)
With .Cells(R, C)
If IsDate(.Value) Then
Dt = .Value
' add 1 hour if there is a Time value in the date
If Dt - Int(Dt) Then .Value = Dt + (1 / 24)
End If
End With
Next R
Stop
Next C
End With
With Application
.ScreenUpdating = True
.StatusBar = False
End With
End Sub
You still have to replace the worksheet name "AddHour" in the code with whatever name your worksheet really has and specify the first and last columns where your dates are. You can change the first row in which the code starts looking for them.
The code presumes that your dates are "true" dates. You can test this by selecting any cell with a date you want to change and set its cell format temporarily to "General". If the date is a "true" date a number will be displayed instead of the date, like 43086.5046489583. If the display in the cell doesn't change upon reformatting then your dates are "Text" and must be treated differently.
If you are absolutely certain, that all dates in your sheet have to be modified you could loop over all cells in your used range and make the adjustments using your function like so:
Sub ChangeDate()
Dim rngDates As Range
Dim varCounter As Variant
Dim dt As Date
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.Calculation = xlManual
Set rngDates = ThisWorkbook.Worksheets("Tabelle2").UsedRange
'Loop over all cells in range
For Each varCounter In rngDates
'If it's a date, change its value
If IsDate(varCounter.Value) Then
dt = CDate(varCounter.Value)
dt = DateAdd("h", 1, dt)
varCounter.Value = Format(dt, "mm/dd/yy h:nnam/pm")
End If
Next varCounter
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.Calculation = xlAutomatic
End Sub
Depending on the amount of cells in your used range this might not be very performant.
To improve on that we can read your used range into an array and process it in memory like so:
Sub ChangeDate()
Dim varValues As Variant
Dim lngColumns As Long, lngRows As Long
Dim dt As Date
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.Calculation = xlManual
'Read entire range to array
varValues = ThisWorkbook.Worksheets("Tabelle2").UsedRange
'Loop over all "columns"
For lngColumns = 1 To UBound(varValues, 1)
'Loop over all "rows" in that "column"
For lngRows = 1 To UBound(varValues, 2)
If IsDate(varValues(lngColumns, lngRows)) Then
dt = CDate(varValues(lngColumns, lngRows))
dt = DateAdd("h", 1, dt)
varValues(lngColumns, lngRows) = Format(dt, "mm/dd/yy h:nnam/pm")
End If
Next lngRows
Next lngColumns
'Overwrite usedRange with array
ThisWorkbook.Worksheets("Tabelle2").UsedRange = varValues
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.Calculation = xlAutomatic
End Sub
This should be quiet performant regardless of the amount of data you're processing.
It goes without saying that this might not account for everything without having seen your workbook and has to be thoroughly tested.
Related
So I have got 2 listobject that I merge and i'd like to get the data timestamped after they are merged successfully.
It all works out pretty ok, but this little piece of code takes almost 2 minutes to finish.
2020-11-04 10:46:34
2020-11-04 10:48:13
There just has to be a faster way. Any ideas?
(lotarget and losource are declared and assigned listobjects on the same sheet)
Application.ScreenUpdating = false
dtoday = Date
sCreator = Application.UserName
For i = 0 To 350
loTarget.Range(i, 7).Value = dtoday
loTarget.Range(i, 8).Value = sCreator
Next i
Application.ScreenUpdating = True
Column of ListObject
Option Explicit
Sub test()
Application.ScreenUpdating = False
Dim loTarget As ListObject
Set loTarget = Sheet1.ListObjects(1)
Dim dtoday As Date
dtoday = Now ' Now for time, Date for date.
Dim sCreator As String
sCreator = Application.UserName
' For data rows 1 to 350:
' loTarget.DataBodyRange(1, 7).Resize(350).Value = dtoday
' loTarget.DataBodyRange(1, 8).Resize(350).Value = sCreator
' For all data rows:
loTarget.DataBodyRange.Columns(7).Value = dtoday
loTarget.DataBodyRange.Columns(8).Value = sCreator
Application.ScreenUpdating = True
End Sub
You could write all values of a column at once. I assume that loTarget is your listObject. Use something like
Application.Calculation = xlManual
With loTarget.Range
.Range("G2").Resize(.Rows.Count-1, 1).Value = Date
.Range("H2").Resize(.Rows.Count-1, 1).Value = Application.UserName
End With
Application.Calculation = xlAutomatic
I am skipping the top row as this holds likely the column header. Note that "G2" is relative to the table and not the worksheet cell "G2" (unless your table starts as cell "A1").
Maybe speed could also be improved by temporarily deactivating calculation.
I am attempting to write a code to average values in a particular column. I have a table with 5 columns, Column number 2 has the date/time, and column number 5 has a measurement. There are over 500 lines of data, one for each minute of the test, and my goal is to write a code to take all the data and average it down to 15 minute intervals.
I attempted this by starting my active cell on the first line of the table, and inserting a row, offsetting the active cell to take the value of the row below (date/time of minute 15) and move it into the newly inserted row. Then, to average the 15 rows of data for column 5 and input it into the inserted row in column 5. I attempted to do this with an iterative process but I cant get the macro to average the value.
Could someone please assist? - new to VBA, any help is appreciated.
Thanks
'Averages PID Values to 15 min intervals
Sub Make_Data_Box()
' Databoxes_1 Macro
Do Until IsEmpty(ActiveCell)
Dim loc As String
loc = ActiveCell.Value
Dim iter_1 As Integer
iter_1 = 1
Do Until ActiveCell.Value <> ActiveCell.Offset(iter_1, 0).Value
iter_1 = iter_1 + 1
Loop
ActiveCell.EntireRow.Insert
ActiveCell.Offset(iter_1, 1).Select
ActiveCell.Offset(-iter_1, 0).Value = ActiveCell.Value
ActiveCell.Offset(iter_1 - 1, 2).Select
ActiveCell.Value = Application.WorksheetFunction.Average(Range("ActiveCell:ActiveCell - 14"))
' ActiveSheet.Range(ActiveCell) = Application.WorksheetFunction.Average(ActiveSheet.Range("ActiveCell:ActiveCell+14"))
ActiveCell.Offset(-iter_1 + 1, 0).Value = ActiveCell.Value
ActiveCell.Offset(iter_1 + 14, -2).Select
Loop
End Sub
Sub Make_Data_Box()
Dim ws As Worksheet
Dim rngData As Range
Dim rngAvg As Range
Dim idx As Long
Dim AvgCol As Long
Dim DataCol As Long
Dim AveragePeriod As Long
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Set ws = ActiveSheet ' or specify sheet you want
' Get reference to Data Values range, this assume there are Headers in Row 1, Data from Row 2
DataCol = 5 ' Column number containing data
Set rngData = ws.Range(ws.Cells(2, DataCol), ws.Cells(ws.Rows.Count, DataCol).End(xlUp))
' Column to put Average in
AvgCol = 6 ' adjust to place Average where you want it
AveragePeriod = 15 ' Take average of this number of rows
For idx = AveragePeriod To rngData.Rows.Count Step AveragePeriod ' fill every AveragePeriod cell with average
rngData.EntireRow.Cells(idx, AvgCol) = _
WorksheetFunction.Average(rngData.Cells(idx, 1).Offset(1 - AveragePeriod, 0).Resize(AveragePeriod, 1))
Next
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Or, if you want to insert a row for the AVERAGES, work from the bottom up
Sub Make_Data_Box2()
Dim ws As Worksheet
Dim rngData As Range
Dim rngAvg As Range
Dim idx As Long
Dim AvgCol As Long
Dim DataCol As Long
Dim AveragePeriod As Long
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Set ws = ActiveSheet ' or specify sheet you want
' Get reference to Data Values range, this assume there are Headers in Row 1, Data from Row 2
DataCol = 5 ' Column number containing data
Set rngData = ws.Range(ws.Cells(2, DataCol), ws.Cells(ws.Rows.Count, DataCol).End(xlUp))
' Column to put Average in
AvgCol = 6 ' adjust to place Average where you want it
AveragePeriod = 15 ' Take average of this number of rows
For idx = (rngData.Rows.Count \ 15) * 15 To 1 Step -AveragePeriod ' fill every 15th cell with average
With rngData.Cells(idx, 1)
.EntireRow.Insert
.Offset(-1, AvgCol - DataCol) = _
WorksheetFunction.Average(.Offset(-AveragePeriod, 0).Resize(AveragePeriod, 1))
End With
Next
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
let's say i have a worksheet with 5 columns and ~30,000 - two of those columns are timestamp formatted. both the TS columns have ~300 blank cells, which i would like to populate with a dummy TS value (1900-01-01 00:00:00) for later filtering. if i inserted an extra column for an IF statement, the formula would look like this, of course:
=IF(B2="","1900-01-01 00:00:00",B2)
however, i would rather use a macro to loop through both TS columns (let's define that range as B2:B30000, C2:C30000).
any help is much appreciated. thanks!
Hopefully it will solve your issue.
Option Explicit
Sub Fill_In_Value()
'Turn off following processes to speed up code
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
Dim ws As Worksheet
Dim i As Long
Dim lRow As Long
Dim Column1 As Long
Dim Column2 As Long
Column1 = 1 '1 = To Column A, this setup is easier to change the column later on
Column2 = 2 '2 = To Column B
Set ws = ActiveWorkbook.Worksheets("Sheet1") 'Set the worksheetname
lRow = ws.Cells(Rows.Count, "A").End(xlUp).Row 'Find the last row in Column A
For i = 2 To lRow 'Loop from row 2 to last row
If ws.Cells(i, Column1) = "" Or ws.Cells(i, "B") = "" Then 'If any of the column A or column B is empty then:
If ws.Cells(i, Column1) = "" Then 'If column A have blank value then:
ws.Cells(i, Column1).Value = "1900-01-01 00:00:00" 'Add dummy value
If ws.Cells(i, Column2) = "" Then 'If column A have blank value, also check column B.
ws.Cells(i, Column2).Value = "1900-01-01 00:00:00" 'Add dummy value
End If
ElseIf ws.Cells(i, Column2) = "" Then 'If Column A is not blank, B should be blank
ws.Cells(i, Column2).Value = "1900-01-01 00:00:00"
End If
End If
Next i
'Turn on following processes to get back to normal state
Application.DisplayAlerts = True
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
MsgBox ("Complete") 'Message box to show when code is finished
End Sub
Faster version
Sub Fill_In_Value2()
'Turn off following processes to speed up code
Application.DisplayAlerts = False
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
Dim ws As Worksheet
Dim i As Long
Dim lRow1 As Long
Dim lRow2 As Long
Dim Column1 As Long
Dim Column2 As Long
Column1 = 1 '1 = To Column A, this setup is easier to change the column later on
Column2 = 2 '2 = To Column B
Set ws = ActiveWorkbook.Worksheets("Sheet1") 'Set the worksheetname
lRow1 = ws.Cells(Rows.Count, Column1).End(xlUp).Row 'Find the last row in Column A
lRow2 = ws.Cells(Rows.Count, Column2).End(xlUp).Row 'Find the last row in Column B
ws.Range(Cells(2, Column1), Cells(lRow1, Column1)).SpecialCells(xlCellTypeBlanks).Value = "1900-01-01 00:00:00"
ws.Range(Cells(2, Column2), Cells(lRow2, Column2)).SpecialCells(xlCellTypeBlanks).Value = "1900-01-01 00:00:00"
'Turn on following processes to get back to normal state
Application.DisplayAlerts = True
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
MsgBox ("Complete") 'Message box to show when code is finished
End Sub
First I'm open to do this with an other angle.
I want to count the total hours of work hours estimated, see sheet2. In another sub I've calculated the total work hours (timer tot) with worksheetfunction.sum and timer FRJ/HET with worksheetfunction.sumif. This code doesn't consider overlapping days which means if the dates intersect each other it will calculate 8*2(3,4,5...) (8 hours is average workday in Norway) instead of 8 hours per workday. This will mess up the total amount of time estimated and posibly we will estimate more hours per day than 24 hours :D
I've started this code underneath which I will use to substract the total amount of time and total amout for FRJ and HET.
Code:
Sub Overlapping_WorkDays()
Dim rng_FRJ_HET As Range
Dim cell_name As Range
Dim startDateRng As Range
Dim endDateRng As Range
Set rng_FRJ_HET = Sheet1.Range("A8", Sheet1.Range("A8").End(xlDown))
Set startDateRng = Sheet1.Range("D8", Sheet1.Range("D8").End(xlDown))
Set endDateRng = Sheet1.Range("E8", Sheet1.Range("E8").End(xlDown))
For Each cell_name In rng_FRJ_HET
If cell_name = "FRJ" Then
'Count Overlapping networkdays for FRJ
Elseif cell_name = "HET" Then
'Count Overlapping networkdays for HET
End If
Next cell_name
End Sub
Sheet1 screenshot
Sheet2 screenshot
All you need to do is loop through all the date ranges and count them if they haven't already been counted. A Dictionary from the Microsoft Scripting Runtime is well suited for this (you'll need to add a reference in Tools->References).
Function TotalWorkDays(Optional category As String = vbNullString) As Long
Dim lastRow As Long
With Sheet1
lastRow = .Cells(.Rows.Count, 4).End(xlUp).Row
Dim usedDates As Scripting.Dictionary
Set usedDates = New Scripting.Dictionary
Dim r As Long
'Loop through each row with date ranges.
For r = 8 To lastRow
Dim day As Long
'Loop through each day.
For day = .Cells(r, 4).Value To .Cells(r, 5).Value
'Check to see if the day is already in the Dictionary
'and doesn't fall on a weekend.
If Not usedDates.Exists(day) And Weekday(day, vbMonday) < 6 _
And (.Cells(r, 1).Value = category Or category = vbNullString) Then
'Haven't encountered the day yet, so add it.
usedDates.Add day, vbNull
End If
Next day
Next
End With
'Return the count of unique days.
TotalWorkDays = usedDates.Count
End Function
Note that this will work for any arbitrary category found in column 1, or all categories combined if it isn't passed an argument. Sample usage:
Sub Usage()
Debug.Print TotalWorkDays("HET") 'Sample data prints 55
Debug.Print TotalWorkDays("FRJ") 'Sample data prints 69
Debug.Print TotalWorkDays 'Sample data prints 69
End Sub
You can convert this to late bound (and skip adding the reference) by replacing these two lines...
Dim usedDates As Scripting.Dictionary
Set usedDates = New Scripting.Dictionary
...with:
Dim usedDates As Object
Set usedDates = CreateObject("Scripting.Dictionary")
I think if I were doing this, I'd use the Collection object, as it'd save converting names and dates to index id's.
You could create a main collection of names and, for each name, a sub collection of dates whose key is Excel's date serial number. This would make it easy to store the 'used days' and you could either acquire the total day count by using the .Count property or loop through the collection to aggregate a specific Oppgave.
The code would be straight forward as shown below. You could just put this in a module:
Option Explicit
Private mNames As Collection
Public Sub RunMe()
ReadValues
'Get the total days count
Debug.Print GetDayCount("FRJ")
'Or get the days count for one Oppgave
Debug.Print GetDayCount("FRJ", "Malfil tegning form")
End Sub
Private Sub ReadValues()
Dim v As Variant
Dim r As Long, d As Long
Dim item As Variant
Dim dates As Collection
With Sheet1
v = .Range(.Cells(8, "A"), .Cells(.Rows.Count, "A").End(xlUp)).Resize(, 5).Value2
End With
Set mNames = New Collection
For r = 1 To UBound(v, 1)
'Acquire the dates collection for relevant name
Set dates = Nothing: On Error Resume Next
Set dates = mNames(CStr(v(r, 1))): On Error GoTo 0
'Create a new dates collection if it's a new name
If dates Is Nothing Then
Set dates = New Collection
mNames.Add dates, CStr(v(r, 1))
End If
'Add new dates to the collection
For d = v(r, 4) To v(r, 5)
On Error Resume Next
dates.Add v(r, 2), CStr(d)
On Error GoTo 0
Next
Next
End Sub
Private Function GetDayCount(namv As String, Optional oppgave As String) As Long
Dim dates As Collection
Dim v As Variant
Set dates = mNames(namv)
If oppgave = vbNullString Then
GetDayCount = dates.Count
Else
For Each v In dates
If v = oppgave Then GetDayCount = GetDayCount + 1
Next
End If
End Function
Dictionary approach should be the fastest.
But if your data are not that big you may want to adopt a "string" approach like follows
Function CountWorkingDays(key As String) As Long
Dim cell As Range
Dim iDate As Date
Dim workDates As String
On Error GoTo ExitSub
Application.EnableEvents = False
With Sheet1
With .Range("E7", .Cells(.Rows.Count, "A").End(xlUp))
.AutoFilter field:=1, Criteria1:=key
For Each cell In Intersect(.Offset(1).Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible), .Columns(1))
For iDate = cell.Offset(, 3) To cell.Offset(, 4)
If Weekday(iDate, vbMonday) < 6 Then
If InStr(workDates, cell.value & iDate) <= 0 Then workDates = workDates & cell.value & iDate
End If
Next iDate
Next cell
End With
End With
CountWorkingDays = UBound(Split(workDates, key))
ExitSub:
Sheet1.AutoFilterMode = False
Application.EnableEvents = True
End Function
that you can use in your code as follows
sht2.Cells(2, 7) = CountWorkingDays("FRJ")
sht2.Cells(2, 8) = CountWorkingDays("HET")
As far as I know there is no direct formula to get overlap dates. My approach will be different from yours.
For each unique value in rng_FRJ_HET (i.e. only FRJ and HET as per e.g.)
Create an array with first date and last date
Mark array index with 1 for each date in range start and end date
Sum the array to get actual number of days
Next
So if the dates are repeated still they will mark as 1 in the array for that date.
=====================Added the code=== This will do for any number of names.
Option Explicit
Dim NameList() As String
Sub Overlapping_WorkDays()
Dim rng_FRJ_HET As Range
Dim cell_name As Range
Dim startDateRng As Range
Dim endDateRng As Range
Dim uniqueNames As Range
Dim stDate As Variant
Dim edDate As Variant
Dim Dates() As Integer
Set rng_FRJ_HET = Sheet1.Range("A8", Sheet1.Range("A8").End(xlDown))
Set startDateRng = Sheet1.Range("D8", Sheet1.Range("D8").End(xlDown))
Set endDateRng = Sheet1.Range("E8", Sheet1.Range("E8").End(xlDown))
stDate = Application.WorksheetFunction.Min(startDateRng)
edDate = Application.WorksheetFunction.Max(endDateRng)
ReDim NameList(0)
NameList(0) = ""
For Each cell_name In rng_FRJ_HET
If IsNewName(cell_name) Then
ReDim Dates(stDate To edDate + 1)
MsgBox cell_name & " worked for days : " & CStr(GetDays(cell_name, Dates))
End If
Next cell_name
End Sub
Private Function GetDays(ByVal searchName As String, ByRef Dates() As Integer) As Integer
Dim dt As Variant
Dim value As String
Dim rowIndex As Integer
Const COL_NAME = 1
Const COL_STDATE = 4
Const COL_EDDATE = 5
Const ROW_START = 8
Const ROW_END = 19
With Sheet1
For rowIndex = ROW_START To ROW_END
If searchName = .Cells(rowIndex, COL_NAME) Then
For dt = .Cells(rowIndex, COL_STDATE).value To .Cells(rowIndex, COL_EDDATE).value
Dates(CLng(dt)) = 1
Next
End If
Next
End With
GetDays = WorksheetFunction.Sum(Dates)
End Function
Private Function IsNewName(ByVal searchName As String) As Boolean
Dim index As Integer
For index = 0 To UBound(NameList)
If NameList(index) = searchName Then
IsNewName = False
Exit Function
End If
Next
ReDim Preserve NameList(0 To index)
NameList(index) = searchName
IsNewName = True
End Function
There are so many ways to delete a whole row based on a blank cell in specific column. What I want to know is which is the fastest way to accomplish this task in terms of Excel speed. I have a sheet with about 39,000 original rows of data which then becomes 21,000 rows after I run the code below. The issue is the chunk of code takes almost 60 seconds to return. While I know CPU and such is a factor, but lets assume all else being equal.
I am using Column A as the total count of rows and Column F as the location of blank cells. Is this the best/ fastest way to write this code?
' Finds the last row with a file numbers and removes the remaining rows
Dim LastRow As Integer
LastRow = Range("A" & Rows.Count).End(xlUp).Row
Range("F2:F" & LastRow).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Try this (wishing it would help, although take backup of your sheet before!):
Sub FastestBlankRowTerminator()
ActiveSheet.UsedRange.Columns(6).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End Sub
One of the simplest things I can advise that should increase performance by a noticeable amount is to turn off screen updating and automatic calculation while running this procedure.
I typically turn these items off at the initial invocation of code and turn them back on after the final one. Meaning I would have one sub to contain a series of other subs and functions that it would execute in sequence. Instead of embedding this in those subs and functions individually I just set them off, execute the main sub, and then reset them.
' Speed Up
application.screenupdating = false
application.calculation = xlCalculationManual
<insert code you want to improve performance on here>
' Slow Down
application.screenupdating = true
application.calculation = xlCalculationAutomatic
I ran a test myself populating column a with a rowcount up to 39000 and then every other record would have a "1" in column f.
It still takes some time but only 46 seconds on my core2duo, if I don't turn off screen updating it takes 3 minutes and 34 seconds.
Sub Main()
' Speed Up
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Begin ' Main Sub
' Reset
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Sub Begin()
' Sub 1
' Sub 2
' Sub 3
Remove_Blanks
End Sub
Sub Remove_Blanks()
Dim dA As Date, dB As Date
Dim wb As Workbook
Dim ws As Worksheet
Dim i As Long, j As Integer
Dim r As Long, c As Integer
dA = Now
' Commented out to indicate they could be here but if you are executing multiple procedures then you should have it occur outside of this.
'Application.ScreenUpdating = False
'Application.Calculation = xlCalculationManual
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1")
With ws
For r = 1 To .UsedRange.Rows.Count
If .Cells(r, 6) = "" Then .Rows(r).Delete
Next r
End With
dB = Now
'Commented out for same reason above
'Application.ScreenUpdating = True
'Application.Calculation = xlCalculationAutomatic
Debug.Print "Remove_Blanks: " & Format((dB - dA), "mm/dd/yyyy hh:mm:ss")
End Sub
I set up a matrix of 50,000 rows x 12 columns. In column F I placed about 25,000 blanks randomly placed.
Read the used range into an array
Iterate through the array and read those rows with content in column F into a results array
Clear the original data
write the results array
A lot of steps, but the execution time was less than one second; it would probably be faster with screenupdating false; and longer if you have more columns.
EDIT: Screenupdating false did not significantly decrease the execution speed, which was approximately 0.36 seconds when timed with a hi-res timer.
EDIT2: After reading Tim Williams comment about preserving formatting and formulas, I present a different approach. This approach will use the Advanced Filter and, on the same made up data as above, will place the data on another worksheet minus the rows that have blanks in column F. This does require a first row of column headers in the data; or, at least, that F1 has a unique, non-blank value.
To accomplish that process takes about 0.15 seconds.
If you also want to copy it back over the original worksheet, and delete the added worksheet, that will take about another 0.3 seconds.
Here is some code to do that, but you'd have to alter it for your own specifications:
==============================================
Sub DeleteBlankFRows2()
Dim WS As Worksheet, wsTemp As Worksheet, rTemp As Range
Dim R As Range, rCrit As Range
Dim I As Long
Set WS = Worksheets("Sheet5")
Set R = WS.Range("a1").CurrentRegion
Set rCrit = R.Offset(0, R.Columns.Count + 3).Resize(2, 1)
rCrit(1) = R(1, 6)
rCrit(2) = "<>"
Application.ScreenUpdating = False
Worksheets.Add
Set wsTemp = ActiveSheet
wsTemp.Name = "Temp"
R.AdvancedFilter xlFilterCopy, rCrit, Cells(1, 1)
Set rTemp = wsTemp.Cells(1, 1).CurrentRegion
WS.Cells.Clear
rTemp.Copy WS.Cells(1, 1)
Application.DisplayAlerts = False
wsTemp.Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
=======================================
This was my original code using VBA arrays:
===========================
Sub foo()
Dim vSrc As Variant, vRes() As Variant
Dim rSrc As Range
Dim I As Long, J As Long, K As Long
Dim lRows As Long
'Or may need to use a different method to include everything
Set rSrc = Range("a1").CurrentRegion
vSrc = rSrc
'how many rows to retain
For I = 1 To UBound(vSrc)
If vSrc(I, 6) <> "" Then lRows = lRows + 1
Next I
ReDim vRes(1 To lRows, 1 To UBound(vSrc, 2))
K = 0
For I = 1 To UBound(vSrc)
If vSrc(I, 6) <> "" Then
K = K + 1
For J = 1 To UBound(vSrc, 2)
vRes(K, J) = vSrc(I, J)
Next J
End If
Next I
Cells.Clear
Range("a1").Resize(UBound(vRes, 1), UBound(vRes, 2)) = vRes
End Sub