DateTime.ParseExact Equivalent for VB6? - datetime

Does anyone know if there's an actual equivalent method in VB6 for .NET's DateTime.ParseExact() method? I've tried using DateSerial(), IsDate() and CDate(), but, due to all of VB6's "helpfulness", I'm coming up with some unexpected results.
To be more specific, I'm trying to parse a text string from user input to validate whether or not it's an actual date. As an example, I'll be using the date 8/25/16. The usual expected input may or may not have delimiters between the month, day and year, so it may be entered as 82516.
Here's a sample of the code that's not working as intended (the value of 82516 is stored in the TempStr variable):
If IsDate(Format(TempStr, "#/##/##")) And IsDate(Format(TempStr, "##/#/##")) Then
TempDate = #12:00:00 AM#
ElseIf IsDate(Format(TempStr, "#/##/##")) Then
TempDate = CDate(Format(Tempstr, "#/##/##"))
ElseIf IsDate(Format(TempStr, "##/#/##")) Then
TempDate = CDate(Format(TempStr, "##/#/##"))
End If
Using the stated value, the first condition triggers. Knowing how it works, I understand why it's happening (it's "rearranging" the month, day and year to try to match a valid date), but I'm really trying to get it to parse the date in a specific order. I know that .NET's DateTime.ParseExact() method would get me there, but I have to do this in VB6 (maintaining some legacy code).
I tried using DateSerial():
If DateSerial(CInt(Right(TempStr, 2)), CInt(Left(TempStr, 1)), CInt(Mid(TempStr, 2, 2))) > #12:00:00 AM# Then
If DateSerial(CInt(Right(TempStr, 2)), CInt(Left(TempStr, 2)), CInt(Mid(TempStr, 3, 1))) > #12:00:00 AM# Then
TempDate = #12:00:00 AM#
Else
TempDate = DateSerial(CInt(Right(TempStr, 2)), CInt(Left(TempStr, 1)), CInt(Mid(TempStr, 2, 2)))
End If
Else
If DateSerial(CInt(Right(TempStr, 2)), CInt(Left(TempStr, 2)), CInt(Mid(TempStr, 3, 1))) > #12:00:00 AM# Then
TempDate = DateSerial(CInt(Right(TempStr, 2)), CInt(Left(TempStr, 2)), CInt(Mid(TempStr, 3, 1)))
Else
TempDate = #12:00:00 AM#
End If
End If
But that also comes along with an automatic correction if the values for any of the parameters fall outside of the acceptable ranges.
I also tried the following variation of the above code:
If IsDate(Format(TempStr, "m/dd/yy")) And IsDate(Format(TempStr, "mm/d/yy")) Then
...
But the first test results in an entirely different value of 3/12/26, which is WAY off from the original input.
Is there any way to accurately emulate the .NET DateTime.ParseExact() method in VB6, or am I just going to have to toss these types of user input values out as invalid/ambiguous?

I will personally write a function for ensuring the correct date is returned -
First get the string/integer, break it down into chunks and add values to those chunks and return a combined date...
Option Explicit
Public Function MakeCorrectDate()
Dim xMakeDate As Long, xDay As Integer, xMonth As Integer, xYear As Integer, xCentury As Integer, strCorrectDate As String
''xMake as long because of size, strCorrectDate as string to allow the /...
xMakeDate = txtInput.Text
''Assuming the format will ALWAYS be the same days, months and year (12/20/16) and length is ALWAYS 6...
xDay = Left$(xMakeDate, 2)
xMonth = Mid$(xMakeDate, 3, 2)
xYear = Right(xMakeDate, 2)
''First get the correct part to 1900 or 2000...
If xYear = "00" Then
xCentury = 20
ElseIf xYear < 99 And xYear > 20 Then ''Year 2000 and year 2020
xCentury = 19
Else
xCentury = 20
End If
strCorrectDate = xDay & "/" & xMonth & "/" & xCentury & xYear
txtYear.Text = strCorrectDate
End Function
Private Sub cmdGetCorrectDate_Click()
If Not Len(txtInput.Text) = 6 Then
MsgBox "Incorrect information, must be 6 or more characters."
Exit Sub
Else
Call MakeCorrectDate
End If
End Sub
Private Sub txtInput_Change()
''Ensure the user adds only numerical text...
Dim WshShell As Object
Set WshShell = CreateObject("WScript.Shell")
If Not IsNumeric(txtInput.Text) Then
WshShell.SendKeys "{BackSpace}"
End If
End Sub

Okay, so here's what I've come up with as a solution for my current needs. Similar to what #Andre-Oosthuizen posted above, I've decided to drastically simplify the validation from what I was doing before. This series of functions requires the user to enter a six-digit date (two-digit month, two-digit day, and two-digit year). I don't believe the century is going to be a factor in this specific application, so I'm going to leave that test out.
This should be acceptable to our users as they've had similar restrictions in other systems. While I'd personally prefer a more "bullet-proof" solution (such as using a DatePicker or other manipulation of the UI), I think this is going to be the most effective for our environment.
'----------------------------------------------------------------------
' LostFocus event handler for the txtEffectiveDate TextBox.
' Test for a valid date when the user attempts to leave the field.
'----------------------------------------------------------------------
Private Sub txtEffectiveDate_LostFocus()
' *********************************************************************
' ** Don't have the event handler try to do any parsing. Just pass **
' ** the .Text value to the validation function. If a date comes **
' ** back, reformat it to "look" like a date and move on. Otherwise **
' ** pop up an "error" message and return focus to the TextBox for **
' ** the user to correct their input. **
' *********************************************************************
Dim TempDate As Date
TempDate = CheckForValidDate(Me.txtEffectiveDate.Text)
If TempDate > #12:00:00 AM# Then
' If a valid Date is returned, put the formatted String value
' into the TextBox and move on.
Me.txtEffectiveDate.Text = Format(TempDate, "mm/dd/yy")
Else
' If the Date value is not valid (#12:00:00 AM#), notify the
' user and refocus on the TextBox to force the user to
' correct the input before continuing.
MsgBox "The date you entered was not valid." & vbCrLf & vbCrLf & _
"Please enter two digits for the month, two digits for the day and" & vbCrLf & _
"two digits for the year." & vbCrLf & vbCrLf & _
"For example, today's date should be entered as either " & Format(Now, "mmddyy") & vbCrLf & _
" or " & Format(Now, "mm/dd/yy") & ".", _
vbOKOnly + vbExclamation, "INVALID INPUT FORMAT"
Me.txtEffectiveDate.SetFocus
Me.txtEffectiveDate.SelStart = 0
Me.txtEffectiveDate.SelLength = Len(Me.txtEffectiveDate.Text)
End If
End Sub
'----------------------------------------------------------------------
' Attempts to convert the String input to a Date value. If the String
' value is already a Date (i.e., "1/1/16" or similar), go ahead and
' assume that the user wants that date and return it as a Date value.
' Otherwise, strip any non-numeric characters and break apart the input
' to pass along for further validation.
'----------------------------------------------------------------------
Private Function CheckForValidDate(ByVal DateStr As String) As Date
Dim TempDate As Date
If IsDate(DateStr) Then
' If the String value is already in a date format,
' just return the Date value of the String.
TempDate = CDate(DateStr)
Else
Dim TempStr As String
Dim CurrentChar As String
Dim TempYear As Integer
Dim TempMonth As Integer
Dim TempDay As Integer
Dim I As Integer
' Strip all non-numeric characters to get a purely numeric string.
For I = 1 To Len(DateStr)
CurrentChar = Mid(DateStr, I, 1)
If IsNumeric(CurrentChar) Then
TempStr = TempStr & CurrentChar
End If
Next I
' The all-numeric string should be exactly six characters
' (for this application).
If Len(Trim(TempStr)) = 6 Then
Dim NewDateStr As String
' Break the numeric string into the component parts -
' Month, Day, and Year. At six characters, there should
' be two characters for each element.
TempMonth = CInt(Left(TempStr, 2))
TempDay = CInt(Mid(TempStr, 3, 2))
TempYear = CInt(Right(TempStr, 2))
' Now pass the individual values to the second part of
' the validation to ensure each of the individual values
' falls within acceptable ranges.
NewDateStr = GetValidDateString(TempMonth, TempDay, TempYear)
' If the returned String value is not empty, then convert
' it to a Date value for returning to the calling method
If Len(Trim(NewDateStr)) > 0 Then
TempDate = CDate(NewDateStr)
End If
End If
End If
CheckForValidDate = TempDate
End Function
'----------------------------------------------------------------------
' Using numeric values for Month, Day, and Year, attempt to build a
' valid Date in mm/dd/yy format.
'----------------------------------------------------------------------
Private Function GetValidDateString(ByVal intMonth As Integer, ByVal intDay As Integer, ByVal intYear As Integer) As String
Dim ReturnStr As String
ReturnStr = ""
If intMonth >= 1 And intMonth <= 12 Then
Select Case intMonth
Case 1, 3, 5, 7, 8, 10, 12
' January, March, May, July, August, October and December
' have 31 days.
If intDay >= 1 And intDay <= 31 Then
ReturnStr = intMonth & "/" & intDay & "/" & intYear
End If
Case 4, 6, 9, 11
' April, June, September and November
' have 30 days
If intDay >= 1 And intDay <= 30 Then
ReturnStr = intMonth & "/" & intDay & "/" & intYear
End If
Case 2
' Depending on whether it is a Leap Year (every four years),
' February may have 28 or 29 days.
If intYear Mod 4 = 0 Then
If intDay >= 1 And intDay <= 29 Then
ReturnStr = intMonth & "/" & intDay & "/" & intYear
End If
Else
If intDay >= 1 And intDay <= 28 Then
ReturnStr = intMonth & "/" & intDay & "/" & intYear
End If
End If
End Select
End If
' Return the recombined string to the calling function.
GetValidDateString = ReturnStr
End Function
There's still obviously going to be some room for error, but I believe this will solve the issue for now. It's not perfect, but hopefully we'll be able to move away from this VB6 system soon. Thank you for all of the ideas and suggestions. They were very helpful in narrowing down the best solution for this specific implementation.

Related

Access 2010 - Last Sunday in March & October

I have a series of dates, which are recorded in both BST and GMT (depending upon the time of year). I want them to all be in GMT but I can't work out how to get Access to return the last Sunday in March (when we switch from GMT to BST) and the last Sunday in October (when we switch back).
All help appreciated!
I'm working in Access 2010.
Thank you in advance.
You can use this generic function:
' Calculates the date of the occurrence of Weekday in the month of DateInMonth.
'
' If Occurrence is 0 or negative, the first occurrence of Weekday in the month is assumed.
' If Occurrence is 5 or larger, the last occurrence of Weekday in the month is assumed.
'
' If Weekday is invalid or not specified, the weekday of DateInMonth is used.
'
' 2016-06-09. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function DateWeekdayInMonth( _
ByVal DateInMonth As Date, _
Optional ByVal Occurrence As Integer, _
Optional ByVal Weekday As VbDayOfWeek = -1) _
As Date
Const DaysInWeek As Integer = 7
Dim Offset As Integer
Dim Month As Integer
Dim Year As Integer
Dim ResultDate As Date
' Validate Weekday.
Select Case Weekday
Case _
vbMonday, _
vbTuesday, _
vbWednesday, _
vbThursday, _
vbFriday, _
vbSaturday, _
vbSunday
Case Else
' Zero, none or invalid value for VbDayOfWeek.
Weekday = VBA.Weekday(DateInMonth)
End Select
' Validate Occurence.
If Occurrence <= 0 Then
Occurrence = 1
ElseIf Occurrence > 5 Then
Occurrence = 5
End If
' Start date.
Month = VBA.Month(DateInMonth)
Year = VBA.Year(DateInMonth)
ResultDate = DateSerial(Year, Month, 1)
' Find offset of Weekday from first day of month.
Offset = DaysInWeek * (Occurrence - 1) + (Weekday - VBA.Weekday(ResultDate) + DaysInWeek) Mod DaysInWeek
' Calculate result date.
ResultDate = DateAdd("d", Offset, ResultDate)
If Occurrence = 5 Then
' The latest occurrency of Weekday is requested.
' Check if there really is a fifth occurrence of Weekday in this month.
If VBA.Month(ResultDate) <> Month Then
' There are only four occurrencies of Weekday in this month.
' Return the fourth as the latest.
ResultDate = DateAdd("d", -DaysInWeek, ResultDate)
End If
End If
DateWeekdayInMonth = ResultDate
End Function
Then:
LastSundayMarch = DateWeekdayInMonth(DateSerial(Year(SomeDateOfYear), 3, 1), 5, vbSunday)
LastSundayOctober = DateWeekdayInMonth(DateSerial(Year(SomeDateOfYear), 10, 1), 5, vbSunday)
or as functions:
Public Function DateLastSundayMarch(ByVal DateOfYear As Date) As Date
DateLastSundayMarch = DateWeekdayInMonth(DateSerial(Year(DateOfYear), 3, 1), 5, vbSunday)
End Function
Public Function DateLastSundayOctober(ByVal DateOfYear As Date) As Date
DateLastSundayOctober = DateWeekdayInMonth(DateSerial(Year(DateOfYear), 10, 1), 5, vbSunday)
End Function
Now you can have expressions like:
=DateLastSundayOctober([SomeDateField])
to be used as ControlSource for a control or in the GUI query designer.
This will work:
Option Explicit
Function getLastSundayOfMarchOfThisYear() As Date
getLastSundayOfMarchOfThisYear = getLastSundayOfMonthOfThisYear(3)
End Function
Function getLastSundayOfOctoberOfThisYear() As Date
getLastSundayOfOctoberOfThisYear = getLastSundayOfMonthOfThisYear(10)
End Function
Private Function getLastSundayOfMonthOfThisYear(month As Long) As Date
Debug.Assert month >= 1 And month <= 12
Dim i As Long, _
tmpDate As Date, _
daysInMonth As Long
daysInMonth = Day(DateSerial(year(Date), month + 1, 1) - 1)
For i = daysInMonth To 1 Step -1
tmpDate = CDate(year(Date) & "-" & month & "-" & i)
If Weekday(tmpDate) = vbSunday Then
getLastSundayOfMonthOfThisYear = tmpDate
Exit Function
End If
Next
End Function

Require previous working day minus public holidays

I am newish to MS Access.
I require the equivalent formula from excel to mc access which will workout -1 day from my data-set less weekends/public holidays.
So this is what i use in excel atm: =WORKDAY(start date,days,[holidays])
Any help would be greatly appreciated.
There is no native function, but you can use this set of functions:
Option Explicit
' Common constants.
' Date.
Public Const DaysPerWeek As Long = 7
Public Const MaxDateValue As Date = #12/31/9999#
Public Const MinDateValue As Date = #1/1/100#
' Workdays per week.
Public Const WorkDaysPerWeek As Long = 5
' Average count of holidays per week maximum.
Public Const HolidaysPerWeek As Long = 1
' Adds Number of full workdays to Date1 and returns the found date.
' Number can be positive, zero, or negative.
' Optionally, if WorkOnHolidays is True, holidays are counted as workdays.
'
' For excessive parameters that would return dates outside the range
' of Date, either 100-01-01 or 9999-12-31 is returned.
'
' Will add 500 workdays in about 0.01 second.
'
' Requires table Holiday with list of holidays.
'
' 2015-12-19. Gustav Brock. Cactus Data ApS, CPH.
'
Public Function DateAddWorkdays( _
ByVal Number As Long, _
ByVal Date1 As Date, _
Optional ByVal WorkOnHolidays As Boolean) _
As Date
Const Interval As String = "d"
Dim Holidays() As Date
Dim Days As Long
Dim DayDiff As Long
Dim MaxDayDiff As Long
Dim Sign As Long
Dim Date2 As Date
Dim NextDate As Date
Dim DateLimit As Date
Dim HolidayId As Long
Sign = Sgn(Number)
NextDate = Date1
If Sign <> 0 Then
If WorkOnHolidays = True Then
' Holidays are workdays.
Else
' Retrieve array with holidays between Date1 and Date1 + MaxDayDiff.
' Calculate the maximum calendar days per workweek.
MaxDayDiff = Number * DaysPerWeek / (WorkDaysPerWeek - HolidaysPerWeek)
' Add one week to cover cases where a week contains multiple holidays.
MaxDayDiff = MaxDayDiff + Sgn(MaxDayDiff) * DaysPerWeek
If Sign > 0 Then
If DateDiff(Interval, Date1, MaxDateValue) < MaxDayDiff Then
MaxDayDiff = DateDiff(Interval, Date1, MaxDateValue)
End If
Else
If DateDiff(Interval, Date1, MinDateValue) > MaxDayDiff Then
MaxDayDiff = DateDiff(Interval, Date1, MinDateValue)
End If
End If
Date2 = DateAdd(Interval, MaxDayDiff, Date1)
' Retrive array with holidays.
Holidays = GetHolidays(Date1, Date2)
End If
Do Until Days = Number
If Sign = 1 Then
DateLimit = MaxDateValue
Else
DateLimit = MinDateValue
End If
If DateDiff(Interval, DateAdd(Interval, DayDiff, Date1), DateLimit) = 0 Then
' Limit of date range has been reached.
Exit Do
End If
DayDiff = DayDiff + Sign
NextDate = DateAdd(Interval, DayDiff, Date1)
Select Case Weekday(NextDate)
Case vbSaturday, vbSunday
' Skip weekend.
Case Else
' Check for holidays to skip.
' Ignore error when using LBound and UBound on an unassigned array.
On Error Resume Next
For HolidayId = LBound(Holidays) To UBound(Holidays)
If Err.Number > 0 Then
' No holidays between Date1 and Date2.
ElseIf DateDiff(Interval, NextDate, Holidays(HolidayId)) = 0 Then
' This NextDate hits a holiday.
' Subtract one day before adding one after the loop.
Days = Days - Sign
Exit For
End If
Next
On Error GoTo 0
Days = Days + Sign
End Select
Loop
End If
DateAddWorkdays = NextDate
End Function
' Returns the holidays between Date1 and Date2.
' The holidays are returned as a recordset with the
' dates ordered ascending, optionally descending.
'
' Requires table Holiday with list of holidays.
'
' 2015-12-18. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function DatesHoliday( _
ByVal Date1 As Date, _
ByVal Date2 As Date, _
Optional ByVal ReverseOrder As Boolean) _
As DAO.Recordset
' The table that holds the holidays.
Const Table As String = "Holiday"
' The field of the table that holds the dates of the holidays.
Const Field As String = "Date"
Dim rs As DAO.Recordset
Dim SQL As String
Dim SqlDate1 As String
Dim SqlDate2 As String
Dim Order As String
SqlDate1 = Format(Date1, "\#yyyy\/mm\/dd\#")
SqlDate2 = Format(Date2, "\#yyyy\/mm\/dd\#")
ReverseOrder = ReverseOrder Xor (DateDiff("d", Date1, Date2) < 0)
Order = IIf(ReverseOrder, "Desc", "Asc")
SQL = "Select " & Field & " From " & Table & " " & _
"Where " & Field & " Between " & SqlDate1 & " And " & SqlDate2 & " " & _
"Order By 1 " & Order
Set rs = CurrentDb.OpenRecordset(SQL, dbOpenSnapshot)
Set DatesHoliday = rs
End Function
' Returns the holidays between Date1 and Date2.
' The holidays are returned as an array with the
' dates ordered ascending, optionally descending.
'
' The array is declared static to speed up
' repeated calls with identical date parameters.
'
' Requires table Holiday with list of holidays.
'
' 2015-12-18. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function GetHolidays( _
ByVal Date1 As Date, _
ByVal Date2 As Date, _
Optional ByVal OrderDesc As Boolean) _
As Date()
' Constants for the arrays.
Const DimRecordCount As Long = 2
Const DimFieldOne As Long = 0
Static Date1Last As Date
Static Date2Last As Date
Static OrderLast As Boolean
Static DayRows As Variant
Static Days As Long
Dim rs As DAO.Recordset
' Cannot be declared Static.
Dim Holidays() As Date
If DateDiff("d", Date1, Date1Last) <> 0 Or _
DateDiff("d", Date2, Date2Last) <> 0 Or _
OrderDesc <> OrderLast Then
' Retrieve new range of holidays.
Set rs = DatesHoliday(Date1, Date2, OrderDesc)
' Save the current set of date parameters.
Date1Last = Date1
Date2Last = Date2
OrderLast = OrderDesc
Days = rs.RecordCount
If Days > 0 Then
' As repeated calls may happen, do a movefirst.
rs.MoveFirst
DayRows = rs.GetRows(Days)
' rs is now positioned at the last record.
End If
rs.Close
End If
If Days = 0 Then
' Leave Holidays() as an unassigned array.
Erase Holidays
Else
' Fill array to return.
ReDim Holidays(Days - 1)
For Days = LBound(DayRows, DimRecordCount) To UBound(DayRows, DimRecordCount)
Holidays(Days) = DayRows(DimFieldOne, Days)
Next
End If
Set rs = Nothing
GetHolidays = Holidays()
End Function

How to insert values of string builder to an Integer Stack? (VB.Net)

This code basically takes a mathematical expression and evaluates it.
The following code i have written in VB.net shamelessly taken from here : Expression Evaluation
which has been written in Java.
Public Function evaluate(expression As [String]) As Integer
Dim tokens As Char() = expression.ToCharArray()
' Stack for numbers: 'values'
Dim values As New Stack(Of UInteger)()
' Stack for Operators: 'ops'
Dim ops As New Stack(Of Char)()
For i As Integer = 0 To tokens.Length - 1
' Current token is a whitespace, skip it
If tokens(i) = " "c Then
Continue For
End If
' Current token is a number, push it to stack for numbers
If tokens(i) >= "0"c AndAlso tokens(i) <= "9"c Then
Dim sbuf As New StringBuilder(100)
'Dim sbuf As New String("", 128)
' There may be more than one digits in number
If i < tokens.Length AndAlso tokens(i) >= "0"c AndAlso tokens(i) <= "9"c Then
sbuf.Append(tokens(System.Math.Max(System.Threading.Interlocked.Increment(i), i - 1)))
End If
If sbuf Is Nothing AndAlso sbuf.ToString().Equals("") Then
Else
Dim intgr As Integer
Dim accpt As Boolean = Integer.TryParse(sbuf.ToString(), intgr)
If accpt = True Then
values.Push([Integer].Parse(sbuf.ToString()))
Else
Dim space As String = " "
values.Push(space)
End If
End If
' Current token is an opening brace, push it to 'ops'
ElseIf tokens(i) = "("c Then
ops.Push(tokens(i))
' Closing brace encountered, solve entire brace
ElseIf tokens(i) = ")"c Then
While ops.Peek() <> "("c
values.Push(applyOp(ops.Pop(), values.Pop(), values.Pop()))
End While
ops.Pop()
' Current token is an operator.
ElseIf tokens(i) = "+"c OrElse tokens(i) = "-"c OrElse tokens(i) = "*"c OrElse tokens(i) = "/"c Then
' While top of 'ops' has same or greater precedence to current
' token, which is an operator. Apply operator on top of 'ops'
' to top two elements in values stack
While Not ops.Count = 0 AndAlso hasPrecedence(tokens(i), ops.Peek())
values.Push(applyOp(ops.Pop(), values.Pop(), values.Pop()))
End While
' Push current token to 'ops'.
ops.Push(tokens(i))
End If
Next
' Entire expression has been parsed at this point, apply remaining
' ops to remaining values
While Not ops.Count = 0
values.Push(applyOp(ops.Pop(), values.Pop(), values.Pop()))
End While
' Top of 'values' contains result, return it
Return values.Pop()
End Function
Public Function hasPrecedence(op1 As Char, op2 As Char) As [Boolean]
If op2 = "("c OrElse op2 = ")"c Then
Return False
End If
If (op1 = "*"c OrElse op1 = "/"c) AndAlso (op2 = "+"c OrElse op2 = "-"c) Then
Return False
Else
Return True
End If
End Function
' A utility method to apply an operator 'op' on operands 'a'
' and 'b'. Return the result.
Public Function applyOp(op As Char, b As Integer, a As Integer) As Integer
Select Case op
Case "+"c
Return a + b
Case "-"c
Return a - b
Case "*"c
Return a * b
Case "/"c
If b = 0 Then
'Throw New UnsupportedOperationException("Cannot divide by zero")
End If
Return a \ b
End Select
Return 0
End Function
this is how im using the code :
formula = "10 + 2 * 6"
Dim result As Double = evaluate(formula)
and i keep getting this following error:
Unhandled exception at line 885, column 13 in http:**** DEDOM5KzzVKtsL1tWZwgsquruscgqkpS5bZnMu2kotJDD8R38OukKT4TyG0z97U1A8ZC8o0wLOdVNYqHqQLlZ9egcY6AKpKRjQWMa4aBQG1Hz8t_HRmdQ39BUIKoCWPik5bv4Ej6LauiiQptjuzBMLowwYrLGpq6dAhVvZcB-4b-mV24vCqXJ3jbeKi0&t=6119e399
0x800a139e - Microsoft JScript runtime error: Sys.WebForms.PageRequestManagerServerErrorException: Conversion from string " " to type 'UInteger' is not valid.
Im a beginner but i think that the error is occurring because its not able to covert space into integer.How to deal with the spaces??
Any help is much appreciated:).
VB.NET is strongly-typed, so you simply cannot push anything other than integers onto a Stack(Of Integer). Therefore this code:
Dim space As String = " "
values.Push(space)
will always fail at runtime. (By the way, you want to set Option Explicit On and Option Strict On at the top of every module. If you do that, the line above will already be marked as an error at build time).
I haven't tried executing your code, but why would you need to save the spaces if what you're building is an expression evaluator? It doesn't seem to add anything to the evaluation. Perhaps if you simply don't add the spaces to the stack it will work anyway.

List object methods and properties

Is there any way to list available methods for created object in VBS?
For example:
Set IE = CreateObject("InternetExplorer.Application")
I want to list available properties of this object, as:
IE.AddressBar
IE.Application
IE.Busy
...
or methods:
IE.ClientToWindow
IE.ExecWB
IE.GetProperty
...
How can I discover available properties to arbitrary valid object in VBS?
Using TypeLib Information Objects from tlbinf32.dll it is possible to list all members of a class.
`tlbinf32.dll` was part of *Visual Studio 6.0*, which was the current release in the years around 2000. Microsoft seems to not offer the DLL for download anymore (situation mid 2017), but you can download it from various sites on the internet. I found version *1.1.88.4, Build 8804, Copyright Matthew Curland 1996, Microsoft 1997-2000, size 148.480 Bytes* at https://www.dll4free.com/tlbinf32.dll.html, or other sites.
To install the DLL in Win32, copy it to `%windir%\System32` and *as administrator* call `regsvr32.exe tlbinf32.dll` from that directory.
To insttall the DLL in Win64, copy it to `%windir%\syswow64`, then *as administrator* register with `%windir%\syswow64\regsvr32.exe`, and finally run the vbscript with `%windir%\syswow64\cscript.exe` (or `wscript.exe`). Thanks [BuvinJ](/users/3220983/buvinj) for the [hint](/questions/14305750/list-object-methods-and-properties/44459670?noredirect=1#comment86169321_44459670)
The following script demonstrates the included function VariableInfo which will return a string with the type of the passed variable, and in case of an Object, all members with details, including type of Property, callable type (Sub or Function), and parameter names and return type in case of Function. The type name of the object in case of a COM object would be the name of the implemented Interface. Not sure if it works for multiple implemented interfaces, but AFAIK it's not possible to implement multiple interfaces in one class via COM anyway.
It does not support recursion in any way, because this would lead to infinity loops for some types.
This will give you virtually full working reflection in VBS. Great to explore APIs for example with the Microsoft Script Debugger.
' Reflection for VBScript via tlbinfo32.dll
'
' Patrick Strasser-Mikhail 2017-2021
' Ansgar Wiechers 2019
' https://stackoverflow.com/questions/14305750/list-object-methods-and-properties/44459670#44459670
'
' v1.1 2021-02-01: Show values of arrays and objects, but only one level
' Returns a String describing the passed object/variable on the first level,
' no recursion.
Function VariableInfo(obj)
VariableInfo = VariableInfoToLevel(obj, 0, 1)
End Function
' Returns a String describing the passed object/variable on the first level,
' recurse down to level max_level(0=no recursion).
Function VariableInfoToLevel(obj, level, max_level)
Const invokeKindPropertyGet = 0 ' simple data member
Const invokeKindFunction = 1 ' method: Sub or Function
Const invokeKindPropertyPut = 2 ' Docs: has a value setter; reality: more like is settable
Const invokeKindPropertyPutRef = 4 ' Docs: has a reference setter; reality: more like is not settable
If level > max_level Then
VariableInfoToLevel = ""
Exit Function
End If
Dim indent : indent = Space(4 * level)
VariableInfoToLevel = indent
If isEmpty(obj) Or _
isNull(obj) _
Then
VariableInfoToLevel = VariableInfoToLevel & TypeNameFromVarType(VarType(obj))
ElseIf Not IsObject(obj) Then
If Not isArray(obj) Then
VariableInfoToLevel = indent & TypeNameFromVarType(VarType(obj)) & ", Value: [" & obj & "]"
Else
VariableInfoToLevel = indent & TypeNameFromVarType(VarType(obj))
Dim dimension
ReDim sizes(0)
Dim size
On Error Resume Next
Err.Clear
For dimension = 0 To 10 ' deliberate limit to prevent infinite loop
size = Ubound(obj, dimension + 1)
If Err.Number <> 0 Then
' report ther then Index out of Bounds
If Err.Number <> 9 Then
WScript.Echo "Exception " & Err.Number & ": " & Err.Description & "; in " & Err.Source
End If
Exit For
End If
ReDim Preserve sizes(dimension)
sizes(dimension) = size
Next
On Error Goto 0
VariableInfoToLevel = VariableInfoToLevel & "(" & Join(sizes, ",") & ")"
Select Case dimension
Case 1
VariableInfoToLevel = VariableInfoToLevel & " {" & vbCrlf
Dim idx
For idx = LBound(obj) To UBound(obj)
VariableInfoToLevel = VariableInfoToLevel & indent & _
" " & idx & ":" & _
Trim(VariableInfoToLevel(obj(idx), level + 1, max_level)) & vbCrlf
Next
VariableInfoToLevel = VariableInfoToLevel & indent & "}" & vbCrlf
Case 2
VariableInfoToLevel = indent & "{" & vbCrlf
Dim idx1, idx2
For idx1 = LBound(obj, 1) To UBound(obj, 1)
For idx2 = LBound(obj, 2) To UBound(obj, 2)
VariableInfoToLevel = VariableInfoToLevel & indent & _
" " & idx1 & "," & idx2 & ":" & _
Trim(VariableInfoToLevel(obj(idx1, idx2), level + 1, max_level)) & vbCrlf
Next
Next
VariableInfoToLevel = VariableInfoToLevel & indent & " }" & vbCrlf
Case Else
' 0 is empty anyway, more is too complicated to print, just leave it for now
End Select
End If
ElseIf TypeName(obj) = "Nothing" Then
VariableInfoToLevel = indent & "Nothing (The Invalid Object)"
Else
' Object
VariableInfoToLevel = indent & "Object " & TypeName(obj)
'' Need to think about that... True for Err, but not for System.Dictionary
'' Seems Err is very special, and we should compare explicitly with internal/predifined Objects (Err, WScript)
'If varType(obj) <> vbObject Then
' hm, interresting...
' VariableInfoToLevel = VariableInfoToLevel & " with default property (no analysis possible)"
' Exit Function
'End If
Dim TLI
Dim MemberInfo
Dim TypeInfo
Set TLI = CreateObject("TLI.TLIApplication")
VariableInfoToLevel = indent & "Object " & TypeName(obj)
On Error Resume Next
Err.Clear
Set TypeInfo = TLI.InterfaceInfoFromObject(obj)
If Err.Number <> 0 Then
VariableInfoToLevel = VariableInfoToLevel & "; Error " & Err.Number
VariableInfoToLevel = VariableInfoToLevel & ": " & Err.Description
Err.Clear
On Error Goto 0
Exit Function
End If
On Error Goto 0
For Each MemberInfo In TypeInfo.Members
Dim Desc
Dim printNextLevel : printNextLevel = vbFalse
Desc = ""
' based on .Net System.Runtime.IteropService.ComTypes
'' FIXME: Call by Value/Reference and settable seems to be switched some
'' InvokeKind seems to not encode value passing, rather settable/not settable
'' Needs more work to decode byValue/byReference
Select Case MemberInfo.InvokeKind
Case InvokeKindFunction
If MemberInfo.ReturnType.VarType <> 24 Then
Desc = " Function " & TypeNameFromVarType(MemberInfo.ReturnType.VarType)
Else
Desc = " Sub"
End If
Desc = Desc & " " & MemberInfo.Name
Dim ParameterList
ParameterList = Array()
Dim Parameter
For Each Parameter In MemberInfo.Parameters
ReDim Preserve parameterList(UBound(ParameterList) + 1)
ParameterList(Ubound(parameterList)) = Parameter.Name
Next
Desc = Desc & "(" & Join(ParameterList, ", ") & ")"
'Set parameters = Nothing
Case InvokeKindPropertyGet
Desc = " Data Member " & MemberInfo.Name
printNextLevel = vbTrue
Case InvokeKindPropertyPut
' Seems to be
Desc = " Property " & MemberInfo.Name & " [set by val"
If IsGettable(obj, MemberInfo.Name) Then
Desc = Desc & "/get"
printNextLevel = vbTrue
End If
Desc = Desc & "]"
'Stop
Case InvokeKindPropertyPutRef
'Stop
Desc = " Property " & MemberInfo.Name & " [set by ref"
If IsGettable(obj, MemberInfo.Name) Then
Desc = Desc & "/get"
printNextLevel = vbTrue
End If
Desc = Desc & "]"
'Stop
Case Else
Desc = " Unknown member, InvokeKind " & MemberInfo.InvokeKind
End Select
VariableInfoToLevel = VariableInfoToLevel & vbNewLine & _
indent & Desc
If printNextLevel And level < max_level Then
VariableInfoToLevel = VariableInfoToLevel & vbNewLine & _
VariableInfoToLevel(eval("obj." & MemberInfo.Name), level + 1, max_level)
End If
Next
Set TypeInfo = Nothing
Set TLI = Nothing
End If
End Function
Function IsGettable(obj, memberName)
Dim value
On Error Resume Next
Err.Clear
value = eval("obj." & memberName)
Stop
If Err.Number <> 0 And _
Err.Number <> 438 And _
Err.Number <> 450 Then
WScript.Echo Err.Number & ": " & Err.Description
End If
'438: Object doesn't support this property or method
'450: Wrong number of arguments or invalid property assignment
If Err.Number = 438 Or _
Err.Number = 450 Then
IsGettable = vbFalse
Else
IsGettable = vbTrue
End If
End Function
Function IsSimpleType(obj)
If (isEmpty(obj) Or isNull(obj)) And (Not IsObject(obj)) And (Not isArray(obj)) Then
IsSimpleType = vbTrue
Else
IsSimpleType = vbFalse
End If
End Function
' Decode Type Number to something readable
Function TypeNameFromVarType(typeNr)
Dim typeDetails
set typeDetails = CreateObject("Scripting.Dictionary")
typeDetails.add 0, "vbEmpty (uninitialized variable)"
typeDetails.add 1, "vbNull (value unknown)"
typeDetails.add 2, "vbInteger" ' Short?
typeDetails.add 3, "vbLong" ' Integer?
typeDetails.add 4, "vbSingle"
typeDetails.add 5, "vbDouble"
typeDetails.add 6, "vbCurrency"
typeDetails.add 7, "vbDate"
typeDetails.add 8, "vbString"
typeDetails.add 9, "vbObject"
typeDetails.add 10, "Exception"
typeDetails.add 11, "vbBoolean"
typeDetails.add 12, "vbVariant"
typeDetails.add 13, "DataObject"
typeDetails.add 14, "vbDecimal"
typeDetails.add 17, "vbByte"
typeDetails.add 18, "vbChar"
typeDetails.add 19, "ULong"
typeDetails.add 20, "Long" ' realy Long?
typeDetails.add 24, "(void)"
typeDetails.add 36, "UserDefinedType"
If typeDetails.Exists(typeNr) Then
TypeNameFromVarType = typeDetails(typeNr)
ElseIf typeNr > 8192 Then
TypeNameFromVarType = "vbArray{" & TypeNameFromVarType(typeNr - 8192) & "}"
Else
typeNameFromVarType = "Unknown Type " & typeNr
End If
End Function
' Some nice example class to demonstrate all possible interfaces.
Class MyClass
Dim Name_
Dim Name2_
Dim Name3_
Dim Name4_
Dim dict
Private Sub Class_Initialize()
Name_ = "foo"
Name2_ = "bar"
Name3_ = "baz"
Name4_ = "spam"
Set dict = CreateObject("Scripting.Dictionary")
End Sub
Private Sub Class_Terminate()
Set dict = Nothing
End Sub
Public Property Get Name
Name = Name_
End Property
Public Property Let Name(ByVal Value)
Name_ = Value
End Property
Public Property Let Name2(ByRef Value)
Set Name2_ = Value
End Property
Public Property Get Name3
Name3 = Name3_
End Property
Public Property Set Name3(ByVal Value)
Set Name3_ = Value
End Property
Public Property Get Name4
Name4 = Name4_
End Property
Public Property Set Name4(ByRef Value)
Set Name4_ = Value
End Property
Sub TestSub()
WScript.Echo "Test"
End Sub
Sub TestFunc(message)
WScript.Echo "Test: " & message
End Sub
Sub TestFunc2(ByRef message)
WScript.Echo "Test: " & message
End Sub
Function Add(first, second)
Add = first + second
End Function
Function Substract(ByVal first, ByRef second)
Add = first - second
End Function
End Class
Sub testVariableInfo()
Dim variable
' vbEmpty
Wscript.Echo VariableInfo(variable)
variable = Null
Wscript.Echo VariableInfo(variable)
Set variable = Nothing
Wscript.Echo VariableInfo(variable)
Wscript.Echo VariableInfo(Int(23))
Wscript.Echo VariableInfo(cLng(23))
Wscript.Echo VariableInfo(2147483647)
Wscript.Echo VariableInfo(5/4)
Wscript.Echo VariableInfo(4 * Atn(1)) ' Simplest way to pi, not all inverse functions like arcsin are defined.
Wscript.Echo VariableInfo(3.4E38)
Wscript.Echo VariableInfo(CDbl(3.4E38))
Wscript.Echo VariableInfo(cCur(20.123456))
Wscript.Echo VariableInfo(now)
Wscript.Echo VariableInfo("Some Text")
Wscript.Echo VariableInfo(Err)
Dim MyObject
Set MyObject = new MyClass
Wscript.Echo VariableInfo(MyObject)
Set MyObject = Nothing
Dim TestAEmpty()
Wscript.Echo VariableInfo(TestAEmpty)
ReDim TestA1(17)
Wscript.Echo VariableInfo(TestA1)
Dim TestA2(3, 7)
Wscript.Echo VariableInfo(TestA2)
Dim TestA3
TestA3 = Array(4, 5, 6)
Wscript.Echo VariableInfo(TestA3)
Dim dict
Set dict = CreateObject("Scripting.Dictionary")
WScript.Echo VariableInfo(dict)
Set dict = Nothing
End Sub
testVariableInfo
For for more information about the Typelib Interface, get the documentation help file from Microsoft KB artivle 224331
Matthew Curland offers for download at the website to his book Advanced Visual Basic 6 the nice program Type Library Editor (EditTLBEval.exe) as evaluation version, and the according Documentation
Especially in this context I really like the line If you're a Visual Basic developer who refuses to recognize the commonly accepted limitations of VB, this book is definitely for you. by Ted Pattison. Just replace VB by VBScript here.
VBWebProfi gave the hint for TLI, thanks for that. Working out the details and writing the code was several hours of work, though ;-)
VBScript itself does not support type introspection outside the TypeName and VarType functions, which will give you the type of an object, but won't give you access to its internal structure.
As other answers explained there is a DLL that would provide this feature, but it doesn't ship with Windows, and since it was part of an old version of Visual Studio there might not be a legal way to obtain it nowadays.
While that is partially true, its incomplete.... Google, GetObjectText_, Methods_, & Propeties_
The referenced methods will only work on objects collected while connected to the cimv2 namespace of a remote host via the WbemScripting.SWbemLocator object. If this object has the ability to work on localhost, it's unapparent to me.
Once you do this you can query any of the classes held therein [Win32_Services,Win32_Drives, etc] and interrogate the objects in the resultset using a For-Next loop on the object like below...
For Each oProp in oObject.Properties_
'be careful here because some propeties may be an object or an array.
'so test for that here using "typename" or "vartype"
wScript.Echo oProp.Name & vbTab & oProp
Next
Or...
For Each oMethod in oObject.Methods_
wScript.Echo oProp.Name
Next
Finally, ...
For Each oProp in oObject.Properties_
'This will display all of an objects properties
oProp.GetObjectText_
Next
If you happen to be using HP UFT or QTP then follow these steps:
1) Install any version of MS Visual Studio onto your laptop. (Don't worry about licensing, you won't be running VS)
2) Reboot your computer.
3) Launch UFT or QTP, load a script and hit F11, (or pause at any piece of code that is near the object you want to inspect).
4) Add the object to the Watch window. It can be an Object Repository object or a programmatic description.
If the object exists, the object will now display two Plus (+) signs in the Watch window that can be expanded to show all available Methods and Properties, as well as child objects that can be expanded.
Use TLI . The TLI.TLIApplication class (from tlbinf32.dll) can inspect various COM objects from their instance. Explore the TLI library in Excel or other Microsoft product that supports scripting and has an script editor which is able to add references, then add tlbinf32.dll. The name in the references is "Typelib information".
Note that the DLL does not ship with Windows, though.
Use the method InterfaceInfoFromObject() for VBScript classes and alternatively try ClassInfoFromObject().
Option Explicit
Dim TLI
Dim MyObject
Dim TypeInfo
Dim MemberInfo
Set TLI = CreateObject("TLI.TLIApplication")
Set MyObject = New MyClass
Set TypeInfo = TLI.InterfaceInfoFromObject(MyObject)
For Each MemberInfo In TypeInfo.Members
WScript.Echo MemberInfo.Name
Next
Class MyClass
Dim Name_
Public Property Get Name
Name = Name_
End Property
Public Property Let Name(ByVal Value)
Name_ = Value
End Property
End Class
Try this ...
For i = 0 To webElementCount-1 Step 1
innertextProp = myValue2(i).GetROProperty("innertext")
print i & innertextProp
print innertextProp
Next

DateAdd() in Special Format

I've tried all the ways I see to add a month to a certain date then return that in a specific format but I'm at a loss. Here's my code but I need to format it:
replace( formatdatetime( dateadd( "m" , 1 , request("date") ), 0 ) , "/" , "-" ) & "' )
request("date") is in yyyyy-dd-mm hh:mm:ss format and that's how I need the new date.
The following should work perfect:
replace( formatdatetime( dateadd( "m" , 1 , cDate(request("date")) ), 0 ) , "/" , "-" )
Notice the use of the cDate function to convert a value to a date explicitly.
Edit:
I removed last part of your code & "' ), it gave me an error otherwise.
When working with dates, it's especially important to take care of the proper data (sub)types. Feeding a string to a function that expects a date (and relying on 'VBScript - and your local settings - will do the right thing') is dangerous.
Using replace will never change the order of the date parts.
FormatDateTime depends on the local/regional settings and should be avoided as a sure path to disaster.
One way to solve this problem + most of all other problems concerning fancy formatting in VBScript is to use a .Net System.Text.StringBuilder:
Given Lib.vbs:
' Lib.vbs - simple VBScript library/module
' use
' ExecuteGlobal goFS.OpenTextFile(<PathTo\Lib.vbs>).ReadAll()
' to 'include' Lib.vbs in you main script
Class ToBeAShamedOf
Public a
Public b
End Class ' ToBeAShamedOf
Class cFormat
Private m_oSB
Private Sub Class_Initialize()
Set m_oSB = CreateObject("System.Text.StringBuilder")
End Sub ' Class_Initialize
Public Function formatOne(sFmt, vElm)
m_oSB.AppendFormat sFmt, vElm
formatOne = m_oSB.ToString()
m_oSB.Length = 0
End Function ' formatOne
Public Function formatArray(sFmt, aElms)
m_oSB.AppendFormat_4 sFmt, (aElms)
formatArray = m_oSB.ToString()
m_oSB.Length = 0
End Function ' formatArray
End Class ' cFormat
and main.vbs:
' main.vbs - demo use of library/module Lib.vbs
' Globals
Dim gsLibDir : gsLibDir = ".\"
Dim goFS : Set goFS = CreateObject("Scripting.FileSystemObject")
' LibraryInclude
ExecuteGlobal goFS.OpenTextFile(goFS.BuildPath(gsLibDir, "Lib.vbs")).ReadAll()
WScript.Quit demoDateFormat()
WScript.Quit main()
Function main()
Dim o : Set o = New ToBeAShamedOf
o.a = 4711
o.b = "whatever"
WScript.Echo o.a, o.b
main = 1 ' can't call this a success
End Function ' main
Function demoDateFormat()
Dim sD : sD = "2012-05-16 01:02:03" ' near future; not yyyyy!
Dim dtD : dtD = CDate(sD)
Dim dtDM : dtDM = DateAdd("m", 1, dtD)
Dim oFmt : Set oFmt = New cFormat
WScript.Echo oFmt.formatArray( _
" sD: {1}{0} dtD: {2}{0} dtDM: {3}{0}dtDM': {4}" _
, Array(vbCrLf, sD, dtD, dtDM, oFmt.formatOne("{0:yyyy-MM-dd hh:mm:ss}", dtDM)))
demoDateFormat = 0 ' seems to be decent
End Function ' demoDateFormat
you'll get:
cscript main.vbs
sD: 2012-05-16 01:02:03
dtD: 16.05.2012 01:02:03
dtDM: 16.06.2012 01:02:03
dtDM': 2012-06-16 01:02:03
(to be seen in the context of this answer)
This may help:
FormatDateTime(DateAdd("M",1,DateSerial(Left(request("date"),4),Mid(request("date"),9,2),Mid(request("date"),6,2))) & " " & Mid(request("date"),12,8),d,0)
It basically converts the string to a valid date in the native format, adds the 1 requested month and then rebuilds the string.
NOTE: request("date") looks as though it returns the current datetime so running it in this way may generate a final value that is a second or so out, if that's a problem then you will be better storing a static value in a variable, otherwise this should hopefully be ok.

Resources