how to get the microsecond portion of GetSystemTimePreciseAsFileTime/ " cLngLng of FileTime" - excel

Spring boarding off of Gustav's comments(Thanks, G) and another hour of searching, if someone can get me started on how to convert filetime to VBA LongLong, I'm confident I can handle it from there. There are examples of C++ FileTime two part variable to Quadword etc, but I know nothing about C++ so cannot mimic this in VBA.
Example in C++ is Stack Overflow question: FILETIME to __int64, scroll down to "Performing Arithmetic with File Times"
Essentially if I can get a custom cLngLng in VBA to handle FileTime input, I should be good to go.
Original Post:
I'm looking at accuracy of various time stamps in VBA, but hit a roadblock on using GetSystemTimePreciseAsFileTime. Apparently it gives a UTC type timestamp with a resolution of 100nSec in FileTime format. I cant figure out how to extract the last four decimal places of the seconds portion. If I use FileTimeToSystemTime it truncates the time to S.000 accuracy. I want to look at the rest of the GetSystemTimePreciseAsFileTime time stamp, i.e look at the seconds portion to the full S.0000000 resolution.
I was hoping I could extract the data and insert into Excel spreadsheet to let me figure this out..code works in saving the timing data, but I'm stuck. You can see the changes to FileTime.dwLowDate when you convert from file time to system time back to file time...is there a way to look at the changes to dwLowDate that tell you what the last four decimal places are?
Code so far is below. Plus of course the appropriate declarations and kernal32 subs and functions.
Some of my reading suggests I might have to use "quadpart", whatever that is, as part of my calculations. Haven't been able to find out how to use quadpart or even if it exists in VBA.
Can someone steer me in the right direction to get the full resolution of the GetSystemTimePreciseAsFileTime?
Sub TestPrecisionTime()
Dim FileTimePercision As FileTime
Dim FileTimeBack As FileTime
Dim LocalSystemTime As SYSTEMTIME
Dim res As Long
GetSystemTimePreciseAsFileTime FileTimePercision 'Get the precision 100nSec system time
'convert it to System Time
res = FileTimeToSystemTime(lpFileTime:=FileTimePercision, lpSystemTime:=LocalSystemTime)
'Convert it back to FileTime to see what has changed from FileTimePercision.dwLowDateTime
res = SystemTimeToFileTime(lpSystemTime:=LocalSystemTime, lpFileTime:=FileTimeBack)
'Save the results so can try to develop algorithm to extract the last four digits of the time
'seconds down to 100 nsec, so S.0000000 "format", System time only gives S.000 "format"
Dim appAnalysis As Application
Dim wbPrecisionTime As Workbook
Dim wsPrecise As Worksheet
Dim sControlWB As String
If appAnalysis Is Nothing Then Set appAnalysis = Application
sControlWB = ActiveWorkbook.Name
If wbPrecisionTime Is Nothing Then Set wbPrecisionTime = appAnalysis.Workbooks(sControlWB)
If wsPrecise Is Nothing Then Set wsPrecise = wbPrecisionTime.Sheets("Precise")
With wsPrecise
.Range("A2") = FileTimePercision.dwLowDateTime
.Range("B2") = FileTimePercision.dwHighDateTime
.Range("C2") = FileTimeBack.dwLowDateTime
.Range("D2") = FileTimeBack.dwHighDateTime
.Range("E2") = LocalSystemTime.wYear
.Range("F2") = LocalSystemTime.wMonth
.Range("G2") = LocalSystemTime.wDayOfWeek
.Range("H2") = LocalSystemTime.wDay
.Range("I2") = LocalSystemTime.wHour
.Range("J2") = LocalSystemTime.wMinute
.Range("K2") = LocalSystemTime.wSecond
.Range("L2") = LocalSystemTime.wMilliseconds
End With
End Sub

To preserve the high resolution, you must use a FILETIME structure.
As you have found, that page mentions using FileTimeToSystemTime to obtain an "easy to display" value, but it forgets to tell, that this can only hold down to milliseconds.
So, I guess, you will have write your own function to convert/display the returned FILETIME value including microseconds. To hold the value, data type DateTime may not do as it is limited to milliseconds, though it can hold microseconds in a limited range (see VBA.Date):
' Interval with minimum one microsecond resolution.
Public Const MaxMicrosecondDateValue As Date = #5/18/1927#
Public Const MinMicrosecondDateValue As Date = #8/13/1872#
Better suited in VBA will be DateTime2. You could also convert to ticks and use data type BigInt.
Addendum:
This answer, Convert from a Windows filetime to a DateTime, explains the relation between ticks and the DateTime of .Net.
The functions DateDotNet and DotNet from module DateSpan shows how to convert between ticks and DateTime of VBA.

Related

Clean data in excel that comes in varying formats

I have an excel table that contain values in these formats. The tables span over 30000 entries.
I need to clean this data so that only the numbers directly after V- are left. This would mean that when the value is SV-51140r3_rule, V-4407..., I would only want 4407 to remain and when the value is SV-245744r822811_rule, I would only want 245744 to remain. I have about 10 formulas that can handle these variations, but it requires a lot of manual labor. I've also used the text to column feature of excel to clean this data as well, but it takes about 30 minutes to an hour to go through the whole document. I'm looking for ways that I can streamline this process so that one formula or function can handle all of these different variations. I'm open to using VBA but don't have a whole lot of experience with it and I am unable to use Pandas or any IDE or programming language. Help please!!
I've used text to columns to clean data that way and I've used a variation of this formula
=IFERROR(RIGHT(A631,LEN(A631)-FIND("#",SUBSTITUTE(A631,"-","#",LEN(A631)-LEN(SUBSTITUTE(A631,"-",""))))),A631)
Depending on your version of Excel, either of these should work. If you have the ability to use the Let function, it will improve your performance, as this outstanding article articulates.
If you're on a really old version of excel, you'll need to hit ctl shift enter to make array formula work.
While these look daunting, all these functions are doing is finding the last V (by this function) =SUBSTITUTE(RIGHT(SUBSTITUTE(A2,"V",REPT("🍄",999)),999),"🍄","") and then looping through each character and only returning numbers.
Obviously the mushroom 🍄 could be any character that one would consider improbable to appear in the actual data.
Old School
=TEXTJOIN("",TRUE,IF(ISNUMBER(MID(MID(SUBSTITUTE(RIGHT(SUBSTITUTE(A2,"V",REPT("🍄",999)),999),"🍄",""),
FIND("-",SUBSTITUTE(RIGHT(SUBSTITUTE(A2,"V",REPT("🍄",999)),999),"🍄","")),9^9),
FILTER(COLUMN($1:$1),COLUMN($1:$1)<=LEN(MID(SUBSTITUTE(RIGHT(SUBSTITUTE(A2,"V",REPT("🍄",999)),999),"🍄",""),
FIND("-",SUBSTITUTE(RIGHT(SUBSTITUTE(A2,"V",REPT("🍄",999)),999),"🍄","")),9^9))),1)+0),
MID(MID(SUBSTITUTE(RIGHT(SUBSTITUTE(A2,"V",REPT("🍄",999)),999),"🍄",""),
FIND("-",SUBSTITUTE(RIGHT(SUBSTITUTE(A2,"V",REPT("🍄",999)),999),"🍄","")),9^9),
FILTER(COLUMN($1:$1),COLUMN($1:$1)<=LEN(MID(SUBSTITUTE(RIGHT(SUBSTITUTE(A2,"V",REPT("🍄",999)),999),"🍄",""),
FIND("-",SUBSTITUTE(RIGHT(SUBSTITUTE(A2,"V",REPT("🍄",999)),999),"🍄","")),9^9))),1),""))
Let Function
(use this if you can)
=LET(zText,SUBSTITUTE(RIGHT(SUBSTITUTE(A2,"V",REPT("🍄",999)),999),"🍄",""),
TEXTJOIN("",TRUE,IF(ISNUMBER(MID(MID(zText,FIND("-",zText),9^9),
FILTER(COLUMN($1:$1),COLUMN($1:$1)<=LEN(MID(zText,FIND("-",zText),9^9))),1)+0),
MID(MID(zText,FIND("-",zText),9^9),
FILTER(COLUMN($1:$1),COLUMN($1:$1)<=LEN(MID(zText,FIND("-",zText),9^9))),1),"")))
VBA Custom Function
You could also use a VBA custom function to accomplish what you want.
Function getNumbersAfterCharcter(aCell As Range, aCharacter As String) As String
Const errorValue = "#NoValuesInText"
Dim i As Long, theValue As String
For i = Len(aCell.Value) To 1 Step -1
theValue = Mid(aCell.Value, i, 1)
If IsNumeric(theValue) Then
getNumbersAfterCharcter = Mid(aCell.Value, i, 1) & getNumbersAfterCharcter
ElseIf theValue = aCharacter Then
Exit Function
End If
Next i
If getNumbersAfterCharcter = "" Then getNumbersAfterCharcter = errorValue
End Function

Convert Dropdownlist String Value to Date

I want to do a date comparison to check whether is the Before Period is bigger than After Periode
So far it has been working properly until the date range is a bit tricky
For example
The value is from a dropdownlist item
Before period is 21-10-2022
After period is 04-11-2022
It will trigger the error message I set if the before period is bigger than the period after
I have a code like this
If CDate(ddlPeriodeBefore.SelectedValue) <= CDate(ddlPeriodeBefore.SelectedValue) Then
'Does the job if the the before period is smaller than after period
Else
lblInfo.Text = "Period BEFORE Must Be SMALLER Than Period AFTER."
End If
Can anyone help me? it keeps saying "conversion from string to date is not valid"
I've tried datetime.parse, parse exact, cdate, convert.todatetime but nothing works so far, or maybe I used it the wrong way
Please help, thanks in advance
Tim Schmelter's suggestion is not wrong, but I did want to provide an alternative. Create a collection of Tuple, add the dates (both formatted string and native value) to the collection, then bind the control to the list.
Here is the alternative:
Dim dates As New List(Of Tuple(Of String, DateTime))()
Dim today = DateTime.Today
For daysSubtract = 90 To 0 Step -1
Dim dateToAdd = today.AddDays(-daysSubtract)
dates.Add(New Tuple(Of String, DateTime)(dateToAdd.ToString("dd-MM-yyyy"), dateToAdd))
Next
ddlPeriodeBefore.ValueMember = "Item1"
ddlPeriodeBefore.DisplayMember = "Item2"
ddlPeriodeBefore.DataSource = dates
ddlPeriodeAfter.ValueMember = "Item1"
ddlPeriodeAfter.DisplayMember = "Item2"
ddlPeriodeAfter.DataSource = dates
Now when you go to get the selected value, you can use a simpler conversion since the stored object is already a DateTime:
Dim beforePeriod = DirectCast(ddlPeriodeBefore.SelectedValue, DateTime)
Dim afterPeriod = DirectCast(ddlPeriodeAfter.SelectedValue, DateTime)
If (beforePeriod <= afterPeriod) Then
' ...
Else
lblInfo.Text = "Period BEFORE Must Be SMALLER Than Period AFTER."
End If
The advantage to this approach is that you do not have to refactor your code if the date formatting changes.
If DateTime.Parse works depends on your curren culture because you don't pass any. For me this works fine (but for you not):
Dim selectedValue1 = "21-10-2022"
Dim selectedValue2 = "04-11-2022"
Dim beforePeriod = Date.Parse(selectedValue1)
Dim afterPeriod = Date.Parse(selectedValue2)
So either you know the correct culture that you have used when you have created this string, then you can pass this as second parameter to Date.Parse, or you use ParseExact:
Dim beforePeriod = Date.ParseExact(selectedValue1, "dd-MM-yyyy", CultureInfo.InvariantCulture)
Dim afterPeriod = Date.ParseExact(selectedValue2, "dd-MM-yyyy", CultureInfo.InvariantCulture)
The culture is relevant because they can have different date separators and the order of the day-, month- and year-tokens are different.

Comparing Best Times not always working correctly and not sure why

This is for VB.NET 2017. I am creating a program and keeping score with the best time. The timer in the program runs like a stopwatch. I would like at the end to compare the best record from a past game with the current time of a new game. If the new game has a faster time then I would like to replace it on the data file. I can do that just fine but sometimes it will put a slower time ahead of the fastest time. I have tried multiple scenarios and cannot get it consistent. If anyone could help that would be appreciated very much. I have some message boxes so I can see some outcomes. They will be commented out later when it is working properly.
If TotalBalls = 2 And SelectPoison = 2 Then
tmrTime.Enabled = False
CurrentScore = lblTime.Text
MsgBox("You win.")
'''''''''''''''''''''''''''''''''''''''''''''''''''
' CurrentScore = lblTime.Text
MsgBox("Current Time is " & CurrentScore)
NewScoreCheck = String.Compare(CurrentScore, RecordHighScore)
MsgBox(NEwScoreCheck)
'NewScoreCheck will be less than 0 if CurrentScore is less (alphabetically) than RecordhighScore
'NewScoreCheck will be greater than 0 if RecordHighSchore is greater than CurrentScore
If NewScoreCheck < 0 Then
MsgBox(CurrentScore)
Try
MsgBox("In the try statement. Writing new time")
Dim FileWrite As System.IO.StreamWriter
FileWrite = New System.IO.StreamWriter("PoisonHighScore.TXT", False)
FileWrite.WriteLine(CurrentScore)
FileWrite.Close()
Catch
MsgBox("Saving error")
End Try
Else
MsgBox("Not the fastest time.")
End If
Else
MsgBox("You lose.")
End If
strExit = MsgBox("Do you want to play again?", vbYesNo)
If strExit = vbYes Then
Application.Restart()
End If
End Sub
Edit 1: I am using some variables as TimeSpan which is why I have the values CurrentScore and RecordHighScore as Strings. When I am using TimeSpan it will not store as an Integer and will return an error. I am looking for a way to compare two times but need to store them in such a way that they can be compared which is why I used the compare string method mentioned above. I understand after looking at the solution below as to why I cannot. My question now becomes how do I store them since it cannot be stored as double, single, or integer?
To make it a little more clear think of two racers who finish with two different times and those times being unpredictable. The fastest time would win and we would write to the text file (which I know how to do) the time of the winner.
P.S. I have also tried the CInt(CurrentScore) < CInt(RecordHighScore) but that just returns an error too. Any help again would be greatly appreciated and thank you for taking the time to help me with this.
Right off the bat it looks like you're doing some implicit type conversions such as:
CurrentScore = lblTime.Text
Presumably CurrentScore is a numeric data type (like an Integer or Double), but you're setting the value equal to a String. To correct those errors, turn Option Strict on. Looking even deeper, this appears to be your problem because you use the String.Compare method to compare the scores alphabetically. To give you an example, String.Compare returns -1 when you pass 1112 and 121 as your current score and high score respectively, but obviously 121 is quicker than 1112.
What you need to do is convert all numeric values as numeric data types and then compare them using the appropriate comparison operator.
If you want the Timer to behave like a stopwatch then why not use a StopWatch? If you use a StopWatch, you can get the ElapsedMilliseconds which returns a long. The Stop method only pauses the timer; you need to call the Reset method to reset the StopWatch to zero. Call this after you collect the ElapseMilliseconds into a variable.
NewScoreCheck = String.Compare(CurrentScore, RecordHighScore) Strings are not compared in the same way numbers are.
Dim a As String = "72"
Dim b As String = "100"
If String.Compare(a, b) < 0 Then
MessageBox.Show("a comes first")
Else
MessageBox.Show("b comes First")
End If
Result b comes first!
Using a MsgBox to check values is not a great idea. Visual Studio has all sorts of great debugging tools. Inevidibly you will forget to remove a MsgBox; I have :-). Use Debug.Print which will not be in the release version.
Dim sw As New Stopwatch()
Private Sub BeginGame()
sw.Start()
End Sub
Private Sub OPCode2()
Dim TimeInMilliseconds As Long = sw.ElapsedMilliseconds
Dim TotalBalls As Integer = 2
Dim SelectPoison As Integer = 2
Dim RecordHighScore As Long
Dim CurrentScore As Long
If TotalBalls = 2 And SelectPoison = 2 Then
sw.Stop()
CurrentScore = sw.ElapsedMilliseconds
sw.Reset() 'So you can play again and get a new time
MsgBox("You win.")
'''''''''''''''''''''''''''''''''''''''''''''''''''
Debug.Print($"Current Time is {CurrentScore}")
If CurrentScore > RecordHighScore Then
Try
Debug.Print("In the try statement. Writing new time")
Dim FileWrite As System.IO.StreamWriter
FileWrite = New System.IO.StreamWriter("PoisonHighScore.TXT", False)
FileWrite.WriteLine(CurrentScore.ToString)
FileWrite.Close()
Catch
MsgBox("Saving error")
End Try
Else
MsgBox("Not the fastest time.")
End If
Else
MsgBox("You lose.")
End If
Dim strExit As MsgBoxResult
strExit = MsgBox("Do you want to play again?", vbYesNo)
If strExit = vbYes Then
'Not a good way to do this, clear your variables and UI
Application.Restart()
End If
End Sub

Excel UDF calculation should return 'original' value

I have created a VSTO plugin with my own RTD implementation that I am calling from my Excel sheets. To avoid having to use the full-fledged RTD syntax in the cells, I have created a UDF that hides that API from the sheet.
The RTD server I created can be enabled and disabled through a button in a custom Ribbon component.
The behavior I want to achieve is as follows:
If the server is disabled and a reference to my function is entered in a cell, I want the cell to display Disabled.
If the server is disabled, but the function had been entered in a cell when it was enabled (and the cell thus displays a value), I want the cell to keep displaying that value.
If the server is enabled, I want the cell to display Loading.
Sounds easy enough. Here is an example of the - non functional - code:
Public Function RetrieveData(id as Long)
Dim result as String
// This returns either 'Disabled' or 'Loading'
result = Application.Worksheet.Function.RTD("SERVERNAME", "", id)
RetrieveData = result
If(result = "Disabled") Then
// Obviously, this recurses (and fails), so that's not an option
If(Not IsEmpty(Application.Caller.Value2)) Then
// So does this
RetrieveData = Application.Caller.Value2
End If
End If
End Function
The function will be called in thousands of cells, so storing the 'original' values in another data structure would be a major overhead and I would like to avoid it. Also, the RTD server does not know the values, since it also does not keep a history of it, more or less for the same reason.
I was thinking that there might be some way to exit the function which would force it to not change the displayed value, but so far I have been unable to find anything like that.
EDIT:
Due to popular demand, some additional info on why I want to do all this:
As I said, the function will be called in thousands of cells and the RTD server needs to retrieve quite a bit of information. This can be quite hard on both network and CPU. To allow the user to decide for himself whether he wants this load on his machine, they can disable the updates from the server. In that case, they should still be able to calculate the sheets with the values currently in the fields, yet no updates are pushed into them. Once new data is required, the server can be enabled and the fields will be updated.
Again, since we are talking about quite a bit of data here, I would rather not store it somewhere in the sheet. Plus, the data should be usable even if the workbook is closed and loaded again.
Different tack=new answer.
A few things I've discovered the hard way, that you might find useful:
1.
In a UDF, returning the RTD call like this
' excel equivalent: =RTD("GeodesiX.RTD",,"status","Tokyo")
result = excel.WorksheetFunction.rtd( _
"GeodesiX.RTD", _
Nothing, _
"geocode", _
request, _
location)
behaves as if you'd inserted the commented function in the cell, and NOT the value returned by the RTD. In other words, "result" is an object of type "RTD-function-call" and not the RTD's answer. Conversely, doing this:
' excel equivalent: =RTD("GeodesiX.RTD",,"status","Tokyo")
result = excel.WorksheetFunction.rtd( _
"GeodesiX.RTD", _
Nothing, _
"geocode", _
request, _
location).ToDouble ' or ToString or whetever
returns the actual value, equivalent to typing "3.1418" in the cell. This is an important difference; in the first case the cell continues to participate in RTD feeding, in the second case it just gets a constant value. This might be a solution for you.
2.
MS VSTO makes it look as though writing an Office Addin is a piece of cake... until you actually try to build an industrial, distributable solution. Getting all the privileges and authorities right for a Setup is a nightmare, and it gets exponentially worse if you have the bright idea of supporting more than one version of Excel. I've been using Addin Express for some years. It hides all this MS nastiness and let's me focus on coding my addin. Their support is first-rate too, worth a look. (No, I am not affiliated or anything like that).
3.
Be aware that Excel can and will call Connect / RefreshData / RTD at any time, even when you're in the middle of something - there's some subtle multi-tasking going on behind the scenes. You'll need to decorate your code with the appropriate Synclock blocks to protect your data structures.
4.
When you receive data (presumably asynchronously on a separate thread) you absolutely MUST callback Excel on the thread on which you were intially called (by Excel). If you don't, it'll work fine for a while and then you'll start getting mysterious, unsolvable crashes and worse, orphan Excels in the background. Here's an example of the relevant code to do this:
Imports System.Threading
...
Private _Context As SynchronizationContext = Nothing
...
Sub New
_Context = SynchronizationContext.Current
If _Context Is Nothing Then
_Context = New SynchronizationContext ' try valiantly to continue
End If
...
Private Delegate Sub CallBackDelegate(ByVal GeodesicCompleted)
Private Sub GeodesicComplete(ByVal query As Query) _
Handles geodesic.Completed ' Called by asynchronous thread
Dim cbd As New CallBackDelegate(AddressOf GeodesicCompleted)
_Context.Post(Function() cbd.DynamicInvoke(query), Nothing)
End Sub
Private Sub GeodesicCompleted(ByVal query As Query)
SyncLock query
If query.Status = "OK" Then
Select Case query.Type
Case Geodesics.Query.QueryType.Directions
GeodesicCompletedTravel(query)
Case Geodesics.Query.QueryType.Geocode
GeodesicCompletedGeocode(query)
End Select
End If
' If it's not resolved, it stays "queued",
' so as never to enter the queue again in this session
query.Queued = Not query.Resolved
End SyncLock
For Each topic As AddinExpress.RTD.ADXRTDTopic In query.Topics
AddinExpress.RTD.ADXRTDServerModule.CurrentInstance.UpdateTopic(topic)
Next
End Sub
5.
I've done something apparently akin to what you're asking in this addin. There, I asynchronously fetch geocode data from Google and serve it up with an RTD shadowed by a UDF. As the call to GoogleMaps is very expensive, I tried 101 ways and several month's of evenings to keep the value in the cell, like what you're attempting, without success. I haven't timed anything, but my gut feeling is that a call to Excel like "Application.Caller.Value" is an order of magnitude slower than a dictionary lookup.
In the end I created a cache component which saves and re-loads values already obtained from a very-hidden spreadsheet which I create on the fly in Workbook OnSave. The data is stored in a Dictionary(of string, myQuery), where each myQuery holds all the relevant info.
It works well, fulfils the requirement for working offline and even for 20'000+ formulas it appears instantaneous.
HTH.
Edit: Out of curiosity, I tested my hunch that calling Excel is much more expensive than doing a dictionary lookup. It turns out that not only was the hunch correct, but frighteningly so.
Public Sub TimeTest()
Dim sw As New Stopwatch
Dim row As Integer
Dim val As Object
Dim sheet As Microsoft.Office.Interop.Excel.Worksheet
Dim dict As New Dictionary(Of Integer, Integer)
Const iterations As Integer = 100000
Const elements As Integer = 10000
For i = 1 To elements + 1
dict.Add(i, i)
Next
sheet = _ExcelWorkbook.ActiveSheet
sw.Reset()
sw.Start()
For i As Integer = 1 To iterations
row = 1 + Rnd() * elements
Next
sw.Stop()
Debug.WriteLine("Empty loop " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")
sw.Reset()
sw.Start()
For i As Integer = 1 To iterations
row = 1 + Rnd() * elements
val = sheet.Cells(row, 1).value
Next
sw.Stop()
Debug.WriteLine("Get cell value " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")
sw.Reset()
sw.Start()
For i As Integer = 1 To iterations
row = 1 + Rnd() * elements
val = dict(row)
Next
sw.Stop()
Debug.WriteLine("Get dict value " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")
End Sub
Results:
Empty loop 0.07 uS
Get cell value 899.77 uS
Get dict value 0.15 uS
Looking up a value in a 10'000 element Dictionary(Of Integer, Integer) is over 11'000 times faster than fetching a cell value from Excel.
Q.E.D.
Maybe... Try making your UDF wrapper function non-volatile, that way it won't get called unless one of its arguments changes.
This might be a problem when you enable the server, you'll have to trick Excel into calling your UDF again, it depends on what you're trying to do.
Perhaps explain the complete function you're trying to implement?
You could try Application.Caller.Text This has the drawback of returning the formatted value from the rendering layer as text, but seems to avoid the circular reference problem.Note: I have not tested this hack under all possible circumstances ...

How do I Use a Date in an Array in GetAllEntriesByKey?

I'm trying to use the current day in GetAllEntriesByKey by passing an array. The array so far looks like this
Dim keyarray(2) As Variant
keyarray(0) = "FF Thompson ADT"
Set keyarray(1) = dateTime
I would like to say this
Set vc = view.GetAllEntriesByKey(keyarray, False)
Here is a row of what it looks like when it works. The test agent prints out csv in an email.
FF Thompson ADT,2/3/2009,11:45:02 PM,0,6,0,,00:00:04,5400,4
I can't seem to pass a current day dateTime that runs. I can set the dateTime manually in the declaration and it works. I think it's because it's trying to also pass the time but I don't know. I have tried three ways and it says invalid key value type.
Dim dateTime As New NotesDateTime("")
Call dateTime.LSLocalTime = Now
...
keyarray(1) = dateTime.Dateonly
Dim dateTime As New NotesDateTime("")
Call dateTime.SetNow
...
keyarray(1) = dateTime.Dateonly
Dim dateTime As New NotesDateTime("Today")
...
keyarray(1) = dateTime.Dateonly
I don't know if this is useful but I read about Evaluate here.
What I'm ultimately trying to do is GetFirstEntry for "FF Thompson ADT" for the most recent day entries exist. I'm also trying to do the same for the day before that. I'm trying to sum up the files processed (the number 6) for both days and errors (the null) for the most recent day using something like this. I need to tweak it so it finds the files processed and errors for entries but I haven't gotten there but should be able to do. I'm also just trying to find the most recent date with time value for the feed ie "FF Thompson ADT".
Set entry = vc.GetFirstEntry
filesprocessed = 0
Dim errors, errortotal As Integer
errors = 0
errorstotal = 0
While Not entry Is Nothing
rowval = 0
errors = 0
Forall colval In entry.ColumnValues
If rowval > 0 Then
errors = Cint(colval)
Else
rowval = Cint(colval)
End If
End Forall
filesprocessed = filesprocessed + rowval
errorstotal = errorstotal + errors
Set entry = vc.GetNextEntry(entry)
Wend
Thanks for any help or suggestions. They are greatly appreciated.
I've only used the GetAllEntriesByKey method with an array of strings. I've never tried mixing types. But assuming differing types are valid for that method, the problem might lie in the difference between datetime types in Notes. There's a core LS datetime type and then there's a NotesDateTime object. I'd bet the view considers a date column to be made up of the core datetime types, and so it fails when you pass the NotesDateTime type.
But that issue aside, my suggestion is to create a view that has the columns you want to access, and set the sort order of the first column (containing FF Thompson ADT) to asc, then set the second column with your dates to desc. You can then access the view entries in the order you want, with the most recent being first, 2nd most recent 2nd, etc.
If by some chance the GetAllEntriesByKey method returns the documents out of order (I forget if it guarantees order), I know I've done this before using the NotesViewNavigator class. There's definitely an alternate way to do it without need to call GetAllEntriesByKey with the date key.
Here's an answer I found
Dim todaysdate As New NotesDateTime("Today")
Dim dateTime As New NotesDateTime(todaysdate.DateOnly)
Dim keyarray(1) As Variant keyarray(0) = feedname
Set keyarray(1) = dateTime
Set vc = view.GetAllEntriesByKey(keyarray, False)

Resources