Difficulty forcing VBA to parse dates in US/UK format - vba

Thanks to all those who have helped thus far and helped me refine the problem
I'm receiving a CSV file from a third party software application with date time stamp in UK format that can be read by the VBA as a US date format
My machines "Region and Language Settings" are set to English (United Kingdom) but if I use
Application.LanguageSettings.LanguageID(msoLanguageIDUI)
The result is 1033 - ie: English, US and not fitting my machine settings.
CSV is a file with columns as follows:
| report name | value | % | date |
CSV created by a program and saved by users - a direct save of the CSV to disk by the third party app results in CSV table rows as follows:
"Report name","264","2.2 %","08 Jul 2021 11:05" (this imports correctly)
If users open the CSV and then save from Excel the file is saved as follows (note no quotation marks and amended date format:
Report name,1,0.00%,01/07/2021 12:37 (this fails to import correctly - cell read by VBA as 7th Jan 21)
My code should be able to handle both situations.
I'm importing the CSV data as follows:
Set csvWB = Workbooks.Open(my_FileName)
Set csvData = csvWB.Worksheets(1)
Dim csvReportAr() As Variant 'array of reprot names from column one of the CSV
Dim csvNumberAr() As Variant 'array of values (report results) from col 2 of the CSV
Dim csvDateAr() As Date 'array of the dates the reports were run
Do While csvData.Cells(cellRow, 1).Value <> Empty
Do While csvData.Cells(cellRow, 1).Value <> Empty
ReDim Preserve csvReportAr(cellRow)
ReDim Preserve csvNumberAr(cellRow)
ReDim Preserve csvDateAr(cellRow)
csvReportAr(cellRow - 1) = csvData.Cells(cellRow, 1).Value
csvNumberAr(cellRow - 1) = csvData.Cells(cellRow, 2).Value
Debug.Print "cell value = " & csvData.Cells(cellRow, 4).Value
Debug.Print "Month (of cell value) = " & Month(csvDateAr(cellRow - 1))
csvDateAr(cellRow - 1) = convertDateUKtoUS(csvData.Cells(cellRow, 4).Value)
Debug.Print "Date value = " & csvDateAr(cellRow - 1)
Debug.Print "Month of date = " & Month(csvDateAr(cellRow - 1))
cellRow = cellRow + 1
Loop
the debug statements print the following:
cell value = 07/01/2021 06:45:00
Month (of cell value) = 12
Date value = 07/01/2021
The difficulty is that the date I'm trying to import here is listed as 01/07/2021 06:45:00 in the CSV (1st July) based on the text editor view of the CSV. Just reading this cell value it seems that the VBA assumes this is a date and stores as a date saved in US format.
I suspect this is a locale issue? My locale settings seem incongruous and other users might have erroneous locale settings as well so we should be able to account for this
Questions:
Can I force VBA to read the date-time stamp as a string and then parse manually to ensure read as date in UK format? - OR - Can I read as a date time stamp specifying a specific format to read with?
Can you advise on the issue with locale with VBA stating locale is English US whilst machine settings seem to be English UK?
Can I change the locale with VBA temporarily? (other posts I've read suggests this is not advised!)
Is my use of cell.value inappropriate?
Rest of post deleted as probably no longer relevant
Thanks to all contributors so far!

I have partial but inelegant solution - I hope others can do better than me. I still can't work out why the UK date is being parsed to a US date automatically.
Thanks to all for their help as I could not have got my project to work without your helpful comments.
My partial solution:
Read the data from the CSV, assuming it may have been parsed incorrectly
If day(importDate) <=12 we are at risk of erroneous date import
create an alternate date with transposed days /months from the importDate
Use a vbYesNo MsgBox to get user to confirm which date is correct.
This works for my project as all dates in the single CSV file should be equal, You could use a similar solution of error checking a date for being read in US vs UK format if all dates in your CSV where recorded in the same format. However it will not help others who want to processes multiple CSVs or who may have a CSV with different date formats in it.

An alternative approach would be to read the csv file into an array using a CSV parser that lets you specify the file's date format, and then paste the array into a worksheet.
Here's such a parser (I'm the author):
https://github.com/PGS62/VBA-CSV
In this example, Data is pasted to a new worksheet.
Sub Demo()
Dim Data As Variant
Dim Target As Range
Data = CSVRead("c:\temp\datesukformat.csv", _
ConvertTypes:=True, _
Delimiter:=",", _
DateFormat:="D/M/Y")
Set Target = Application.Workbooks.Add.Worksheets(1).Cells(1, 1).Resize(UBound(Data, 1), UBound(Data, 2))
Target.value = Data
End Sub

Related

Excel vba - resize as date format

I have a line in code:
.Range("D2").Resize(UBound(vef, 1), 1) = vef
Where vef is an array with data. Can I edit this line so it pastes vef in Date format dd.mm.yyyy hh:mm:ss
That should be easy enough. All you need to do is work with the same range and change the numberformat of the cells. Please note: the value being pasted is unchanged (and the values in your array should be dates). Excel applies a formatting so that what is displayed is changed, but without losing the underlying information.
If your array is full of strings, you will want to look at converting them to Dates beforehand. Excel may do this implicitly (e.g it may automatically format them, despite the original values being dates), but explicit conversion is preferred.
For your code, use this:
With .Range("D2").Resize(UBound(vef, 1), 1)
.Value = vef
.Numberformat = "dd.mm.yyyy hh:mm:ss"
End With
Finally, I would just point out that your Ubound(vef, 1) will only work if your array is 1 based. If it is 0 based, your resized range will be off by 1. The formula you can use to find the length of a dimension in an array is:
Length = (Ubound(Array, DimensionNumber) - LBound(Array, DimensionNumber)) + 1
So in your case:
Length = (Ubound(vef, 1) - LBound(vef, 1)) + 1
Good luck!

"Runtime error 13 - Type mismatch" when parsing date from a text cell

I'm importing a .csv file from another program into Excel. The date format is text, formatted as follows :
mm/dd/yy or
07/03/17
The imported file is very unstructured, with more than just dates in the first field.
I want to write 2017-07-03 into the cell (2,13)
Here is the code I'm using
ActiveSheet.Cells(2, 13).Select
ActiveCell.FormulaR1C1 = "=IF(LEN(RC[-12]))=8, _ 'How I identify date
20&MID((RC[-12]),7,2)&" - "& 'To get 2017 4 digit Year
MID((RC[-12]),1,2)&" - "& 'To extract 2 digit month
MID((RC[-12]),4,2)),"""")" 'To extract 2 digit day
This gives me Runtime error 13 - Type mismatch.
I think that my code is causing the error by mixing values and text, but I cannot see where.
The reasons for your error message is due to the formula not being properly created.
It should look like:
Cells(2, 13).FormulaR1C1 = "=IF(LEN(RC[-12])=8, 20 & MID(RC[-12],7,2) & ""-"" & MID(RC[-12],1,2) & ""-"" & MID(RC[-12],4,2),"""")"
Instead of writing formulas to the worksheet, I suggest doing the conversion within VBA and then writing the results to the worksheet. This can make your code easier to understand, debug, and maintain in the future.
The code below could be shortened, but purposely is not so as to provide more clarity. It is written as a macro that will process everything in column A, and write the dates to column M, in the format you specify.
I note that in your question, you specify a format of 2017-07-03, but in your code, you generate a format of 2017 - 07 - 03. I generated the former in the code, but it should be obvious how to change to the latter if that is what you really want.
Also note that in the code I used the default conversion for Excel for 2-digit years, where two digit years are assumed to be in the range 1930 - 2029. That can be changed if necessary.
The code uses a more involved method of assuring the value being converted is truly a date. But it does not check for "illegal" dates and will convert, for example 2/31/17 to 2017-03-03. Your formula method would return the string 2017-02-31 It would be trivial, in the VBA macro, to add code to flag this kind of problem, if it might be an issue.
There are other ways to check for valid dates, including seeing if CDate or VBA's DateValue functions return a date or an error. But these may not work properly across workbooks in different locale's, with different default short date formats in the windows Regional Settings.
Instead of writing the results as text, the results could be written as a real date formatted as you wish with the .numberformat property of the cell (which could be used in future calculations), and that option is in the comments in the macro.
If you require that the result be dynamic, with a formula, the macro could be easily converted into a User Defined Function, but you would have to assure that the cell format is "text" else Excel will try to convert the resultant date into a "real date" (depending on which of the two formats you really want).
Post back with any questions about the code.
Option Explicit
Sub ConvertOnlyDates()
Dim V As Variant
Dim YR As Long, MN As Long, DY As Long
Dim DT As Date
Dim WS As Worksheet
Dim rSrc As Range, C As Range
'Define the range to check: Columns A
'Always best to explicitly define worksheets and cells
' and not rely on ActiveSheet, Activate, Select, etc
Set WS = Worksheets("sheet2")
With WS
Set rSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp))
End With
For Each C In rSrc
'check if a date
V = Split(C.Text, "/")
If UBound(V) = 2 Then
If V(0) > 0 And V(0) <= 12 _
And V(1) > 0 And V(1) <= 31 _
And V(2) >= 0 And V(2) <= 99 Then
MN = V(0)
DY = V(1)
'note that this is Excel's default (at least for now)
YR = V(2) + IIf(V(2) < 30, 2000, 1900)
DT = DateSerial(YR, MN, DY)
'Can be written as text
' or as a real date with proper formatting
' REAL DATE
'With C.Offset(0, 12) 'write in column M
' .NumberFormat = "yyyy-mm-dd"
' .Value = DT
'End With
With C.Offset(0, 12)
.NumberFormat = "#"
.Value = Format(DT, "yyyy-mm-dd")
End With
End If
End If
Next C
End Sub
You haven't appropriately closed your strings with double quotes for each line. Using the continuation character _ doesn't allow you to break a string in the middle. You can do this if you properly concatenate:
ActiveCell.FormulaR1C1 = "=IF(LEN(RC[-12]))=8," & _ 'How I identify date
"20&MID((RC[-12]),7,2)&" - " & _ 'To get 2017 4 digit Year
"MID((RC[-12]),1,2)&" - " & _ 'To extract 2 digit month
"MID((RC[-12]),4,2)),"""")" 'To extract 2 digit day
(Your code will be far more readable if you take the time to indent continued lines in the fashion shown above. You can more quickly and easily pick out the destination variable and the assignment if you follow this format.)

VBA - Converting a mixed row ( Data Type wise) to Date

I have an excel sheet, one of the columns is mixed with Dates and Dates that has been copied to it as text ( see below ).
I dont manage to convert the text type to Date type, i need to do it though VBA to add it to some automation im working on. is there a way to do this at all ?
I noticed excel is looking for format like this 03/09/2016 23:39:57 and it doesn't like 3/21/16 11:07:22 PM, apparently this is my case :) every look i run i get ( obviously data mismatch ), in the image bellow the spoken column is "C"
thx :)
ExcelSheet bad Date format
Assuming wvery bad dates are MM/DD/YYYY, then you could use the following code that I wrote for you:
Sub main()
Dim celda As Range
Dim s_date As String
Dim s_time As String
Dim validate_date As String
Dim valid_date As String
Dim date_arr() As String
Dim rango As Range
Dim limit As Long
limit = Columns("B").Find("", Cells(Rows.Count, "B")).Row - 1
Set rango = ActiveSheet.Range("B2:B" & limit)
' works only for date values, another value would return non expected values
For Each celda In rango
validate_date = Left(celda.Value, 1)
If validate_date <> "" Then
If Not celda.Rows.Hidden Then
If validate_date <> "0" Then
s_date = Trim(Mid(celda.Value, 1, InStr(1, celda.Value, " ") - 1))
s_time = Trim(Mid(celda.Value, InStr(1, celda.Value, " "), Len(celda.Value) - InStr(1, celda.Value, " ")))
date_arr = Split(s_date, "/")
valid_date = date_arr(1)
valid_date = valid_date & "/0" & date_arr(0)
valid_date = valid_date & "/" & date_arr(2)
valid_date = valid_date & " " & s_time
celda.Offset(0, 1).Value = CDate(valid_date)
End If
End If
End If
Next celda
End Sub
In order to use this code you should insert one empty column to the right from target. Second, you should to select entire C column and run the macro.
Edit 1. Ok, this macro autoselect column B. Select column dates is not necessary now.
Excel has parsed the dates according to your Windows Regional Settings short date format. Those that it could not parse (where the month>12) it left as text. Since there was initially a difference between the date format in the text file, and the date format in your Windows Regional settings, it is likely that many of the dates that appear as dates (or as unformatted numbers) were converted incorrectly.
You have a few options:
Import the text file using the Get External Data tab From Text option on the Data Ribbon. This will open up the text import wizard and allow you to specify the date format of the data being imported.
Change your Windows Regional settings short date format to match that in the text file.
Those are probably the two simplest options. The first can be automated through VBA. The second, not so much.

Display last imported week

I have a excel file that imports from weekly files. I can specify the number of week to import in an input box. From this number, I construct the file path and name for that specific week and then imports it.
In cell D1 I want to show the week that was last imported, so if I last Imported week 12, cell D1 should show "12" and if later later I imported a file from a older week like 4, cell D1 should show "4"
In the excel file the source files that will be imported will be trough a vba code, it don't have any type of tables in the source file, so I need to implement this in my code in order to show in the cell D1 the week that as been imported.
I tried implementing this to my code Range("D1").Value = WeekVal in order to try to fill that specific cell with the number of the week that was imported.
I searched online but the ones that I saw didn't apply very well to my code. So I don't really need a date I just need the value from the file name that represents the week, like here :
"Status 496 800 week -->12<-- 2015.xls"
that number is what I need to show in the D1
I don't know how your file's name is referenced so I did a function, here is a routine to test it :
Sub Luis_Montez()
Dim TpS As String, _
ReZ As String
TpS = "Status 496 800 week 12 2015"
ReZ = Get_WeekNb_From_FileName(TpS)
MsgBox "_" & ReZ & "_"
End Sub
And here is the function :
Public Function Get_WeekNb_From_FileName(ByVal FileName As String) As Integer
Dim TpStr As String
TpStr = Trim(Mid(FileName, InStr(1, LCase(FileName), "week") + 4, 3))
Get_WeekNb_From_FileName = CInt(TpStr)
End Function
An even easier solution that in the duplicate, just use :
Format(Date, "ww")
You are probably writing your WeekVal in the D2 cell of another worksheet (for instance the workbook you just imported your data for the week from).
To avoid this, always refer to any cell or range by explicitly naming the workbook and either the named range or the sheet name.
In your case, I advice to select the D1-cell you want to write to. Now left of your formula bar you can read D1. Fill in WeekVal instead. Then change your code to
Thisworkbook.Name("WeekVal").RefersToRange.Value = WeekVal

Excel VBA - Extract the correct dates from badly formatted dates?

I am currently learning VBA programming by doing, and have encountered the below situation with which I would appreciate your help. Ideally not just in finding a solution, but also to understand how and why the solution works.
Say that there is a database from which one can export a spreadsheet of data. One of the columns has date values, but they are badly formatted from the export. The system sends the dates as mm/dd/yyyy hh:mm AM/PM, for example, 04/11/2014 09:24 AM, but the spreadsheet has this identified as dd/mm/..., meaning it enters 04 as the day and 11 as the month.
Within this column, if the day is before or including 12 of the month, the cell is formatted as date. If the day is past the 12th, the cell is formatted with a general format.
My question is, could I write a VBA macro that could reverse the values for day and month and create the correct dates in a new column? I would think that it would first have to identify if a cell is formatted as date, and then somehow extract the date and month in the correct positions, or if it's formatted as a general format, and then use a different code to extract the correct date.
If this is too basic an issue for this community and there's another community more suited, I will gladly repost my question there.
EDIT:
After my comment below I played around with functions and looked for other similar functions that may help do what I need, switch the day value with the month value, and so far I have:
'for dates with general format: 04/14/2014 11:20 AM
=DATE(MID(A1,7,4),LEFT(A1,2),MID(A1,4,2)) 'in a column for the date
=TIME(MID(A1,12,2),MID(A1,15,2),"00") 'in a column for time, since I may need this
'for dates with a date format: 4/11/2014 7:35:00 PM
=DATE(TEXT(A1,"yyyy"),TEXT(A1,"dd"),TEXT(A1,"mm")) 'in a column for the date
=TEXT(A1,"hh:mm AM/PM") 'in a column for time
Now I just need to figure out a conditional function to identify when to apply each set of formulas according to the values or formatting or column A.
But are there equivalent functions to achieve this through VBA? I need these date and time columns to only hold values, not formulas, so that I may export the data out of them directly. And somehow putting this in VBA code seems more "clean" to me, using formulas feels to me like a volatile solution. I'm not sure how to explain this properly, but I'm somehow more confortable with proper coding behind my data manipulation.
EDIT2:
I've resolved the worksheet functions solution as below. It took me a while to figure out how to go around the FIND error with date formatted cells, and only found the IFERROR function by chance in the list Excel suggests when writing =IF.
'to get the correct date
=IF(IFERROR(FIND("/",A1),0)>0,DATE(MID(A1,7,4),LEFT(A1,2),MID(A1,4,2)),DATE(TEXT(A1,"yyyy"),TEXT(A1,"dd"),TEXT(A1,"mm")))
'to get the correct time
=IF(IFERROR(FIND("/",A1),0)>0,TIME(MID(A1,12,2),MID(A1,15,2),"00"),TEXT(A1,"h:mm AM/PM"))
Now at least I have a working solution, but I'm still interested in a VBA translation for these formulas and will continue searching for these.
Check this out. Let's take for example your formula:
=IF(IFERROR(FIND("/",A1),0)>0,DATE(MID(A1,7,4),LEFT(A1,2),MID(A1,4,2)),DATE(TEXT(A1,"yyyy"),TEXT(A1,"dd"),TEXT(A1,"mm")))
VBA equivalent functions:
Find = Instr
Date = DateSerial
Text = Format (not exactly the same but the nearest)
Code equivalent:
Dim mydate As Date
Dim myformat As String
myformat = "mm/dd/yyyy hh:mm AM/PM"
If InStr(1, [A1], "/") > 0 Then
mydate = DateSerial(Mid(Format([A1], myformat), 7, 4), _
Left(Format([A1], myformat), 2), Mid(Format([A1], myformat), 4, 2))
Else
mydate = DateSerial(Year([A1]), Month([A1]), Day([A1]))
End If
[B1] = mydate
Take note that [A1] is a shortcut Evaluate function which can also be written as Evaluate("A1").
I used that to refer to Cell A1 as in your formula. You can use the conventional Range Object reference like this: Range("A1"). I used the shortcut because it looks cleaner. But it is not advisable in huge data sets.
For your time formula:
=IF(IFERROR(FIND("/",A1),0)>0,TIME(MID(A1,12,2),MID(A1,15,2),"00"),TEXT(A1,"h:mm AM/PM"))
Code Equivalent:
Dim mytime As Date
If InStr(1, [A1], "/") > 0 Then
mytime = TimeValue([A1])
Else
'~~> myformat is declared above
mytime = TimeValue(Format([A1], myformat))
End If
[C1] = mytime
You can also check the format of the cell like below:
Select Case True
Case [A1].NumberFormat = "General"
mydate = DateSerial(Year([A1]), Month([A1]), Day([A1]))
mytime = TimeValue(Format([A1], myformat))
Case [A1].NumberFormat = myformat '~~> again this is declared above
mydate = DateSerial(Mid(Format([A1], myformat), 7, 4), _
Left(Format([A1], myformat), 2), Mid(Format([A1], myformat), 4, 2))
mytime = TimeValue([A1])
Case Else
MsgBox "Invalid Format. Cannot be evaluated"
End Select
[B1] = mydate: [C1] = mytime
Not sure if above will really solve your problem.
There are just many possibilities when you extract datetime stamp from a database.
If the scenarios you mentioned are only the problems you encounter, then above solutions might work.
This is now an old thread but in case anyone else stumbles upon it (as I did) with a similar problem, I'm just offering this up.
My suggested VBA function for this is shown below. Its style doesn't strictly follow purist programming practice (declaration of variables, etc); it's written, rather, to be relatively easily comprehensible.
Function Date_Text_Convert( _
date_text As String, _
return_with_month_letters As Boolean, _
return_as_date_time_value As Boolean)
' Patrick S., June 2018
' Intention: to enable mm/dd/yyyy[etc] imported text-string dates
' to be switched to dd/mm/yyyy[etc]. Can be adapted for other cases.
' Usage examples: if cell A2 contains the text-string:
' 06/26/2018 09:24 AM
' then in, for example, cell B2, type:
' =Date_Text_Convert(A2,TRUE,FALSE) or =Date_Text_Convert(A2,FALSE,FALSE)
' which returns:
' 26-Jun-2018 09:24 am or 26/06/2018 09:24 am
' To return a date-and-time value instead of a string, use, for example:
' =Date_Text_Convert(A2,TRUE,TRUE)
' establish the positions where the day and month digits start
daypos = 4
mthpos = 1
rempos = 7 ' starting position of remaining part of the string
' establish the length of the relevant text sections: 2 characters each, in this case
daylen = 2
mthlen = 2
' so that,
daytext = Mid(date_text, daypos, daylen)
mthtext = Mid(date_text, mthpos, mthlen)
remtext = Mid(date_text, rempos, 999) ' the remainder of the text string
' format the output according to 'return_with_month_letters'
' there are 2 options available, each using a different separator
sep_stroke = "/"
sep_hyphen = "-"
If return_with_month_letters = True Then
mthnum = mthtext * 1
mthindex = ((mthnum - 1) * 3) + 1
mthname = Mid("JanFebMarAprMayJunJulAugSepOctNovDec", mthindex, 3)
newtext = daytext & sep_hyphen & mthname & sep_hyphen & LCase(remtext) ' LCase optional
Else
newtext = daytext & sep_stroke & mthtext & sep_stroke & UCase(remtext) ' UCase optional
End If
' finally, return the output through the function name: either as a date, or as the text equivalent
If return_as_date_time_value = True Then
newdate = DateValue(newtext) + TimeValue(newtext)
Date_Text_Convert = newdate
Else
Date_Text_Convert = newtext
End If
End Function