How to convert text to date and sum to time by vba? - vba

how are you? Look, I am opening a tab-delimited file which contains information about the day and the time when a sample was analysed. I'd like to sum time and date, in a way that would be possible to sort lines based on this information.
Time and date are stored as strings, in two different cells, just like:
Date: 29/11/2013
13:41:59:546
Because of this, I had to create a formula to remove "Date: " and to convert ":546" to milliseconds and add to the rest of the number. Unfortunately, even removing "Date: ", I can't convert "29/11/2013" to a number, considering that date is interpreted as a number by excel. It's interesting because with the workbook opened, if I select the cell with 29/11/2013 (after "Date: " being removed) and press F2, then enter, excel converts it to a number. Am I missing something when I am trying to do the same by vba?
My result should be a number that with the appropriate format
.numberformat="dd/mm/yyyy hh:mm:ss.000"
should show 29/11/2013 13:41:59.546.
My function is:
Function DateTimeCustomFormat(TimeCell As Range, DateCell As Range, formatTime As String, formatDate As String)
Dim ms As Double 'Means milliseconds
Dim msTOday As Long 'Factor to convert ms to day (ms divided by all milliseconds in a day)
msTOday = 86400000
Select Case formatTime
Case "hh:mm:ss:ms(xxx)"
ms = Val(Right(TimeCell, 3)) / msTOday
TimeCell = Left(TimeCell, Len(TimeCell) - 4)
TimeCell.NumberFormat = "0.00" '"h:mm:ss.000"
TimeCell = TimeCell + ms
End Select
Select Case formatDate
Case "Date: dd/mm/yyyy"
DateCell = Right(DateCell, Len(DateCell) - 6)
DateCell.NumberFormat = "dd:mm:aaaa"
'DateCell = DateCell.Value * 1
End Select
DateTimeCustomFormat = TimeCell + DateCell
End Function

Seems as if you not really familiar with object datatypes such as Range and with what can a user defined function do and what not.
In your code you get the parameter TimeCell as Range but then you overwrite this with a string (TimeCell = Left(TimeCell, Len(TimeCell) - 4)) and then you try setting NumberFormat to this. A string has no NumberFormat ;-). Furthermore a user defined function cannot set NumberFormat to cells and it also cannot set cell values, if this was the goal of the above code line. It only can return a value. This value gets then the value of the cell, which contains the user defined function as a formula.
Same Problem with DateCell.
Your code should get the parts of the cell values which represents the time or the date as strings and then convert them to dates. Therefor some functions are useable.
Easy to use are TimeValue and DateValue. But these functions depend on the system settings for date and time format. So it may be that they don't get the right values. For the date for example with "06/07/2014" is not really clear if it is July the 06. or June the 07. This depends on the system date format settings.
A more general solution is to use TimeSerial and DateSerial. This is the better solution in my opinion because the formates are precisely defined.
Function DateTimeCustomFormat(TimeCell As Range, DateCell As Range, formatTime As String, formatDate As String) As Date
Dim ms As Double 'Means milliseconds
Dim msTOday As Long 'Factor to convert ms to day (ms divided by all milliseconds in a day)
Dim sTime As String, sDate As String 'String parts of the given parameters
Dim dTime As Date, dDate As Date 'Calculated datetime values of the given parameters
msTOday = 86400000
Select Case formatTime
Case "hh:mm:ss:ms(xxx)"
ms = Val(Right(TimeCell.Value, 3)) / msTOday
sTime = Left(TimeCell.Value, Len(TimeCell.Value) - 4)
'dTime = TimeValue(sTime) + ms 'please read help for TimeValue
dTime = TimeSerial(Left(sTime, 2), Mid(sTime, 4, 2), Mid(sTime, 7, 2)) + ms
Case Else
dTime = 0
End Select
Select Case formatDate
Case "Date: dd/mm/yyyy"
sDate = Right(DateCell.Value, Len(DateCell.Value) - 6)
'dDate = DateValue(sDate) 'please read help for DateValue
dDate = DateSerial(Right(sDate, 4), Mid(sDate, 4, 2), Left(sDate, 2))
Case Else
dDate = 0
End Select
DateTimeCustomFormat = dTime + dDate
End Function
Using as a UDF (user defined function):

Related

Getting the date format in VBA in the correct form

I am trying to reference a file that has the date of the previous Friday at the end in the form of mm.dd.yy.
I need to now take that date and add it to the end of a string, to end of a string in order to open select the other workbook. This is what I have right now.
File Name:
Submittals Wk Ending 06.02.17.xlsx
This is what I have so far
Dim wrbk As String
Dim weekdate As String
range("a1").value="=TODAY()-WEEKDAY(TODAY())-1"
weekdate = Range("a1").Value
'range("b1").value="06.02.17"
'weekdate = Range("b1").Value
msgbox weekdate 'use to check what the date format is
wrbk = "Submittals Wk Ending " & weekdate
Windows(wrbk & ".xlsx").Activate
When I read it from B2 with the typed in format of 06.02.17 it works, however no matter what I do, I cannot get it to read it from A1 because it changes the format to m/d/yyyy. I have tried to copy it and paste as value. Nothing seems to work.
I have the other workbook open as well when I try to run it.
Any ideas? Thanks!
To get the previous Friday of any date, try below UDF. This should work fine if the Date NumberFormat is same as your System's Date format. The key is the CDate() which converts according to System's Date format which Office apps defaults to.
Option Explicit
Function GetLastFridayDate(AnyDate As Variant) As Date
Dim dInput As Date, dLastFriday As Date
dInput = CDate(AnyDate)
dLastFriday = dInput - Weekday(dInput) + vbFriday - IIf(Weekday(dInput) > vbFriday, 0, 7)
GetLastFridayDate = dLastFriday
End Function
Try
Range("A1").Value = Format$(Date - Weekday(Date) - 1, "MM.DD.YY")

Excel VBA importing .txt file results in wrong date format

I'm importing a text file (not .csv, a .txt) which is tab delimited. The first column contains dates, some are in dd/mm/yyyy format, others are in dd/mm/yyyy hh:mm:ss format.
When running the following code a few of the dates come out in mm/dd/yyyy format. There is nothing unusual about the ones that do, it appears to happen at random (some have the time, some dont but either way the source is still day-month-year)
Sub LQMTrend()
Dim fp, textLine As String
Dim iRow As Integer
Dim lineArr() As String
Dim ws As Worksheet
Set ws = Sheets("Data")
iRow = 1
fp = "//srv57data1\product_support\xChange\Beam Profile Image Tool\LQM Reviews\Log files\Log file.txt"
Open fp For Input As #1
Do Until EOF(1)
Line Input #1, textLine
lineArr = Split(textLine, vbTab)
For x = 0 To UBound(lineArr)
ws.Cells(iRow, x + 1) = lineArr(x)
Next x
iRow = iRow + 1
Loop
Close #1
I've tried declaring lineArr as a variant but it makes no difference. Any ideas?
Thanks
Edit: I appreciate this is similar to Excel VBA: importing CSV with dates as dd/mm/yyyy but the simplest answers are different in each case - for CSV files the 'use local date setting' import option solves the problem, this is not available when opening .txt files, the date must be converted on the fly using CDate or similar. Hope this helps clarify.
Excel first tries to convert a date string to the format of the local setting. If it fails, like when the month is superior to 12, it will then inverse the month and the days. Since you are dealing with the two types of formats, you best option is probably to parse the dates yourself:
Sub Macro1()
Dim arr() As String
Dim mydate As Date
' US format 6 may 2015
arr = Split("05/06/2015", "/")
mydate = DateSerial(Year:=arr(2), Month:=arr(0), Day:=arr(1))
' UK format 6 may 2015
arr = Split("06/05/2015", "/")
mydate = DateSerial(Year:=arr(2), Month:=arr(1), Day:=arr(0))
End Sub
As Assylias mentioned, some dates can be ambiguous. To Excel a date is nothing but a formatted number which represents the number of days since 01/01/1900, today (March 3, 2016) to excel is nothing more than 42447. When using the number, there can be no ambiguity about the date format.
I Suggest changing
ws.Cells(iRow, x + 1) = lineArr(x)
to
With ws.Cells(iRow, x + 1)
If x = 0 Then
.Value = CLng(CDate(lineArr(x)))
.NumberFormat = "mm/dd/yyyy;#"
Else
.Value = lineArr(x)
End If
End With
You need to convert your string date to a "real date" (serial number) before writing it to your worksheet. Here is one way to do that. Alter the array element to reflect the proper column in your original file.
'convert date to proper date before writing to worksheet
'Assuming date is in column 3
Dim DT As Date, TM As Date, V As Variant
V = Split(lineArr(2))
'is there a time segment
If UBound(V) = 1 Then
TM = TimeValue(V(1))
Else
TM = 0
End If
'convert the date segment to a real date
V = Split(V(0), "/")
DT = DateSerial(V(2), V(1), V(0)) + TM
'write it back to linearr
lineArr(2) = DT
You should change the date AFTER you do the import. A text file, like a CSV file, has no formatting whatsoever.

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

Adding parts of a second to time

I am writing an excel VBA script, and I would like to add parts of a second to a time extracted from a cell.
I am using the time function to divide the content by 2, and then convert using the Time function, and add the result to the current time.
tt = Time(0, 0, Cells(1, 12) / 2)
I get a type mismatch error, as the result of the division is not an integer.
what I would like to do at the end is to perform the following addition.
newTime = timeCurrent + tt
Is there any alternative to the Time function to be used in this case ?
Consider:
Sub TimeOnMyHands()
Dim tt As Date
tt = TimeSerial(0, 0, CInt(Cells(1, 12).Value / 2))
MsgBox Format(tt, "hh:mm:ss.000")
End Sub
assuming the cell contains seconds as a simple number rather than a time value.

MS Excel: Finding the earliest date on a row of mixed content

Does anyone know how to get the earliest date on a row in Microsoft Excel. There is not a predictable number of columns on each row, and there are values other than dates which need to be ignored. It could be done with Excel formulas or VBA.
Does anyone have any suggestions?
Right now I am using this quick and dirty VBA function, but when I loaded new input data (approx 200 rows by 100 columns) a message box came up saying that Excel does not have enough resources to process my changes.
' returns smallest date on row
Public Function getSmallestDateFromRow(r As Integer, sheetName As String) As Date
Dim toReturn As Date
Dim rng As Range
Dim scanToColumn As Integer
Dim c As Integer
Dim tmp As Variant
Set rng = Sheets(sheetName).Cells.Find("*", [a1], , , xlByColumns, xlPrevious) 'is this inefficient?
scanToColumn = rng.Column
toReturn = #12/12/2100#
For c = 1 To scanToColumn
tmp = Sheets(sheetName).Cells(r, c).Value
If (IsDate(tmp)) Then
If (toReturn = Null) Then
toReturn = tmp
ElseIf (toReturn > tmp) Then
toReturn = tmp
End If
End If
Next c
If (toReturn = #12/12/2100#) Then
toReturn = 0
End If
getSmallestDateFromRow = toReturn
End Function
You have to remember that Excel (and many other Microsoft products) store dates as floating-point numbers:
The integer part is the count of days elapsed since January 1st, 1900 (e.g.: 1 is equivalent to 1/1/1900)
The decimal part is the 'fraction' of day elapsed (e.g.: 0.5 is equivalent to 12:00 pm)
The question on how to find a minimum or maximum date value is then obfuscated by the fact that you may have many other numbers in your row. So you have to define first a "valid rank" for the dates. After that, a simple "array formula" can do the trick:
Example. Let's say your valid range is between January 1st, 2000 and December 31st, 2100. Then your valid "number rank" is:
1/1/2000 is equivalent to 36526
12/31/2100 is equivalent to 73415
Now you can write the function to track the minimum date value within this range:
function trackMinimum(rowRange as range) as date
on error resume next
dim j as integer, minValue as date
dim t0 as double, t1 as double
dim ans as date
t0 = cdbl(dateserial(2000,1,1))
t1 = cdbl(dateserial(2100,12,31))
ans = 0
for j = 1 to rowRange.columns.count
if ans = 0 then ' You need to store the first valid value
if rowRange.cells(1,j).value >= t0 and rowRange.cells(1,j) <= t1 then
ans = rowRange.cells(1,j).value
end if
else
if (rowRange.cells(1,j).value >= t0 and rowRange.cells(1,j) <= t1) _
and rowRange.cells.value < ans then
ans = rowRange.cells(1,j).value
end if
end if
next j
trackMinimum = ans
end function
Of course I am assuming that the rowRange parameter is a single row range.
Hope this helps you
By the way, the isDate() function is not "fail safe": It must take a valid expression (text) to detect if it is a date or not, and that may depend on formatting. That said, you may prefer to use isDate(.cells(r,c).Text)' instead of.value, since theValue` property may return a double or floating-point value that can be evaluated as "not a date".