Загрузка данных
Option Explicit
Public Sub RefreshMonthData()
Dim folderPath As String
Dim fileName As String
Dim fullPath As String
Dim wb As Workbook
Dim wsSummary As Worksheet, wsRepairs As Worksheet, wsReasons As Worksheet
Dim wsFiles As Worksheet, wsDash As Worksheet
Dim dictSummary As Object, dictReasons As Object
Dim repairsRow As Long, filesRow As Long
Dim reportName As String
Dim dt As Variant
On Error GoTo EH
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.EnableEvents = False
Application.AskToUpdateLinks = False
Application.Calculation = xlCalculationManual
Application.StatusBar = "Обновление отчета..."
folderPath = ThisWorkbook.Path
If Len(folderPath) = 0 Then
MsgBox "Сначала сохраните файл отчета в папку с исходными файлами.", vbExclamation
GoTo SafeExit
End If
reportName = ThisWorkbook.Name
Set dictSummary = CreateObject("Scripting.Dictionary")
Set dictReasons = CreateObject("Scripting.Dictionary")
PrepareSheets wsDash, wsSummary, wsRepairs, wsReasons, wsFiles
repairsRow = 2
filesRow = 2
fileName = Dir(folderPath & "\*.xls*")
Do While fileName <> ""
If LCase(fileName) <> LCase(reportName) Then
If Left$(fileName, 2) <> "~$" Then
fullPath = folderPath & "\" & fileName
dt = ExtractDateFromFilename(fileName)
If IsDate(dt) Then
Set wb = Nothing
On Error Resume Next
Set wb = Workbooks.Open(Filename:=fullPath, ReadOnly:=True, UpdateLinks:=False, IgnoreReadOnlyRecommended:=True)
On Error GoTo EH
If Not wb Is Nothing Then
ProcessWorkbook wb, CDate(dt), fileName, dictSummary, dictReasons, wsRepairs, repairsRow, wsFiles, filesRow
wb.Close SaveChanges:=False
Set wb = Nothing
Else
LogFile wsFiles, filesRow, fileName, CDate(dt), "Ошибка", "Не удалось открыть файл"
filesRow = filesRow + 1
End If
Else
LogFile wsFiles, filesRow, fileName, Empty, "Пропущен", "Не найдена дата в имени файла"
filesRow = filesRow + 1
End If
End If
End If
fileName = Dir
Loop
WriteSummary wsSummary, dictSummary
WriteReasons wsReasons, dictReasons
BuildDashboard wsDash, wsSummary, wsReasons, wsFiles
FormatSheets wsSummary, wsRepairs, wsReasons, wsFiles, wsDash
MsgBox "Обновление завершено.", vbInformation
SafeExit:
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.EnableEvents = True
Application.AskToUpdateLinks = True
Application.Calculation = xlCalculationAutomatic
Application.StatusBar = False
Exit Sub
EH:
MsgBox "Ошибка при обновлении: " & Err.Description, vbCritical
Resume SafeExit
End Sub
Public Sub CreateRefreshButton()
Dim ws As Worksheet
Dim shp As Shape
Set ws = GetOrCreateSheetSafe("Dash")
On Error Resume Next
ws.Shapes("btnRefresh").Delete
On Error GoTo 0
Set shp = ws.Shapes.AddShape(msoShapeRoundedRectangle, 700, 20, 180, 35)
shp.Name = "btnRefresh"
shp.TextFrame.Characters.Text = "Обновить данные"
shp.OnAction = "RefreshMonthData"
End Sub
Private Sub PrepareSheets(ByRef wsDash As Worksheet, ByRef wsSummary As Worksheet, ByRef wsRepairs As Worksheet, ByRef wsReasons As Worksheet, ByRef wsFiles As Worksheet)
Set wsDash = GetOrCreateSheetSafe("Dash")
Set wsSummary = GetOrCreateSheetSafe("Summary")
Set wsRepairs = GetOrCreateSheetSafe("Repairs")
Set wsReasons = GetOrCreateSheetSafe("Reasons")
Set wsFiles = GetOrCreateSheetSafe("Files")
wsDash.Cells.Clear
wsSummary.Cells.Clear
wsRepairs.Cells.Clear
wsReasons.Cells.Clear
wsFiles.Cells.Clear
wsSummary.Range("A1:H1").Value = Array("Дата", "Файлов", "Ремонтов", "Выработка ДЭС", "Расход топлива", "ДГУ в работе", "ДГУ в резерве", "ДГУ в ремонте")
wsRepairs.Range("A1:F1").Value = Array("Дата", "Источник", "Объект/оборудование", "Причина", "Примечание", "Исходный файл")
wsReasons.Range("A1:B1").Value = Array("Причина", "Количество")
wsFiles.Range("A1:D1").Value = Array("Файл", "Дата", "Статус", "Комментарий")
End Sub
Private Sub ProcessWorkbook(ByVal wb As Workbook, ByVal dt As Date, ByVal fileName As String, ByVal dictSummary As Object, ByVal dictReasons As Object, ByVal wsRepairs As Worksheet, ByRef repairsRow As Long, ByVal wsFiles As Worksheet, ByRef filesRow As Long)
Dim sumGeneration As Double, sumFuel As Double
Dim cntRepairs As Long, cntWork As Long, cntReserve As Long, cntRepairState As Long
Dim okAny As Boolean
okAny = False
sumGeneration = ReadDES(wb, cntWork, cntReserve, cntRepairState)
If sumGeneration <> 0 Or cntWork <> 0 Or cntReserve <> 0 Or cntRepairState <> 0 Then okAny = True
sumFuel = ReadBoilers(wb)
If sumFuel <> 0 Then okAny = True
cntRepairs = ReadRepairs(wb, dt, fileName, dictReasons, wsRepairs, repairsRow)
If cntRepairs <> 0 Then okAny = True
AddSummary dictSummary, dt, 1, cntRepairs, sumGeneration, sumFuel, cntWork, cntReserve, cntRepairState
If okAny Then
LogFile wsFiles, filesRow, fileName, dt, "ОК", "Обработан"
Else
LogFile wsFiles, filesRow, fileName, dt, "ОК", "Файл открыт, но данные не распознаны"
End If
filesRow = filesRow + 1
End Sub
Private Function ReadDES(ByVal wb As Workbook, ByRef cntWork As Long, ByRef cntReserve As Long, ByRef cntRepairState As Long) As Double
Dim ws As Worksheet
Dim headerRow As Long
Dim genCol As Long, statusCol As Long
Dim lastRow As Long, r As Long
Dim v As Variant, txt As String
Dim totalGen As Double
On Error Resume Next
Set ws = wb.Worksheets("ДЭС")
On Error GoTo 0
If ws Is Nothing Then Exit Function
headerRow = FindHeaderRow(ws, Array("выработ", "состоя", "режим", "статус"))
If headerRow = 0 Then headerRow = 1
genCol = FindColumnByKeywords(ws, headerRow, Array("выработ"))
statusCol = FindColumnByKeywords(ws, headerRow, Array("состоя", "режим", "статус"))
lastRow = LastUsedRow(ws)
totalGen = 0
If genCol > 0 Then
For r = headerRow + 1 To lastRow
v = ws.Cells(r, genCol).Value
If IsNumeric(v) Then totalGen = totalGen + CDbl(v)
Next r
End If
If statusCol > 0 Then
For r = headerRow + 1 To lastRow
txt = LCase(Trim(CStr(ws.Cells(r, statusCol).Value)))
If txt <> "" Then
If InStr(txt, "работ") > 0 Then
cntWork = cntWork + 1
ElseIf InStr(txt, "резерв") > 0 Then
cntReserve = cntReserve + 1
ElseIf InStr(txt, "ремонт") > 0 Then
cntRepairState = cntRepairState + 1
End If
End If
Next r
End If
ReadDES = totalGen
End Function
Private Function ReadBoilers(ByVal wb As Workbook) As Double
Dim ws As Worksheet
Dim headerRow As Long
Dim fuelCol As Long
Dim lastRow As Long, r As Long
Dim v As Variant
Dim totalFuel As Double
On Error Resume Next
Set ws = wb.Worksheets("Котельные")
On Error GoTo 0
If ws Is Nothing Then Exit Function
headerRow = FindHeaderRow(ws, Array("расход", "топлив"))
If headerRow = 0 Then headerRow = 1
fuelCol = FindColumnByKeywords(ws, headerRow, Array("расход", "топлив"))
If fuelCol = 0 Then fuelCol = FindColumnByKeywords(ws, headerRow, Array("топлив"))
lastRow = LastUsedRow(ws)
totalFuel = 0
If fuelCol > 0 Then
For r = headerRow + 1 To lastRow
v = ws.Cells(r, fuelCol).Value
If IsNumeric(v) Then totalFuel = totalFuel + CDbl(v)
Next r
End If
ReadBoilers = totalFuel
End Function
Private Function ReadRepairs(ByVal wb As Workbook, ByVal dt As Date, ByVal fileName As String, ByVal dictReasons As Object, ByVal wsRepairs As Worksheet, ByRef repairsRow As Long) As Long
Dim ws As Worksheet
Dim headerRow As Long
Dim objCol As Long, reasonCol As Long, noteCol As Long
Dim lastRow As Long, r As Long
Dim objVal As String, reasonVal As String, noteVal As String
Dim cnt As Long
On Error Resume Next
Set ws = wb.Worksheets("Ремонт оборудования")
On Error GoTo 0
If ws Is Nothing Then Exit Function
headerRow = FindHeaderRow(ws, Array("прич", "оборуд", "объект", "примеч"))
If headerRow = 0 Then headerRow = 1
objCol = FindColumnByKeywords(ws, headerRow, Array("оборуд", "объект", "наимен"))
reasonCol = FindColumnByKeywords(ws, headerRow, Array("прич"))
noteCol = FindColumnByKeywords(ws, headerRow, Array("примеч", "описан", "коммент"))
lastRow = LastUsedRow(ws)
cnt = 0
For r = headerRow + 1 To lastRow
objVal = ""
reasonVal = ""
noteVal = ""
If objCol > 0 Then objVal = Trim(CStr(ws.Cells(r, objCol).Value))
If reasonCol > 0 Then reasonVal = Trim(CStr(ws.Cells(r, reasonCol).Value))
If noteCol > 0 Then noteVal = Trim(CStr(ws.Cells(r, noteCol).Value))
If objVal <> "" Or reasonVal <> "" Or noteVal <> "" Then
wsRepairs.Cells(repairsRow, 1).Value = dt
wsRepairs.Cells(repairsRow, 2).Value = "Ремонт оборудования"
wsRepairs.Cells(repairsRow, 3).Value = objVal
wsRepairs.Cells(repairsRow, 4).Value = reasonVal
wsRepairs.Cells(repairsRow, 5).Value = noteVal
wsRepairs.Cells(repairsRow, 6).Value = fileName
repairsRow = repairsRow + 1
cnt = cnt + 1
If reasonVal <> "" Then
If dictReasons.Exists(reasonVal) Then
dictReasons(reasonVal) = CLng(dictReasons(reasonVal)) + 1
Else
dictReasons.Add reasonVal, 1
End If
End If
End If
Next r
ReadRepairs = cnt
End Function
Private Sub AddSummary(ByVal dictSummary As Object, ByVal dt As Date, ByVal fileCount As Long, ByVal repairCount As Long, ByVal generation As Double, ByVal fuel As Double, ByVal workCnt As Long, ByVal reserveCnt As Long, ByVal repairStateCnt As Long)
Dim key As String
Dim arr As Variant
key = Format(dt, "yyyy-mm-dd")
If dictSummary.Exists(key) Then
arr = dictSummary(key)
Else
ReDim arr(1 To 7)
arr(1) = 0
arr(2) = 0
arr(3) = 0#
arr(4) = 0#
arr(5) = 0
arr(6) = 0
arr(7) = 0
End If
arr(1) = arr(1) + fileCount
arr(2) = arr(2) + repairCount
arr(3) = arr(3) + generation
arr(4) = arr(4) + fuel
arr(5) = arr(5) + workCnt
arr(6) = arr(6) + reserveCnt
arr(7) = arr(7) + repairStateCnt
dictSummary(key) = arr
End Sub
Private Sub WriteSummary(ByVal ws As Worksheet, ByVal dictSummary As Object)
Dim keys As Variant, i As Long, r As Long, arr As Variant
If dictSummary.Count = 0 Then Exit Sub
keys = dictSummary.Keys
SortStringDates keys
r = 2
For i = LBound(keys) To UBound(keys)
arr = dictSummary(keys(i))
ws.Cells(r, 1).Value = CDate(keys(i))
ws.Cells(r, 2).Value = arr(1)
ws.Cells(r, 3).Value = arr(2)
ws.Cells(r, 4).Value = arr(3)
ws.Cells(r, 5).Value = arr(4)
ws.Cells(r, 6).Value = arr(5)
ws.Cells(r, 7).Value = arr(6)
ws.Cells(r, 8).Value = arr(7)
r = r + 1
Next i
End Sub
Private Sub WriteReasons(ByVal ws As Worksheet, ByVal dictReasons As Object)
Dim keys As Variant, i As Long, r As Long
Dim lastRow As Long
If dictReasons.Count = 0 Then Exit Sub
keys = dictReasons.Keys
r = 2
For i = LBound(keys) To UBound(keys)
ws.Cells(r, 1).Value = keys(i)
ws.Cells(r, 2).Value = dictReasons(keys(i))
r = r + 1
Next i
lastRow = LastUsedRow(ws)
If lastRow >= 2 Then
ws.Range("A1:B" & lastRow).Sort Key1:=ws.Range("B2"), Order1:=xlDescending, Header:=xlYes
End If
End Sub
Private Sub BuildDashboard(ByVal wsDash As Worksheet, ByVal wsSummary As Worksheet, ByVal wsReasons As Worksheet, ByVal wsFiles As Worksheet)
Dim lastSummaryRow As Long, lastReasonsRow As Long
Dim ch As ChartObject
wsDash.Cells.Clear
wsDash.Range("A1").Value = "Месячный дашборд"
wsDash.Range("A1").Font.Size = 18
wsDash.Range("A1").Font.Bold = True
wsDash.Range("A3").Value = "Файлов обработано"
wsDash.Range("B3").Formula = "=MAX(0,COUNTA(Files!A:A)-1)"
wsDash.Range("D3").Value = "Дней с данными"
wsDash.Range("E3").Formula = "=MAX(0,COUNTA(Summary!A:A)-1)"
wsDash.Range("A4").Value = "Всего ремонтов"
wsDash.Range("B4").Formula = "=SUM(Summary!C:C)"
wsDash.Range("D4").Value = "Сумма выработки"
wsDash.Range("E4").Formula = "=SUM(Summary!D:D)"
wsDash.Range("A5").Value = "Расход топлива"
wsDash.Range("B5").Formula = "=SUM(Summary!E:E)"
wsDash.Range("D5").Value = "Последняя дата"
wsDash.Range("E5").Formula = "=MAX(Summary!A:A)"
wsDash.Range("E5").NumberFormat = "dd.mm.yyyy"
lastSummaryRow = LastUsedRow(wsSummary)
lastReasonsRow = LastUsedRow(wsReasons)
DeleteAllCharts wsDash
If lastSummaryRow >= 2 Then
Set ch = wsDash.ChartObjects.Add(Left:=20, Top:=110, Width:=420, Height:=220)
With ch.Chart
.ChartType = xlColumnClustered
Do While .SeriesCollection.Count > 0
.SeriesCollection(1).Delete
Loop
.SeriesCollection.NewSeries
.SeriesCollection(1).Name = "=""Ремонты"""
.SeriesCollection(1).XValues = wsSummary.Range("A2:A" & lastSummaryRow)
.SeriesCollection(1).Values = wsSummary.Range("C2:C" & lastSummaryRow)
.HasTitle = True
.ChartTitle.Text = "Ремонты по дням"
End With
Set ch = wsDash.ChartObjects.Add(Left:=460, Top:=110, Width:=420, Height:=220)
With ch.Chart
.ChartType = xlLine
Do While .SeriesCollection.Count > 0
.SeriesCollection(1).Delete
Loop
.SeriesCollection.NewSeries
.SeriesCollection(1).Name = "=""Выработка"""
.SeriesCollection(1).XValues = wsSummary.Range("A2:A" & lastSummaryRow)
.SeriesCollection(1).Values = wsSummary.Range("D2:D" & lastSummaryRow)
.HasTitle = True
.ChartTitle.Text = "Выработка по дням"
End With
Set ch = wsDash.ChartObjects.Add(Left:=20, Top:=350, Width:=420, Height:=220)
With ch.Chart
.ChartType = xlLine
Do While .SeriesCollection.Count > 0
.SeriesCollection(1).Delete
Loop
.SeriesCollection.NewSeries
.SeriesCollection(1).Name = "=""Топливо"""
.SeriesCollection(1).XValues = wsSummary.Range("A2:A" & lastSummaryRow)
.SeriesCollection(1).Values = wsSummary.Range("E2:E" & lastSummaryRow)
.HasTitle = True
.ChartTitle.Text = "Расход топлива по дням"
End With
Set ch = wsDash.ChartObjects.Add(Left:=460, Top:=350, Width:=420, Height:=220)
With ch.Chart
.ChartType = xlColumnClustered
Do While .SeriesCollection.Count > 0
.SeriesCollection(1).Delete
Loop
.SeriesCollection.NewSeries
.SeriesCollection(1).Name = "=""Последняя дата"""
.SeriesCollection(1).XValues = Array("В работе", "В резерве", "В ремонте")
.SeriesCollection(1).Values = Array(wsSummary.Cells(lastSummaryRow, 6).Value, wsSummary.Cells(lastSummaryRow, 7).Value, wsSummary.Cells(lastSummaryRow, 8).Value)
.HasTitle = True
.ChartTitle.Text = "Состояние ДГУ"
End With
End If
If lastReasonsRow >= 2 Then
Set ch = wsDash.ChartObjects.Add(Left:=20, Top:=590, Width:=860, Height:=250)
With ch.Chart
.ChartType = xlBarClustered
Do While .SeriesCollection.Count > 0
.SeriesCollection(1).Delete
Loop
.SeriesCollection.NewSeries
.SeriesCollection(1).Name = "=""Причины"""
.SeriesCollection(1).XValues = wsReasons.Range("A2:A" & WorksheetFunction.Min(lastReasonsRow, 11))
.SeriesCollection(1).Values = wsReasons.Range("B2:B" & WorksheetFunction.Min(lastReasonsRow, 11))
.HasTitle = True
.ChartTitle.Text = "Топ причин ремонтов"
End With
End If
End Sub
Private Sub DeleteAllCharts(ByVal ws As Worksheet)
Dim i As Long
For i = ws.ChartObjects.Count To 1 Step -1
ws.ChartObjects(i).Delete
Next i
End Sub
Private Sub FormatSheets(ByVal wsSummary As Worksheet, ByVal wsRepairs As Worksheet, ByVal wsReasons As Worksheet, ByVal wsFiles As Worksheet, ByVal wsDash As Worksheet)
FormatOneSheet wsSummary
FormatOneSheet wsRepairs
FormatOneSheet wsReasons
FormatOneSheet wsFiles
wsSummary.Columns("A").NumberFormat = "dd.mm.yyyy"
wsRepairs.Columns("A").NumberFormat = "dd.mm.yyyy"
wsFiles.Columns("B").NumberFormat = "dd.mm.yyyy"
wsDash.Columns("A:F").ColumnWidth = 16
wsDash.Range("A3:E5").Font.Bold = True
End Sub
Private Sub FormatOneSheet(ByVal ws As Worksheet)
With ws.Rows(1)
.Font.Bold = True
.Interior.Color = RGB(217, 225, 242)
End With
ws.Cells.EntireColumn.AutoFit
On Error Resume Next
ws.Rows(1).AutoFilter
On Error GoTo 0
End Sub
Private Sub LogFile(ByVal ws As Worksheet, ByVal rowNum As Long, ByVal fileName As String, ByVal dt As Variant, ByVal statusText As String, ByVal commentText As String)
ws.Cells(rowNum, 1).Value = fileName
If IsDate(dt) Then ws.Cells(rowNum, 2).Value = CDate(dt)
ws.Cells(rowNum, 3).Value = statusText
ws.Cells(rowNum, 4).Value = commentText
End Sub
Private Function GetOrCreateSheetSafe(ByVal sheetName As String) As Worksheet
Dim ws As Worksheet
Dim candidate As String
Dim i As Long
For Each ws In ThisWorkbook.Worksheets
If LCase(ws.Name) = LCase(sheetName) Then
Set GetOrCreateSheetSafe = ws
Exit Function
End If
Next ws
Set ws = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
candidate = CleanSheetName(sheetName)
If candidate = "" Then candidate = "SheetX"
On Error Resume Next
ws.Name = candidate
If Err.Number = 0 Then
On Error GoTo 0
Set GetOrCreateSheetSafe = ws
Exit Function
End If
Err.Clear
For i = 1 To 99
ws.Name = Left$(candidate, 28) & "_" & CStr(i)
If Err.Number = 0 Then Exit For
Err.Clear
Next i
On Error GoTo 0
Set GetOrCreateSheetSafe = ws
End Function
Private Function CleanSheetName(ByVal s As String) As String
Dim badChars As Variant
Dim ch As Variant
badChars = Array(":", "\", "/", "?", "*", "[", "]")
CleanSheetName = Trim$(s)
For Each ch In badChars
CleanSheetName = Replace(CleanSheetName, CStr(ch), "")
Next ch
If Len(CleanSheetName) > 31 Then
CleanSheetName = Left$(CleanSheetName, 31)
End If
End Function
Private Function ExtractDateFromFilename(ByVal fileName As String) As Variant
Dim re As Object, m As Object
Dim d As Integer, mn As Integer, y As Integer
Set re = CreateObject("VBScript.RegExp")
re.Global = False
re.IgnoreCase = True
re.Pattern = "(\d{1,2})[.\-_](\d{1,2})[.\-_](\d{4})"
If re.Test(fileName) Then
Set m = re.Execute(fileName)(0)
d = CInt(m.SubMatches(0))
mn = CInt(m.SubMatches(1))
y = CInt(m.SubMatches(2))
On Error Resume Next
ExtractDateFromFilename = DateSerial(y, mn, d)
If Err.Number <> 0 Then ExtractDateFromFilename = Empty
On Error GoTo 0
Else
ExtractDateFromFilename = Empty
End If
End Function
Private Function FindHeaderRow(ByVal ws As Worksheet, ByVal keywords As Variant) As Long
Dim r As Long, c As Long, lastCol As Long, txt As String, k As Variant
Dim score As Long, bestScore As Long, bestRow As Long
bestScore = 0
bestRow = 0
For r = 1 To WorksheetFunction.Min(10, ws.UsedRange.Rows.Count)
lastCol = LastUsedColInRow(ws, r)
score = 0
For c = 1 To lastCol
txt = LCase(Trim(CStr(ws.Cells(r, c).Value)))
If txt <> "" Then
For Each k In keywords
If InStr(txt, LCase(CStr(k))) > 0 Then
score = score + 1
Exit For
End If
Next k
End If
Next c
If score > bestScore Then
bestScore = score
bestRow = r
End If
Next r
FindHeaderRow = bestRow
End Function
Private Function FindColumnByKeywords(ByVal ws As Worksheet, ByVal headerRow As Long, ByVal keywords As Variant) As Long
Dim c As Long, lastCol As Long, txt As String, k As Variant
lastCol = LastUsedColInRow(ws, headerRow)
For c = 1 To lastCol
txt = LCase(Trim(CStr(ws.Cells(headerRow, c).Value)))
If txt <> "" Then
For Each k In keywords
If InStr(txt, LCase(CStr(k))) > 0 Then
FindColumnByKeywords = c
Exit Function
End If
Next k
End If
Next c
End Function
Private Function LastUsedRow(ByVal ws As Worksheet) As Long
Dim f As Range
On Error Resume Next
Set f = ws.Cells.Find(What:="*", After:=ws.Range("A1"), LookAt:=xlPart, LookIn:=xlFormulas, SearchOrder:=xlByRows, SearchDirection:=xlPrevious)
On Error GoTo 0
If f Is Nothing Then
LastUsedRow = 1
Else
LastUsedRow = f.Row
End If
End Function
Private Function LastUsedColInRow(ByVal ws As Worksheet, ByVal rowNum As Long) As Long
LastUsedColInRow = ws.Cells(rowNum, ws.Columns.Count).End(xlToLeft).Column
If LastUsedColInRow < 1 Then LastUsedColInRow = 1
End Function
Private Sub SortStringDates(ByRef arr As Variant)
Dim i As Long, j As Long
Dim tmp As Variant
For i = LBound(arr) To UBound(arr) - 1
For j = i + 1 To UBound(arr)
If CDate(arr(j)) < CDate(arr(i)) Then
tmp = arr(i)
arr(i) = arr(j)
arr(j) = tmp
End If
Next j
Next i
End Sub