Extremely slow VBA code when formatting cells - vba

When attempting to format 2 columns in a workbook, the execution of the macro is extremely slow. To format approximately 4000 rows, it takes over 10 minutes.
The dates are populated from an external source that stores them as strings.
When commenting the code, it loads under 60 seconds.
The code
'Discover last row of data
RowsToProcess = Range("A" & Rows.Count).End(xlUp).Row
For i = 6 To RowsToProcess
Worksheets("Data").Range("B" & i).Select
Selection.NumberFormat = "dd/mm/yy;;"
Selection.Value = CDate(Selection.Value)
Worksheets("Data").Range("C" & i).Select
Selection.NumberFormat = "dd/mm/yy;;"
Selection.Value = CDate(Selection.Value)
Next i
The code below does not format cells in the required format either.
Worksheets("Data).Columns("C").NumberFormat = dd/mm/yy;;"

The post #aelgoa linked to is spot on. When the standard Application.ScreenUpdating options for speeding up your code aren't enough, I turn to Variant arrays.
(If you wanted to see how I use Application.ScreenUpdating etc., wrapped in a GoFast function, check out my answer here: VBA code optimization)
The script below works like this:
Load the Range defined in columns B and C into a Variant array
Apply CDate logic there (rather than accessing the Sheet every time)
Write the CDate-modified array out to Sheet
One caveat though -- my question in the comment above about differentiating between mm/dd and dd/mm (say May 6th, 2014 vs June 5th, 2014) still stands. I'll modify the code below based on your thoughts there. Thanks!
Option Explicit
Sub ProcessDates()
Dim AryColBandC As Variant
Dim DateFormatB As Date, DateFormatC As Date
Dim RngColBandC As Range
Dim LastRow As Long, Counter As Long
Dim MySheet As Worksheet
'set references up-front
Set MySheet = ThisWorkbook.Worksheets("Sheet1")
With MySheet
LastRow = .Range("A" & .Rows.Count).End(xlUp).Row
Set RngColBandC = .Range(.Cells(6, 2), .Cells(LastRow, 3))
End With
'load the B-C column range into a variant array
AryColBandC = RngColBandC
'loop through the variant array, applying the date
'conversion to each entry in the array and writing back
For Counter = LBound(AryColBandC) To UBound(AryColBandC)
DateFormatB = CDate(AryColBandC(Counter, 1)) '<~ temporarily store
DateFormatC = CDate(AryColBandC(Counter, 2)) '<~ dates here
AryColBandC(Counter, 1) = DateFormatB
AryColBandC(Counter, 2) = DateFormatC
Next Counter
'write the results out to the sheet
For Counter = LBound(AryColBandC) To UBound(AryColBandC)
MySheet.Cells(5 + Counter, 2) = AryColBandC(Counter, 1)
MySheet.Cells(5 + Counter, 3) = AryColBandC(Counter, 2)
Next Counter
End Sub

Related

VBA Macro wanted - Loop copying data from one sheet to another

Last year, I made a huge spreadsheet with all newest available data on every country in the world. The idea was that I could download the latest data - say, a data sheet containing population statistics from the World Bank - and easily transport them into my main sheet.
Here is an example of how it looked like:
To draw the data from the other spreadsheets, I used long, messy lines of IF-functions, such as:
=IF(ISNUMBER(IFERROR(VLOOKUP($A3;Population!$A$3:$C$400;2;FALSE);"Not
Found"));IFERROR(VLOOKUP($A3;Population!$A$3:$C$400;2;FALSE);"Not
Found");"Not Found")&"
("&IF(ISNUMBER(IFERROR(VLOOKUP($A3;Population!$A$3:$C$400;3;FALSE);"Not
Found"));IFERROR(VLOOKUP($A3;Population!$A$3:$C$400;3;FALSE);"Not
Found");"Not Found")&")"
Obviously, this is not the most efficient way of doing this. Here is what I need the macro to do:
To first match column A, containing all country names, in my main sheet, with column A in the data sheet, containing countries specific to this data set.
Then copy-paste the latest data (non-blank cell furthest to the right) from the data sheet into the main sheet, at the appropriate places (i.e. Uganda gets matched with Uganda).
The pasted data must also contain their respective years in parenthesis (in the picture, all data happen to be from 2016, but this is not always the case).
I have experimented with some loops to try and replicate the above-mentioned IF-functions, but nothing seems to work for me. So far, my tries have led me to this:
Option Explicit
Sub test()
Dim data As Worksheet
Dim report As Worksheet
Dim finalrow As Integer
Dim finalcol As Integer
Dim rngMatch As Range
Dim i As Integer
Dim countryname As String
Set data = Ark2
Set report = Ark1
countryname = data.Range("A5").Value
report.Range("B2:CC300").ClearContents
data.Select
finalrow = Cells(Rows.Count, 1).End(xlUp).Row
For i = 2 To finalrow
If Cells(i, 1) = countryname Then
Cells(i, 5).Copy
report.Select
Range("B300").End(xlUp).Offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
data.Select
End If
Next i
report.Select
End Sub
There are many flaws here, and it does not come close to solve my problem. Can anyone perhaps point me in the right direction of what to do?
Thank you for your time.
here is a loop that will:
Loop through column A in your main workbook (country names)
Will look up this country in your data workbook
Gets the last used column of the found row (if value is found)
Prints the value in the direct window, obviously you must adjust that piece of code
Sub Test()
Dim RNG1 As Range, CL1 As Range
Dim LR1 As Long, LR2 As Long, LC As Long
LR1 = Workbooks("MainWB").Sheets(1).Cells(Rows.Count, 1).End(xlUp).Row
LR2 = Workbooks("DataWB").Sheets(1).Cells(Rows.Count, 1).End(xlUp).Row
Workbooks("DataWB").Activate
Set RNG1 = Workbooks("DataWB").Sheets(1).Range(Cells(1, 1), Cells(LR2, 1))
For X = 3 To LR1
With RNG1
Set CL1 = .Find(What:=Workbooks("MainWB").Sheets(1).Cells(X, 1).Value, LookIn:=xlValues, LookAt:=xlWhole)
If Not CL1 Is Nothing Then
LC = Workbooks("DataWB").Sheets(1).Cells(CL1.Row, Columns.Count).End(xlToLeft) + 1
Debug.Print Workbooks("DataWB").Sheets(1).Cells(CL1.Row, LC).Value 'Do something else with this value obviously
End If
End With
Next X
Workbooks("MainWB").activate
End Sub
You obviously need to adjust all variables and names to your needs. Hopefully you will find bits and pieces usefull.
EDIT - As JvdV pointed out, copy pasting is not really necessary, so I changed the code to report.Sheets[...].Value = data.Sheets[...].Value instead, which is much, much faster. Thank you again, JvdV.
So, with the help of JvdV, I was able to piece together a macro, which works just fine for me.
Sub extract()
Dim RNG1 As Range, CL1 As Range
Dim LR1 As Long, LR2 As Long, LC As Long
Set report = Workbooks("Main.xlsm")
Set data = Workbooks("API_NE.EXP.GNFS.CD_DS2_en_excel_v2_9944773.xls")
report.Sheets("Report").Activate
data.Sheets("Data").Activate
LR1 = report.Sheets("Report").Cells(Rows.Count, 1).End(xlUp).Row
LR2 = data.Sheets("Data").Cells(Rows.Count, 1).End(xlUp).Row
RC2 = report.Sheets("Report").Cells(LR1, Columns.Count).End(xlToLeft).Column + 1
RC3 = RC2 + 1
Set RNG1 = data.Sheets("Data").Range(Cells(1, 1), Cells(LR2, 1))
report.Sheets("Report").Cells(1, RC2).Value = data.Sheets("Data").Cells(5, 3).Value
report.Sheets("Report").Cells(1, RC3).Value = "Year"
For X = 2 To LR1
With RNG1
Set CL1 = .Find(What:=report.Sheets("Report").Cells(X, 1).Value, LookIn:=xlValues, LookAt:=xlWhole)
If Not CL1 Is Nothing Then
LC1 = data.Sheets("Data").Cells(CL1.Row, Columns.Count).End(xlToLeft).Column
If IsNumeric(data.Sheets("Data").Cells(CL1.Row, LC1)) Then
report.Sheets("Report").Cells(LR1, RC2).End(xlUp).Offset(1, 0).Value = data.Sheets("Data").Cells(CL1.Row, LC1).Value
Else
report.Sheets("Report").Cells(LR1, RC2).End(xlUp).Offset(1, 0).Value = "N/A"
End If
If IsNumeric(data.Sheets("Data").Cells(CL1.Row, LC1)) Then
report.Sheets("Report").Cells(LR1, RC3).End(xlUp).Offset(1, 0).Value = data.Sheets("Data").Cells(4, LC1).Value
Else
report.Sheets("Report").Cells(LR1, RC3).End(xlUp).Offset(1, 0).Value = "N/A"
End If
End If
End With
Next X
report.Sheets("Report").Activate
With Worksheets("Report").Columns(RC2)
.NumberFormat = "0.00"
.Value = .Value
End With
With Worksheets("Report").Columns(RC3)
.NumberFormat = "0"
.Value = .Value
End With
End Sub
This macro allows you to extract latest data from a timeseries, as well as the respective year of the datapoint. In this specific macro you can duplicate data on any country, from any spreadsheet provided by the World Bank. All you have to do, is:
plug in the name of your workbook (eg. "Main.xlsm") as well as the name of the workbook from the World Bank (eg. "API_NE.EXP.GNFS.CD_DS2_en_excel_v2_9944773.xls")
Name the countries of your interest in Column A of your own workbook.
Let the macro run
Plug in a new workbook from the World Bank
Let the macro run again
etc.
The macro will not overwrite previous data, but rather duplicate the datapoints and sample years in the right-most columns. An example of the macro in action can be seen below.
Example of the macro

If Date in this range can be found in separate range, delete row

I have a Excel workbook, almost like a database, where I update Historical data each week. Using a separate sub, I pull in an Export as a worksheet to the book. I find the unique dates that are in the export. I then look at Historical data, and if the Historical date matches one of the Export dates, I delete the row in Historical. Eventually I copy and paste the Export in to the Historical data tab.
The code below works how I'd like it to, but I have some questions after the block of code:
Sub AddNewData()
'This will take what's in Export and put it in to Historical
Dim Historical As Worksheet
Dim Export As Worksheet
Dim exportdates As Range
Set Historical = ThisWorkbook.Worksheets("Historical")
Set Export = ThisWorkbook.Worksheets("Export")
'Pulling unique values of dates from this range and pasting to M1:
Export.Range("B2:B" & Export.Cells(Export.Rows.Count, 1).End(xlUp).Row).AdvancedFilter _
Action:=xlFilterCopy, CopyToRange:=Export.Range("M1"), Unique:=True
'Originally I was thinking I could make this a list of some sort vlookup or match?
'As of now, though, it goes unused...:
Set exportdates = Export.Range("M1:M" & Export.Cells(Export.Rows.Count, 13).End(xlUp).Row)
For r = Historical.Cells(Rows.Count, 1).End(xlUp).Row To 1 Step -1
If Historical.Cells(r, 2).Value = exportdates(1, 1).Value Or _
Historical.Cells(r, 2).Value = exportdates(2, 1).Value Or _
Historical.Cells(r, 2).Value = exportdates(3, 1).Value _
Then Historical.Rows(r).Delete
Next
'Copying and pasting Export data to Historical tab
Export.Range("A2:J" & Export.Cells(Export.Rows.Count, 1).End(xlUp).Row).Copy
Historical.Range("A" & Historical.Cells(Historical.Rows.Count, 1).End(xlUp).Row + 1).PasteSpecial xlPasteValues
Application.CutCopyMode = False
End Sub
1) Can that IF statement be condensed somehow using the exportdates range?
2) This works just fine for a few hundred rows of data when my dates are simply the first of each month, but I also have an export that has each day as a date that I'll have to match with a different tab with daily information. That one has THOUSANDS of rows. I don't believe this macro will be much more efficient than simply sorting by date and eliminating myself? Can I change the IF statement to be more inclusive, like question 1?
Thank you!
Whenever you have to delete many rows in Excel with VBA, the best practice is to assign these rows to a range and to delete the range at the end.
Thus, your code should be refactored in this part:
For r = Historical.Cells(Rows.Count, 1).End(xlUp).Row To 1 Step -1
If Historical.Cells(r, 2).Value = exportdates(1, 1).Value Or _
Historical.Cells(r, 2).Value = exportdates(2, 1).Value Or _
Historical.Cells(r, 2).Value = exportdates(3, 1).Value _
Then Historical.Rows(r).Delete
Next
This is a simple sample that you can use for the refactoring (just make sure to write a few times 1 in Range("A1:A20") to see how it works:
Public Sub TestMe()
Dim deleteRange As Range
Dim cnt As Long
For cnt = 20 To 1 Step -1
If Cells(cnt, 1) = 1 Then
If Not deleteRange Is Nothing Then
Set deleteRange = Union(deleteRange, Cells(cnt, 1))
Else
Set deleteRange = Cells(cnt, 1)
End If
End If
Next cnt
deleteRange.EntireRow.Select
Stop
deleteRange.EntireRow.Delete
End Sub
Once you run the code it stops at the Stop sign. You see that the rows to be deleted are selected. Once you continue with F5 they would be deleted. Consider removing the Stop and .Select line in your code.
Some general ideas how to speed up code: https://stackoverflow.com/a/49514930/5448626

Sum Values based on unique ID

Just started a new job. I'm automating a month-end report and I'm new at VBA. Been googling most of my issues with success, but I've finally run into a wall. In essence I'm downloading some data from SAP and from there I need to build a report.
My question is: How to do a sumif function using loops in VBA?
Data pull:
Sheet1 contains a product code and purchase amounts (columns A & B) respectively. One product code can have several purchases (several rows with the same product code).
Steps so far:
I arranged the data sheet1 to be in ascending order.
Copied unique values for the product codes onto another sheet (sheet2). So Sheet2 has a list of all the products (in ascending order).
I want to get the sum of all purchases in sheet2 column B (per product code). I know how to do this using formulas, but I need to automate this as much as possible. (+ I'm genuinely interested in figuring this out)
This is what I did in VBA so far:
Sub Macro_test()
Dim tb As Worksheet
Dim tb2 As Worksheet
Dim x As Integer
Dim y As Integer
Dim lrow As Long
Set tb = Sheets("sheet1")
Set tb2 = Sheets("sheet2")
lrow = tb.Cells(Rows.Count, "A").End(xlUp).Row
For x = 2 To lrow
For y = 2 To lrow
If tb2.Cells(x, 1).Value = tb.Cells(y, 1).Value Then
tb2.Cells(x, 2).Value = tb.Cells(y, 2).Value
End If
Next y
Next x
End Sub
If i'm not mistaken, for each product_code in sheet2 col A, I'm looping through all the product codes in sheet1 and getting back the LAST value it finds, instead of the sum of all values... I understand why it doesn't work, I just don't know how to fix it.
Any help would be much appreciated. Thanks!
This statement overwrites the value of tb2.Cells(x, 2).Value at each iteration:
tb2.Cells(x, 2).Value = tb.Cells(y, 2).Value
Instead, I think you need to keep adding to it:
tb2.Cells(x, 2).Value = tb2.Cells(x, 2).Value + tb.Cells(y, 2).Value
But I don't like the looks of your double-loop which uses only one lrow variable to represent the "last row" on the two different worksheets, that could be causing some issues.
Or, in your loop do something like this which I think will avoid the duplicate sum. Still, assumes the second worksheet doesn't initially have any value in
' Base our lRow on Sheet2, we don't care how many rows in Sheet1.
lrow = tb2.Cells(tb2.Rows.Count, 1).End(xlUp).Row
Dim cl as Range
Set cl = tb.Cells(2,1) 'Our initial cell value / ID
For x = 2 to lRow '## Look the rows on Sheet 2
'## Check if the cell on Sheet1 == cell on Sheet2
While cl.Value = tb2.Cells(x,1).Value
'## Add cl.Value t- the tb2 cell:
tb2.Cells(x, 2).Value = tb2.Cells(x, 2).Value + cl.Offset(0,1).Value
Set cl = cl.Offset(1) '## Reassign to the next Row
Wend
Next
But it would be better to omit the double-loop and simply use VBA to do 1 of the following:
1. Insert The Formula:
(See Scott Holtzman's answer).
This approach is better for lots of reasons, not the least of which is that the WorksheetFunction is optimized already, so it should arguably perform better though on a small dataset the difference in runtime will be negligible. The other reason is that it's stupid to reinvent the wheel unless you have a very good justification for doing so, so in this case, why write your own version of code that accomplishes what the built-in SumIf already does and is specifically designed to do?
This approach is also ideal if the reference data may change, as the cell formulas will automatically recalculate based on the data in Sheet1.
2. Evaluate the formula & replace with values only:
If you prefer not to retain the formula, then a simple Value assignment can remove the formula but retain the results:
With .Range(.Range("B2"), .Range("A2").End(xlDown).Offset(, 1))
.FormulaR1C1 = "=SUMIF(Sheet1!C[-1]:C[-1],RC[-1],Sheet1!C:C)"
.Value = .Value 'This line gets rid of the formula but retains the values
End With
Use this approach if you will be removing Sheet1, as removing the referents will break the formula on Sheet2, or if you otherwise want the Sheet2 to be a "snapshot" instead of a dynamic summation.
If you really need this automated, take advantage of VBA to place the formula for you. It's very quick and easy using R1C1 notation.
Complete code (tested):
Dim tb As Worksheet
Dim tb2 As Worksheet
Set tb = Sheets("sheet1")
Set tb2 = Sheets("sheet2")
Dim lrow As Long
lrow = tb.Cells(tb.Rows.Count, 1).End(xlUp).Row
tb.Range("A2:A" & lrow).Copy tb2.Range("A2")
With tb2
.Range("A2").CurrentRegion.RemoveDuplicates 1
With .Range(.Range("B2"), .Range("A2").End(xlDown).Offset(, 1))
.FormulaR1C1 = "=SUMIF(Sheet1!C[-1]:C[-1],RC[-1],Sheet1!C:C)"
End With
End With
Note that with R1C1 notation the C and R are not referring to column or row letters . Rather they are the column and row offsets from the place where the formula is stored on the specific worksheet. In this case Sheet!C[-1] refers to the entire A column of sheet one, since the formula is entered into column B of sheet 2.
I wrote a neat little algorithm (if you can call it that) that does what you want them spits out grouped by totals into another sheet. Basically it loops through the first section to get unique names/labels and stores them into an array. Then it iterates through that array and adds up values if the current iteration matches what the current iteration of the nested loop position.
Private Sub that()
Dim this As Variant
Dim that(9, 1) As String
Dim rowC As Long
Dim colC As Long
this = ThisWorkbook.Sheets("Sheet4").UsedRange
rowC = ThisWorkbook.Sheets("Sheet4").UsedRange.Rows.Count
colC = ThisWorkbook.Sheets("Sheet4").UsedRange.Columns.Count
Dim thisname As String
Dim i As Long
Dim y As Long
Dim x As Long
For i = LBound(this, 1) To UBound(this, 1)
thisname = this(i, 1)
For x = LBound(that, 1) To UBound(that, 1)
If thisname = that(x, 0) Then
Exit For
ElseIf thisname <> that(x, 0) And that(x, 0) = vbNullString Then
that(x, 0) = thisname
Exit For
End If
Next x
Next i
For i = LBound(that, 1) To UBound(that, 1)
thisname = that(i, 0)
For j = LBound(this, 1) To UBound(this, 1)
If this(j, 1) = thisname Then
thisvalue = thisvalue + this(j, 2)
End If
Next j
that(i, 1) = thisvalue
thisvalue = 0
Next i
ThisWorkbook.Sheets("sheet5").Range(ThisWorkbook.Sheets("Sheet5").Cells(1, 1), ThisWorkbook.Sheets("Sheet5").Cells(rowC, colC)).Value2 = that
End Sub
Yay arrays

Applying VBA RIGHT to an entire column - Infinite Loop Issue

I have data that I am working to Parse Out that I have imported from approval emails sent in Outlook. At this point I am just importing the CreationTime and the SubjectLine.
For the subject line I am able to use the Split function to separate out most of the data. I then am left with Job Codes in Column B and Position numbers in Column C which includes the text: "Job Codes: XXXX" and the four digit job code number and "PN XXXX" and either a four digit or 6 digit position number. I am trying to use the Right functionality to loop through the entire column and reformat the column just to show only the four digit job code number for Column B and either just the 4 digit or 6 digit position number (the actual numbers) for Column C
For Job Code Column B:
Currently my code works for Shortening the Job Codes but it involves adding a column, putting the RIGHT formula in that column for the shortened Job Code, then copying and pasting the formula as values back into the column and then deleting the original column.
The problem- Works but perhaps not the most efficient with a larger data set (currently 200 rows but will have 2000 or more)
Code:
Sub ShortenJobCodes()
Application.ScreenUpdating = False
Const R4Col = "=RIGHT(RC3,4)"
Dim oRng As Range
Dim LastRow As Long
Range("B1").EntireColumn.Insert
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
Set oRng = Range("B:B")
Range(oRng, Cells(LastRow, "B")).FormulaR1C1 = R4Col
Set oRng = Nothing
Columns("B").Select
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues
Range("C1").EntireColumn.Delete
Application.ScreenUpdating = True
End Sub
For Position Numbers Column C:
Currently I have mirrored the above code but added in an if statement using LEN to count if the characters are less than 8, if so then insert one RIGHT function if not insert the other RIGHT function. This also involves adding an additional column putting the RIGHT formula in that column for the shortened Position Number(Eliminating all but just the number), then copying and pasting the formula as values back into the column and then deleting the original column.
Problem - This works but seems to take forever to process and in fact looks like it is in an infinite loop. When I Esc out of it, it does add the column and then input the proper RIGHT formula (leaving just the numeric values) but the sub never seems to end, nor does it copy and paste the formulas as values or delete the original column. As noted above I realize this is likely a more efficient way to do this but I have tried a bunch of options without any luck.
I am realizing part of the loop might be due to the range itself being an entire column but I cannot find a way to stop that with the last row (even though I have a count in there).
Code:
Sub ShortenPositionNumbers()
Application.ScreenUpdating = False
Const R4Col = "=RIGHT(RC4,4)"
Const R6Col = "=RIGHT(RC4,6)"
Dim oRng As Range
Dim rVal As String
Dim y As Integer
Dim selCol As Range
Dim LastRow As Long
Range("C1").EntireColumn.Insert
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
Set selCol = Range("D:D")
For Each oRng In selCol
oRng.Select
rVal = oRng.Value
If Len(oRng.Value) > 8 Then
oRng.Offset(0, -1).FormulaR1C1 = R6Col
Else
oRng.Offset(0, -1).FormulaR1C1 = R4Col
End If
Next
Set oRng = Nothing
Columns("C").Select
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues
Range("D1").EntireColumn.Delete
Application.ScreenUpdating = True
End Sub
Major Question: Is there a way to use RIGHT/TRIM/LEN/LEFT functions to do this within a cell without having to add columns/delete columns and insert functions?
There are a few things you can do here to speed up your code. I'm only going to reference the second code block as you can apply similar logic to the first.
The first issue is that you create a LastRow variable but never reference it again. It looks like you meant to use this in the selCol range. You should change that line to Set selCol = Range("C1:C" & lastRow). This way, when you loop through the rows you only loop through the used rows.
Next, in the For-Each loop you Select every cell you loop through. There really isn't any reason to do this and takes substantially longer. You then create the variable rVal but never use it again. A better way to set up the loop is as follows.
For Each oRng in selCol
rVal = oRng.Value
If Len(rVal) > 8 Then
oRng.Value = Right(rVal, 6)
Else
oRng.Value = Right(rVal, 4)
End If
Next
This is much cleaner and no longer requires creating columns or copying and pasting.
Try this, it uses Evaluate and no loops or added columns.
Sub ShortenPositionNumbers()
Application.ScreenUpdating = False
Dim selCol As Range
Dim LastRow As Long
With ActiveSheet
LastRow = .Cells(Rows.Count, "A").End(xlUp).Row
Set selCol = .Range(.Cells(1, 3), .Cells(LastRow, 3))
selCol.Value = .Evaluate("INDEX(IF(LEN(" & selCol.Address(0, 0) & ")>8,RIGHT(" & selCol.Address(0, 0) & ",6),RIGHT(" & selCol.Address(0, 0) & ",4)),)")
End With
Application.ScreenUpdating = True
End Sub
Or work with arrays
Sub ShortenPositionNumbers()
Dim data As Variant
Dim i As Long
With Range("C3:C" & Cells(Rows.Count, "A").End(xlUp).Row)
data = Application.Transpose(.Value)
For i = LBound(data) to UBound(data)
If Len(data(i)) > 8 Then
data(i) = RIGHT(data(i),6)
Else
data(i) = RIGHT(data(i),4)
End If
Next
.Value = Application.Transpose(data)
End With
End Sub

Using VBA If then statement to copy and paste data

I am a brand new VBA user attempting to copy and paste data based on a range of dates. In column one I have dates and in column two I have the data I would like to copy and paste. CurYear refers to the end date in the range I am looking for and StatDate refers to the beginning date in the Range I am looking for. When I run this code it crashes Excel. Please help I am very lost
Worksheets("Weekly").Select
Dim nRows As Integer
Dim CurYear As Date
Dim StartDate As Date
nRows=Range("A1").CurrentRegions.Count.Rows
CurYear=Range("I265").Value
StartDate=Range("M5").Value
Do While Cells(nRows,1)<>""
if Cells(nRows,1).Value< CurYear & Cells(nRows,1)> StartDate Then
Cells(nRows,1).Offset(0,1).Copy
Worksheets("Weekly").Range("H41").Paste
Loop
End If
Put "option explicit" at the top of your code (before the sub) and it will tell you things to fix. Doing that will fix the part of your error where your end if was outside the loop instead of inside it but it won't catch that you weren't changing your loop counter. Try this code instead. It is actually pretty much the same as what you had with a couple minor changes.
Option Explicit
Sub test()
Dim sht As Worksheet, i As Long, l As Long, j
Dim nRows As Integer
Dim CurYear As Date
Dim StartDate As Date
Set sht = Worksheets("Test1") ' set the sheet as object isntead of selecting it for faster code and avoiding other issues
nRows = Cells(sht.Rows.Count, "B").End(xlUp).Row 'Last used row in column B - current region lastrow gets twitchy in some circumstances and should be avoided unless there is a reason to use it
l = 41
CurYear = range("I265").Value
StartDate = range("M5").Value
For i = 1 To nRows
If Cells(i, 1).Value < CurYear And Cells(i, 1).Value > StartDate Then 'for If statements you use "and" not "&"
Cells(l, 15) = Cells(i, 2) 'you will want something like this line and the next if you don't want to overwrite H41 if there is more than one match
l = l + 1
End If
Next i
End Sub
Also, to help with debugging, Open your locals window (View in the VBE). Step through your code with F8, watching your variables in the locals window to ensure that they are what you expect them to be at that step in your script.
If you do this with your code, you will see that you were missing a counter change with your variable for your loop. So it was looking for nRow to eventually be "" but it stays at whatever it was set to. Infinite loop. I changed it to a for next format but 6 of 1 and half dozen of another for your code.
Welcome to VBA. Don't poke yer eye out. :-)
Instead of using copy/ paste that uses a lot of memory and makes the program run slow, you maybe want to consider the following code that serves the same purpose as your code or Rodger's yet faster than using Select and copy/ paste syntax.
Sub Test()
Dim nRows As Long, LastRow As Long 'Declare as Long instead of Integer to avoid overflow
Dim CurYear As Date, StartDate As Date
LastRow = Cells(Rows.Count, 1).End(xlUp).Row 'Count the last used row in column 1 where you put the first data (dates)
nRows = 2 'Set the starting point of row where you put the first data (dates). In this example I use 2
CurYear = Range("I265").Value
StartDate = Range("M5").Value
Do
If Cells(nRows, 1).Value < CurYear And Cells(nRows, 1) > StartDate Then 'Use And not &
Cells(nRows, 5).Value = Cells(nRows, 2).Value 'This is essentially a "copy/ paste" syntax. Change the value (5) to the column you want to paste the value in column 2
End If
nRows = nRows + 1 'Set an increment value so each looping the nRows will increase by 1
Loop Until nRows = LastRow + 1 'Added by 1 so that the data in LastRow will keep being processed
End Sub