How to sort list of directories in custom order that follows the calendar month name - vb.net

I have this list of directories:
JAN_20
FEB_20
MAR_20
.....
DEC_20
I am reading it using the following VB.Net code:
Private Const MY_PATH As String = "\\120.199.10.39\departments\2020\"
Dim Z_directories = Directory.GetDirectories(MY_PATH, "*", SearchOption.AllDirectories).ToList()
For Each dir1 In Z_directories
'do something
Next
The issue is that I would like to "OrderBy" it in a custom way that follows the month order (Jan, Feb, Mar,... etc), not alphabetically or by creation time, etc.
How can I achieve this?

Basically, the thing you need to know here is that you can turn those folder names into Date values like this:
Dim d = Date.ParseExact(s, "MMM_yy", Nothing, DateTimeStyles.None)
That means that you can convert all your folder names to Dates and then sort by those Dates. There are a number of specific ways you can do the actual sort.
Here's an example that uses the array returned by GetDirectories and sorts it in place:
Dim rootFolderPath = "folder path here"
Dim subFolderPaths = Directory.GetDirectories(rootFolderPath)
Array.Sort(subFolderPaths,
Function(sfp1, sfp2)
Dim folderName1 = Path.GetFileName(sfp1)
Dim folderName2 = Path.GetFileName(sfp2)
Dim folderDate1 = Date.ParseExact(folderName1, "MMM_yy", Nothing, DateTimeStyles.None)
Dim folderDate2 = Date.ParseExact(folderName2, "MMM_yy", Nothing, DateTimeStyles.None)
Return folderDate1.CompareTo(folderDate2)
End Function)
Here's a further example that uses a LINQ query to sort before creating a final array:
Dim rootFolderPath = "folder path here"
Dim subFolderPaths = Directory.EnumerateDirectories(rootFolderPath).
OrderBy(Function(sfp) Date.ParseExact(sfp, "MMM_yy", Nothing, DateTimeStyles.None)).
ToArray()
Note that the second example calls EnumerateDirectories rather than GetDirectories, so as to not create two different arrays. Note that you can also call ToList if you prefer a List(Of String) to a String array but I tend to advise using arrays unless you specifically need to add and/or remove items.

I found the solution:
Z_directories .Sort(Function(valueB, valueA) CDate(Right(valueB, 2) & "00-" & Mid(valueB, Len(valueB) - 5, 3) & "-" & "01").CompareTo(CDate(Right(valueA, 2) & "00-" & Mid(valueA, Len(valueA) - 5, 3) & "-" & "01")))

Related

VBA Function Passing Multi Variables back to Sub

I have a large string over 500 char which is called strEssay. I want to use a function(since I will need to look for several patterns) to return two values if (for example the name) Frank is found or not.
This is the function I'm trying to use:
Function NameFinder(strEssay as String, strName as String)
Dim varNameCounter as Variant
Dim strNameFinderResult as String
varNameCounter = 0
strNameFinderResult = ""
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
End Function
I want to be able to return back to my subroutine both 'strNameFinderResult' and 'varNameFinderCounter'.
Is there any way that I can return both values?
If I can't return both simultaneously can I return one through the function and the other through a textbox or something? What would calling the function look like in the subroutine and/or how would I need to change my function?
NameFinder() function, returning array of 3 elements. It is called and returned by TestMe(), writing the following to the console:
Function NameFinder(essay As String, name As String)
Dim nameFinderResult As String
Dim namefinderCounter As String
nameFinderResult = "" & essay & name
namefinderCounter = 0.001 + 12
NameFinder = Array(nameFinderResult, namefinderCounter, "something else")
End Function
Public Sub TestMe()
Dim myArray As Variant
myArray = NameFinder("foo", "bar")
Dim i As Long
For i = LBound(myArray) To UBound(myArray)
Debug.Print myArray(i)
Next i
End Sub
As a general rule, you have to give the routine a type like this:
Function NameFinder(strEssay as String, strName as String) as string
But, that returns only ONE value.
So, a function (as opposed to a sub) returns one value (as a general rule).
However, you CAN also return parameters that you pass. I mean, in above, you can't make TWO assignments to one variable, can you?
So, you can use a Sub like this:
Sub NameFinder(strEssay as String, strName as String, _
strNameFinderResult as string, _
varNameFinderCounter as double)
If strEssay like "*" & strName & "*" Then
strNameFinderResult = strName
varNameFinderCounter = 1
Else
strNameFinderResult = ""
varNameFinderCounter = .001
EndIf
So in code, you now can go:
dim strMyResult as string
dim strFinderCount as Double
Call NameFinder("MyEassy", "Joe Blow", strMyResult, strFinderCount)
So, you can return values with the parameters.
Now, I suppose it possible for some strange reason, that you want to use a function to return two values with a single assignment?
What you would do is this in your code module.
Define a custom type, and use that.
eg this:
Option Compare Database
Option Explicit
Type SearchResult
strName As String
FindCount As Double
End Type
Function NameFinder(strEssay As String, strName As String) As SearchResult
NameFinder.FindCount = 0
NameFinder.strName = ""
If strEssay Like "*" & strName & "*" Then
NameFinder.strName = strName
NameFinder.FindCount = 1
Else
NameFinder.strName = ""
NameFinder.FindCount = 0.001
End If
End Function
So, now to use in code? You can go like this:
dim MyResults as SearchResult
MyResults = NameFinder("My eassy", "Joe Blow")
debug.print "Name found result = " & MyResults.strName
debug.print "Count of find = " & MyResult.FindCount
The VERY nice thing about above is you get full intel-sense in your code editor.
eg this:
So by building a custom data type, you can use "one" assignment for the return type. And you get nice type checking and inteli-sense in the VBA code editor.
And you can even do this:
But, to get both variables, then you would in theory wind up calling the function two times. So, you can actually use the function without declarer of variables like this:
Debug.Print NameFinder("MyEassy", "Joe blow").strName
Debug.Print NameFinder("MyEassy", "Joe blow").FindCount
So, I don't recommend the above, but in the case in which you ONLY want one of the return values, then the raw expression (function) like above would be a use case (and no need to even declare a return variable).
But, without a doubt, define a custom type in code as per above. The reason is now you get a really nice VBA editor type-checking, inteli-sense, and also that you only have to declare "one" variable that holds two values.
In fact, the results are very much like JavaScript, or even c# in which you declare a "class" type. So with a custom "type" you are declaring a data type of your own. And the beauty of this is if you need say 3 values, then once again you create a type with 3 "inside" values.
The you ONLY have to declare that one variable as the custom type.
With this you get:
Very valuable compile time syntax and data type checking of the var types you are using.
You get GREAT VBA inteli-sense while coding - which means less coding mistakes.
And you type far less typing in the VBA editor as it will pop-up the choices for you as you write code. And you can't type or choose the wrong sub - type, as the compiler will catch this.

Sum the repeated string list values and merge as one using linq in .net

The below code I tried to sum up the string value with the list values, it happens, but other values are not shown in return. I need to sum the values and other value should be returned to the object using linq in vb.net.
My code:
Dim lstrTaxValue As String = "YQ$40"
Dim lstaValues As New List(Of String)
lstaValues.Add("YQ$10")
lstaValues.Add("TQ$3")
lstaValues.Add("PQ$8")
lstaValues.Add("YQ$10")
lstaValues.Add("TQ$3")
lstaValues.Add("AQ$5")
Dim lobjTValues = (From lstr In lstaValues
From lval In lstrTaxValue.Split(" ")
Where (lstr.Split("$")(0) = CStr(lval).Split("$")(0))
Select (CStr(lval).Split("$")(0) & "$" & (CDbl(CStr(lval).Split("$")(1)) + CDbl(lstr.Split("$")(1))))).ToList()
What am I doing wrong?
To quote Jon Skeet...
Change some value inside the List<T>
In comments...
Why do you want to use lambda expressions? The foreach code works fine and is simple. LINQ is for querying data, not mutating it. – Jon Skeet
Your objective does not seem to lend itself to Linq.
Private Sub OPCode()
Dim lstrTaxValue As String = "YQ$40"
Dim lstaValues As New List(Of String)
lstaValues.Add("YQ$10")
lstaValues.Add("TQ$3")
lstaValues.Add("PQ$8")
lstaValues.Add("YQ$10")
lstaValues.Add("TQ$3")
lstaValues.Add("AQ$5")
Dim TaxValue = lstrTaxValue.Split("$"c)
For i = 0 To lstaValues.Count - 1
If lstaValues(i).Split("$"c)(0) = TaxValue(0) Then
lstaValues(i) = TaxValue(0) & "$" & CStr(CDbl(lstaValues(i).Split("$"c)(1)) + CDbl(TaxValue(1)))
End If
Next
For Each s In lstaValues
Debug.Print(s)
Next
End Sub
Result:
YQ$50
TQ$3
PQ$8
YQ$50
TQ$3
AQ$5

Listview - add File type & Last modified Subitems

I'm trying to add "file type" and "last modified" to my Listview when adding items in It same as in Explorer, but I don't find what property should be assigned to SubItem. Here is my code:
For Each MyFile As IO.FileInfo In ItemDirectory.GetFiles
Dim lvi As New ListViewItem
lvi.Tag = mFile.FullName
lvi.Text = mFile.Name
lvi.ImageKey = CacheShellIcon(mFile.FullName)
Listview1.Items.Add(lvi)
lvi.SubItems.Add("File type ??")
lvi.SubItems.Add(mFile.LastAccessTime.ToShortDateString & " " & mFile.LastAccessTime.ToShortTimeString) 'This isn't same as last modified ?
Next
If somebody knows how to do It please let me know, I want to have this in my Details view.
The linked answer provides an all-purpose way to get all the extended properties. With 300+ elements in newer Windows versions it is clearly overkill to fetch them all if you are only interested in one or two. This returns just the file type. A better approach might be to pass a "shopping list" of desired property names.
As before, you need to add a reference to Microsoft Shell Controls and Automation or Microsoft Shell Folder View Router based on your OS version.
Imports Shell32
Imports SHDocVw
Partial Friend Class Shell32Methods
Friend Shared Function GetShellFileProperty(filepath As String, index As Int32) As String
Dim shell As New Shell32.Shell
Dim shFolder As Shell32.Folder
shFolder = shell.NameSpace(Path.GetDirectoryName(filepath))
' get shell data for this file, cast to folder item
Dim shFolderItem = DirectCast(shFolder.Items().Item(Path.GetFileName(filepath)),
Shell32.ShellFolderItem)
If shFolderItem IsNot Nothing Then
Return shFolder.GetDetailsOf(shFolderItem, index)
Else
Return String.Empty
End If
End Function
...
End Class
Usage:
Dim lvi As ListViewItem
Dim fileType As String
For Each f As String In Directory.EnumerateFiles("C:\Temp\ShellTest")
fileType = Shell32Methods.GetShellFileProperty(f, 9)
lvi = New ListViewItem
lvi.Text = Path.GetFileName(f)
lvi.SubItems.Add(fileType)
lvFiles.Items.Add(lvi)
Next
Ideally, you'd want to create an Enum for the properties so the code could avoid magic numbers:
fileType = Shell32Methods.GetShellFileProperty(f, Shell32FileProps.FileType)
As noted elsewhere, the index of the ones >260 or so can change depending on the OS version. That could be easily modified to accept an Enum/Int array and return a list of values so as to prevent iterating all 300+ propertied to get one or three.
For filetype you can use lvi.SubItems.Add(MyFile.Extension)
and for the "last modified" date, of course the last modified! :D
lvi.SubItems.Add(MyFile.LastWriteTime.ToShortDateString)
Last write and last access are not the same ;)
I figured out another solution, I think this one is easier, at least for me :
Public Function ExProperty(filepath As String, PropertyItem As Integer)
Dim arrHeaders As New List(Of String)()
Dim shell As New Shell
Dim rFolder As Folder = shell.[NameSpace](Path.GetDirectoryName(filepath))
Dim rFiles As FolderItem = rFolder.ParseName(Path.GetFileName(filepath))
'I needed only File type so I looped to 2 only (2 is the file type in my case - Windows 10 -
' to see all available properties do a loop
' 0 To Short.MaxValue - 1" and then extract whatever property you like)
For i As Integer = 0 To 2
Dim value As String = rFolder.GetDetailsOf(rFiles, i).Trim()
arrHeaders.Add(value)
Next
Dim DesiredProperty As String
DesiredProperty = arrHeaders.Item(PropertyItem)
Return DesiredProperty
End Function
Usage with Listview just simply (this adds File type subitem):
Listview1_Item.SubItems.Add(ExProperty(filepath, 2))
As in all solutions, a reference to Microsoft Shell Controls and Automation must be set.

String.Format for Integer is Incorrect in VB.Net

This should not happen, so I must be missing something simple.
In the below VB function, I am trying to generate a list of part numbers to display on the screen using this format statement:
ticket = String.Format("{0:000}-{1:00000}-{2:00}", storeNumber, order, release)
With that, ticket should have the format xxx-yyyyy-zz so that the ticket is human readable and the other parts of my application can parse this data.
Public Shared Function GetShipTickets(storeNumber As Integer, auditor As String, startDate As DateTime) As ShipTickets
Dim list As New ShipTickets(auditor)
list.Display = String.Format("Since {0:MMMM d}.", startDate)
Const sqlCmd As String =
"SELECT TICKET_STORE, TICKET_ORDER, TICKET_RELEASE " &
"FROM TBLRELHDR " &
"WHERE TICKET_STORE=#TICKET_STORE AND STATUS='C' AND #CREATE_DATE<=CREATE_DATE " &
"ORDER BY TICKET_ORDER, TICKET_RELEASE, CREATE_DATE; "
Dim table As New DataTable()
Using cmd As New DB2Command(sqlCmd, Db2CusDta)
cmd.Parameters.Add("TICKET_STORE", DB2Type.SmallInt).Value = storeNumber
cmd.Parameters.Add("CREATE_DATE", DB2Type.Char, 8).Value = String.Format("{0:yyyyMMdd}", startDate)
table.Load(cmd.ExecuteReader())
End Using
If 0 < table.Rows.Count Then
For Each row As DataRow In table.Rows
Dim order As String = String.Format("{0}", row("TICKET_ORDER")).Trim().ToUpper()
Dim release As String = String.Format("{0}", row("TICKET_RELEASE")).Trim().ToUpper()
Dim ticket As String = String.Format("{0:000}-{1:00000}-{2:00}", storeNumber, order, release)
list.Add(ticket)
Next
End If
list.Sort()
Return list
End Function
It is not working, though.
Also, when I view my data on the screen, it is not displaying correctly either:
What is going on?
VB is not my strongest programming language. Either there is some nuance of VB that I am unaware of or the compiler is messing up.
Using Visual Studio 2012
The format "{2:00}" works on integers, not strings. It won't automatically convert a string consisting of digits into an integer. Manually convert the strings to integers:
Dim ticket As String = String.Format("{0:000}-{1:00000}-{2:00}",
CInt(storeNumber),
CInt(order),
CInt(release))

Adding values to array

I am trying to run an event which will search through the different files in a given directory. The goal is to have it search for all files that begin with 'SP_', which are .sql files containing Stored Procedures. I would then like to add the full text of these Procedures to an array to be used later. This is causing an error when run, which I believe is because 'FullProcedureArray()', the string array I am trying to load does not have defined boundaries. When I declare it as 'FullProcedureArray(7)', or with some other value, it appears to run fine. But I don't want to have to hard-code a boundary for 'FullProcedureArray'; I would rather let it be defined by whatever the number of files in the folder is.
My question: Is there a way to declare 'FullProcedureArray' without having to give it an absolute value? I may just be missing something painfully obvious, but I haven't worked with this type of array much in the past. Thanks in advance for your help.
Dim AppDataLocation As String = "C:\Files\TestFiles\"
Dim ProcedureArray As String()
Dim ProcedureText As String
Dim FullProcedureArray() As String
Dim sourceDirectoryInfo As New System.IO.DirectoryInfo(AppDataLocation)
Dim fileSystemInfo As System.IO.FileSystemInfo
Dim i As Integer = 0
For Each fileSystemInfo In sourceDirectoryInfo.GetFileSystemInfos
If (fileSystemInfo.Name.Contains("SP_")) Then
ProcedureArray = System.IO.File.ReadAllLines(AppDataLocation & fileSystemInfo.Name)
ProcedureText = Join(ProcedureArray, "")
FullProcedureArray.SetValue(ProcedureText, i)
i = (i + 1)
End If
Next
An array by definition has a fixed upper bound. If you don't want a fixed upper bound, don't use an array. Use, for example, a List(Of String) instead:
Dim AppDataLocation As String = "C:\Files\TestFiles\"
Dim ProcedureList As New List(Of String)
Dim sourceDirectoryInfo As New System.IO.DirectoryInfo(AppDataLocation)
For Each fileSystemInfo As System.IO.FileSystemInfo In sourceDirectoryInfo.GetFileSystemInfos
If (fileSystemInfo.Name.Contains("SP_")) Then
Dim ProcedureText As String = _
System.IO.File.ReadAllText(AppDataLocation & fileSystemInfo.Name)
ProcedureList.Add(ProcedureText)
End If
Next
If, for some reason, you still need the result as an array afterwards, simply convert the list to an array:
Dim myArray() As String = ProcedureList.ToArray()
If you don't want to give a size to your array or want to change at runtime, you can use "Redim Preserve"
http://msdn.microsoft.com/en-us/library/w8k3cys2%28v=vs.71%29.aspx