Home > database >  Sort Excel worksheets based on name, which is a date
Sort Excel worksheets based on name, which is a date

Time:11-11

So I've got this Excel workbook that has some macro's. Users are presented with a button to either create a worksheet with the current date as name, or enter a date manually and that worksheet will be created.

Now the issue: The worksheet has two sheet ('Initial' and 'Version') that must be first and last. However, all worksheets created in between should be sorted on date everytime a new sheet is created. And I mean sorted on date, the sheets are 'DD-MM-YY' so e.g. I could have names like '1-11-21', '2-11-21', '11-11-21' and '21-11-21' in the same workbook and it should be sorted ascending.

Any suggestions? A normal sort just messes things up I found (1-11-21 and 11-11-21, followed by '2-11-21' and '21-11-21'....

Thanks,

Jasper

CodePudding user response:

Sorting sheets of a workbook is rather easy, there a numerous examples out there, looking more or less like this:

Sub SortSheets(Optional wb As Workbook = Nothing)
    If wb Is Nothing Then Set wb = ActiveWorkbook  ' (or maybe ThisWorkbook)
    
    Application.ScreenUpdating = False
    Dim i As Long, j As Long
    
    For i = 1 To wb.Worksheets.Count - 1
        For j = i   1 To wb.Worksheets.Count
            ' ==> The following line needs to be replaced!
            If wb.Worksheets(j).Name < wb.Worksheets(i).Name Then
                wb.Worksheets(j).Move before:=wb.Worksheets(i)
            End If
        Next j
    Next i
    ' Application.ScreenUpdating = True
End Sub

The only logic you need to change now is the If-statement. Instead of comparing the names of the sheets, you need to find a custom logic that compares the names of the two sheets.

Your logic is basically: If the name is Initial, sort it to the top, if it is Version, sort it to the end and for all the others, sort them by the date the name is representing.

I created a small function that calculates a number from the name. The Initial sheets gets 0, the Version gets a arbitrary high number, a worksheet with a date in the name gets the date value (a date is basically a double value in VBA) by converting the name into the date. If the name cannot be converted to a date, the value will be so that the sheet will be sorted to the end (but before the version sheet).

Function getSortNumber(ws As Worksheet) As Double
    Const MaxNumber = 100000
    
    If ws.Name = "Initial" Then
        ' Sort Initial to the beginning
        getSortNumber = 0
    ElseIf ws.Name = "Version" Then
        ' Sort Version to the end
        getSortNumber = MaxNumber   ws.Parent.Sheets.Count
    Else
        ' Create real date fom name
        Dim d As Date, tokens() As String
        tokens = Split(ws.Name, "-")
        On Error Resume Next
        d = DateSerial(Val(tokens(2)), Val(tokens(1)), Val(tokens(0)))
        On Error GoTo 0
        If d = 0 Then
            ' Failed to convert to date, sort to end
            getSortNumber = MaxNumber   ws.Index
        Else
            ' Sort according to the date value
            getSortNumber = CDbl(d)
        End If
    End If
End Function

You can adapt the function easily if your needs changed (eg date format, or you can have extra text with the date, or you want to sort the version sheet to the beginning, or you have additional sheets with different names...). The sort function itself will not change at all, only the comparison logic.

Now all you have to do is change the line in the sort routine:

If wb.Worksheets(j).Name < wb.Worksheets(i).Name Then

to

If getSortNumber(wb.Worksheets(j)) < getSortNumber(wb.Worksheets(i)) Then

CodePudding user response:

Insert Date Worksheet

  • Note the following in two-digit year notation:

    01/01/30 ... 01/01/1930
    12/31/99 ... 12/31/1999
    01/01/00 ... 01/01/2000
    12/31/29 ... 12/31/2029
    
  • Some complications are present due to:

    Sub Test1()
        Debug.Print DateSerial(111, 22, 33) ' Result '11/02/112'
        Debug.Print DateSerial(21, 2, 30) ' Result ' 03/02/2021
    End Sub
    
  • The following will not sort any previously added worksheets. It will just insert the new worksheet in the right spot i.e. before the first worksheet with a greater date than the date supplied, or before the last worksheet (if no greater date).

Option Explicit

Sub InsertDateWorksheet()
' Needs 'RefWorksheet', 'GetTwoDigitYearDate' and 'IsLeapYear'.
    Const ProcName As String = "InsertDateWorksheet"

    Const First As String = "Initial"
    Const Last As String = "Version"
    Const Delimiter As String = "-"
    
    Dim wb As Workbook: Set wb = ThisWorkbook
    
    ' First Worksheet
    Dim fws As Worksheet: Set fws = RefWorksheet(wb, First, True)
    If fws Is Nothing Then Exit Sub
    If Not fws Is wb.Sheets(1) Then
        fws.Move Before:=wb.Sheets(1)
    End If
    ' Last Worksheet
    Dim lws As Worksheet: Set lws = RefWorksheet(wb, Last, True)
    If lws Is Nothing Then Exit Sub
    Dim shCount As Long: shCount = wb.Sheets.Count
    If Not lws Is wb.Sheets(shCount) Then
        lws.Move After:=wb.Sheets(shCount)
    End If
    
    Dim NewDate As Date: NewDate = InputDateText(True)
    If NewDate = 0 Then Exit Sub
    
    Dim NewDateString As String: NewDateString = Day(NewDate) & Delimiter _
        & Month(NewDate) & Delimiter & Right(CStr(Year(NewDate)), 2)
    
    Dim nws As Worksheet: Set nws = RefWorksheet(wb, NewDateString)
    If Not nws Is Nothing Then
        MsgBox "The worksheet '" & NewDateString & "' already exists.", _
            vbCritical, ProcName
        Exit Sub
    End If
    
    Dim ws As Worksheet
    Dim wsDate As Date
    
    For Each ws In wb.Worksheets
        Select Case ws.Name
        Case First
        Case Last
            Worksheets.Add(Before:=lws).Name = NewDateString
        Case Else
            wsDate = GetTwoDigitYearDate(ws.Name, Delimiter)
            If NewDate < wsDate Then
                Worksheets.Add(Before:=ws).Name = NewDateString
                Exit For
            End If
        End Select
    Next ws
            
    MsgBox "Worksheet added.", vbInformation, ProcName

End Sub

Function RefWorksheet( _
    ByVal wb As Workbook, _
    ByVal WorksheetName As String, _
    Optional ByVal DoWriteMessage As Boolean = False) _
As Worksheet
    Const ProcName As String = "RefWorksheet"
    On Error Resume Next
        Set RefWorksheet = wb.Worksheets(WorksheetName)
    On Error GoTo 0
    If DoWriteMessage Then
        If RefWorksheet Is Nothing Then
            MsgBox "Worksheet '" & WorksheetName & "' not found.", _
                vbCritical, ProcName
            Exit Function
        End If
    End If
End Function

Function InputDateText( _
    Optional ByVal DoWriteMessage As Boolean = False) _
As Date
' Needs 'GetTwoDigitYearDate' and 'IsLeapYear'.
    Const ProcName As String = "InputDateText"
    
    Const InputFormat As String = "d-m-yy"
    
    Const nTitle As String = "Input Date Text"
    Dim nPrompt As String
    nPrompt = "Please enter a date in '" & InputFormat & "' format..."
    Dim nDefault As String: nDefault = Format(Date, InputFormat)
    
    Dim NewDateString As Variant: NewDateString = Application.InputBox( _
        nPrompt, nTitle, nDefault, , , , , 2)
    If NewDateString = False Then
        MsgBox "You canceled.", vbExclamation, ProcName
        Exit Function
    End If
    
    InputDateText = GetTwoDigitYearDate(NewDateString, "-")
    If DoWriteMessage Then
        If InputDateText = 0 Then
            MsgBox "The string '" & NewDateString & "' is not valid.", _
                vbCritical, ProcName
        End If
    End If
    
End Function

Function GetTwoDigitYearDate( _
    ByVal DateString As String, _
    Optional ByVal Delimiter As String = "-") _
As Date
' Needs 'IsLeapYear'.
    On Error GoTo ClearError
        
    Dim ArrDate() As String: ArrDate = Split(DateString, Delimiter)
    
    Dim nYear As Long: nYear = CLng(ArrDate(2))
    Select Case nYear
    Case Is < 0, Is > 99
        Exit Function
    Case Else
        nYear = IIf(nYear > 29, nYear   1900, nYear   2000)
    End Select
    
    Dim nMonth As Long: nMonth = CLng(ArrDate(1))
    Select Case nMonth
    Case Is < 1, Is > 12
        Exit Function
    End Select
    
    Dim nDay As Long: nDay = CLng(ArrDate(0))
    Select Case nDay
    Case Is < 1, Is > 31
        Exit Function
    End Select
    Select Case nMonth
    Case 4, 6, 9, 11
        If nDay = 31 Then Exit Function
    Case 2
        If nDay > 29 Then Exit Function
        If nDay = 29 Then
            If Not IsLeapYear(nYear) Then Exit Function
        End If
    End Select
                
    GetTwoDigitYearDate = DateSerial(nYear, nMonth, nDay)

ProcExit:
    Exit Function
ClearError:
    Resume ProcExit
End Function

Function IsLeapYear( _
    TestYear As Long) _
As Boolean
    If TestYear Mod 4 = 0 Then
        If TestYear Mod 100 = 0 Then
            If TestYear Mod 400 = 0 Then
            ' Accounting for e.g. years 2000, 2400, 2800...8800, 9200, 9600.
                IsLeapYear = True
            'Else
            ' Accounting for e.g. years 2100, 2200, 2300...9700, 9800, 9900.
            'isLeapYear = False
            End If
        Else
        ' Accounting for e.g. years 1904, 1908, 1912...1988, 1992, 1996.
            IsLeapYear = True
        End If
    'Else
    ' Accounting for e.g. years 1901, 1902, 1903...1997, 1998, 1999.
    'isLeapYear = False
    End If
End Function
  • Related