I have encountered a strange problem that I simply cannot get my head around. I have a .csv file which contains 4 columns of dates. As a daily task I have to work with these dates and convert them to DMY format using the TextToColumn option of Excel. The original format of the dates is MDY - and for reasons I don't understand when done manually with Data>Text to Columns>Fixed Width>Date:MDY these dates are converted to correct DMY format.
Here is a screenshot of the raw data in the .csv file:
Raw data
Now I wanted to automate it through a simple macro, and after recording the text to columns operation and doing some tweaking to span all 4 columns I have got the following code:
Sub Text_To_Column()
'
' Select_Column_Get_Length Macro
'
Worksheets("Outcome extract").Activate
Dim l As range
Set l = range("A2", range("A2").End(xlDown))
Dim h As Long
h = l.Count
Dim daterange As range
Dim firstcell As range
Set firstcell = range("FG2")
Set daterange = range(firstcell, firstcell.Offset(0, 3))
For Each n In daterange
Dim dates As range
Set dates = range(n, n.Offset(h, 0))
dates.Select
Selection.TextToColumns Destination:=n, DataType:=xlFixedWidth, _
FieldInfo:=Array(0, 3)
Next n
End Sub
The code currently counts the number of entries and performs text to column operation on that many dates (as to not operate all the cells which I think would slow it down?). Either way the macro currently gives me different results to the manual operation even though all the parameters of Text to Column were taken from a recording.
For some reason the entries which were originally aligned to the right have their MM and DD swapped when done manually but NOT by the macro.
Any hint on where I am going wrong would be much appreciated!
P.s. I know I should avoid selecting cells and work with ranges I will fix that bit once I know this method works.
Related
I have inherited a very large spreadsheet and am trying to migrate it to a database. The table has over 300 columns, many of which reference other columns.
By converting it to a table (ListObject) in Excel, I thought it would be easier to deconstruct the logic... basically turn the formula:
=CJ6-CY6
into
=[#[Sale Price]]-[#[Standard Cost]]
Converting it to a table worked great... unfortunately it didn't change any of the embedded formulas. They still reference the ranges.
I think I may notionally understand why -- if a formula references a value in another row, then it's no longer a primitive calculation. But for formulas that are all on the same row, I'm wondering if there is any way to convert them without manually going into each of these 300+ columns and re-writing them. Some of them are beastly. No joke, this is an example:
=IF(IF(IF(HD6="",0,IF(HD6=24,0,IF(HD6="U",((FI6-(ES6*12))*$I6),($I6*FI6)*HS6)))<0,0,IF(HD6="",0,IF(HD6=24,0,IF(HD6="U",((FI6-(ES6*12))*$I6),($I6*FI6)*HS6))))>GO6,GO6,IF(IF(HD6="",0,IF(HD6=24,0,IF(HD6="U",((FI6-(ES6*12))*$I6),($I6*FI6)*HS6)))<0,0,IF(HD6="",0,IF(HD6=24,0,IF(HD6="U",((FI6-(ES6*12))*$I6),($I6*FI6)*HS6)))))
And it's not the worst one.
If anyone has ideas, I'd welcome them. I'm open to anything. VBA included.
I would never use this to teach computer science, but this is the hack that did the trick. To keep things simple, I transposed header names and the corresponding column into A17:
And then this VBA code successfully transformed each range into the corresponding column property.
Sub FooBomb()
Dim ws As Worksheet
Dim r, rw, translate As Range
Dim col, row As Integer
Dim find, anchored, repl As String
Set ws = ActiveWorkbook.ActiveSheet
Set rw = ws.Rows(6)
Set translate = ws.Range("A17:B363")
For col = 12 To 347
Set r = rw.Cells(1, col)
For row = 363 To 17 Step -1
find = ws.Cells(row, 1).Value2 & "6"
anchored = "$" & find
repl = "[#[" & ws.Cells(row, 2).Value2 & "]]"
r.Formula = VBA.Replace(r.Formula, anchored, repl)
r.Formula = VBA.Replace(r.Formula, find, repl)
Next row
Next col
End Sub
Hard-coded and not scalable, but I'm not looking to repeat this ever again.
-- EDIT --
Word to the wise to help performance, especially with as many columns and formulas are in this spreadsheet.
Set Formula calculation to manual before
Check before the field exists before doing a replacement -- skipping happens more often than not
Program ran in a few seconds (minutes prior) before these changes:
If InStr(r.Formula, anchored) > 0 Then
r.Formula = VBA.Replace(r.Formula, anchored, repl)
End If
If InStr(r.Formula, find) > 0 Then
r.Formula = VBA.Replace(r.Formula, find, repl)
End If
I'm new to VBA, my experience is basically record macros and adapt them a little bit, and i´ve been playing with a macro to copy a filtered range in sheet 1 based on a date value located in sheet 2 range "C42", the copy part is working
I have tried a couple of solutions i found on the internet but they don't work for me and I can't find the mistake (probably very simple but my lack of knowledge prevents me from finding it)
Sub CopyPaste
If Worksheets("Costos Médicos").Range("C42") = Worksheets("CC1").Range("B101") Then 'both values are visually in date format "dd/mm/yyyy" but if changed to general give a number
Call Cost1 'This is a macro currently working
ElseIf Worksheets("Costos Médicos").Range("C42") = Worksheets("CC1").Range("B102") Then
Call Cost2 'This one also works fine
end if
End Sub
'I also tried this, I've tried declaring cm as long, string, date, but all returns error 9 (again lack of knowledge)
Dim src As Worksheet
Dim tgt As Worksheet
Dim cm0 As Range
Dim cm1 As Range
Dim cm2 As Range
Set src = ThisWorkbook.Sheets("CC1")
Set tgt = ThisWorkbook.Sheets("Costos Médicos")
Set cm0 = src.Range("C42") 'This is the given date
Set cm1 = tgt.Range("B101") 'This is a date
Set cm2 = tgt.Range("B102") 'This is another date
If cm0 = cm1 Then
Call Cost1 'this Works fine by itself
ElseIf cm0 = cm2 Then
Call Cost2 'this also Works
End If
I think the problem is simple but can't find the answer, I have tried multiple solutions online but they usually are for far more complicated things that I don't understand. Any help would be greatly appreciated.
I am quite sure that one of the Worksheet Names is typed incorrectly, as Error 9 means that you called an element by name or position which is not present, so "out of range".
Change the names of the sheets to x and y for a test.
Concerning the dates: You do not need to worry about the formatting. Every whole day is represented by a whole number. Hours, Minutes etc. are fractions thereof. Dates are stored as Floating point numbers (both in cells an in VBA) and can be compared to other dates or integers with no problems.
What I am doing is very simple - selecting a union of columns which contain numbers stored as text and converting them. Every time this runs, all even union numbered columns of data are getting cleared.
Union(Columns(19), Columns(22), Columns(25), Columns(28), Columns(31), Columns(34), Columns(37), Columns(40), Columns(43), Columns(46)).Select
With Selection
.Value = .Value
End With
I've looked though my entire code multiple times are cant figure why this is behaving so weird. any help is greatly appreciated.
The Value property of a discontiguous range only returns the first area of that range. When you then try and assign that value (array, in this case) back to a discontiguous range, you get strange results. For this particular case, every second column will get the value of the first cell in the first area.
You should loop through the areas in your range.
For each rArea in Selection.Areas
rarea.value2 = rarea.value2
Next rarea
Try to avoid using Select, and fully qualify your ranges. This makes things easier to diagnose and more robust...
Dim myRange As Range
With ThisWorkbook.Sheets("Sheet1")
Set myRange = Union(.Columns(19), .Columns(22), .Columns(25)) ' etc.
End With
Now if you're trying to convert text to numbers, you might be better off using the NumberFormat property as discussed here: What are .NumberFormat Options In Excel VBA?
Looping through range areas and number-formatting:
Dim area As Range
For Each area In myRange.Areas
area.NumberFormat = 0 ' for numbers, could still use area.Value = area.Value
Next area
I have a set of dates in column B like 06/03/2017 formatted in a custom dd-mmm formatting giving me 06-Mar output.
If I manually do Find/Replace and search for 06-Mar and select Look In: Values, it finds it no problem, however doing the same via VBA isn't working.
Dim Source as Worksheet, Dest as Worksheet
Dim mDate as String, copyRow as Integer
Set Dest = ThisWorkbook.Sheets("Weekly Updates")
Set Source = ActiveWorkbook.Sheets("Weekly Updates")
mDate = Dest.Range("B2").Text
copyRow = Source.Range("B:B").Find(What:=mDate, LookIn:=xlValues).Row
I have tried getting mDate as a value and searching in formulas and values, but nothing seems to work.
The line debugs on copyRow = Source....... however if I open the Find/Replace window manually at this point, it fills out the search bar with "06-Mar", and finds the value in column B no issues.
If I replace mDate with string I know is there, for example Find(What:="W/C"..... it returns copyRow as 1 (because its the header of that column).
Any suggestions what I am missing here?
EDIT
To cover a few of the comments below:
Yes the sheets are named the same, just in different workbooks (master file to pull data from multiple identical sources)
In terms of the formatting, I get the search criteria from a date with a true value of 06/03/2017 formatted as dd-mmm, searching in a range of data as true dates 06/03/2017 formatted also as dd-mmm.
Try to give the variable mDate the same format of your date :
copyRow = Source.Range("B:B").Find(What:=Format(mDate, "dd-mmm"), LookIn:=xlValues).Row
Firstly, apologies if I make a lot of mistakes in best VBA practice. I've been learning the language for this project in particular, and there are probably a number of things I'm doing wrong, so sorry if I make anyone cringe.
Next, the problem. I'm trying to sort a range by date (held in one column), in exactly the way which the sort function on the tools menu works when sorting "anything that looks like a number as a number". The column is a mixture of UK-locale dates and text strings held in "general" formatted cells that are essentially just dates. In other words, something simple like:
Range(rngFirstCell, rngLastCell).Sort Key1:= 2, Order1:=xlAscending, _
DataOption1:=xlSortTextAsNumbers, Header:=xlYes
should do the trick. Indeed, with I think the only exception being that the recorded code uses xlGuess for Header, and includes a value for OrderCustom of 1, is exactly what the macro recorder produces. Needless to say, I've tried recorded code with the same results.
The problem is, instead of getting:
Type Date
gen 01/3/2008
date 02/4/2008
date 17/4/2008
gen 25/7/2009
I get:
Type Date
date 02/4/2008
date 17/4/2008
gen 01/3/2008
gen 25/7/2009
Since this works in later versions of Excel, I've concluded it's a bug in 2003. My current solution to which is to firstly set the NumberFormat property of all cells in the column to "d/m/yyyy", and then to iterate over them and replace each value with the result of CDate(Cell.Value). It makes the sort work. It also takes 10 seconds to reformat a column with 20 entries because there's so much interaction between the sheet and VBA (slow, from what I've read). Since it's entirely possible that some datasets I'll need to sort by code will be hundreds of cells long, I need something faster.
Can anyone suggest a better way of doing it?
For clarity, the code I'm using at the moment looks very like:
Range(rngFirstCell, rngLastCell).Columns(2).NumberFormat = "d/m/yyyy"
Dim intIndex As Long, varCellRef As Variant
For intIndex = 0 to Range(rngFirstCell, rngLastCell).Columns(2).End(xlDown).Row
Set varCellRef = Range(rngFirstCell, rngLastCell).Columns(2)(intIndex)
varCellRef.Value = CDate(varCellRef.Value)
Next
Range(rngFirstCell, rngLastCell).Sort Key1:= 2, Order1:=xlAscending, _
DataOption1:=xlSortTextAsNumbers, Header:=xlYes
You are right to say referencing the sheet in a loop is slow, but it can be avoided by copying the data to a variant array and looping over that, then copy back to the sheet:
Dim rngFirstCell As Range
Dim rngLastCell As Range
' Setting a sample range for my testing...
Set rngFirstCell = [B12]
Set rngLastCell = [C131084]
Dim dat As Variant
Dim rng As Range
Dim i As Long
Set rng = Range(rngFirstCell, rngLastCell) ' this includes the header row
dat = rng.Columns(2)
rng.Columns(2).NumberFormat = "d/m/yyyy"
Dim intIndex As Long, varCellRef As Variant
For i = 2 To UBound(dat, 1)
dat(i, 1) = CDate(dat(i, 1))
Next
rng.Columns(2) = dat
rng.Sort Key1:=rng.Cells(1, 2), Order1:=xlAscending, _
DataOption1:=xlSortTextAsNumbers, Header:=xlYes
This ran on a sample data set in < 1sec (approx 130,000 rows)
Notice, I made a few minor tweeks to get it to run