Could you please help me to obtain the difference between 2 dates (working hours only, that's very important)
Take a look at this image:
First response is calculated by the difference between: Date First Response and Date of the problem
Elapsed time is calculated by the difference between: Date Last Response and Date of the problem
This is my macro so far (it is not working properly):
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Const WORKING_DAY_START As String = "09:00"
Const WORKING_DAY_END As String = "18:00"
Const FORMULA_WORKING_TIME As String = _
"=(INT(E2-D2)*(""" & WORKING_DAY_END & """-""" & WORKING_DAY_START & """)" & _
"+MEDIAN(MOD(E2,1),""" & WORKING_DAY_END & """,""" & WORKING_DAY_START & """)" & _
"-MEDIAN(MOD(D2,1),""" & WORKING_DAY_END & """,""" & WORKING_DAY_START & """))"
Const FORMULA_ELAPSED_TIME As String = "=F2-D2"
Dim lastrow As Long
On Error GoTo ws_bdc_exit
Application.ScreenUpdating = False
Application.EnableEvents = False
With Me
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
'input Elapsed Time
.Range("H2").Resize(lastrow - 1).Formula = FORMULA_ELAPSED_TIME
'input First Response time
.Range("G2").Resize(lastrow - 1).Formula = FORMULA_WORKING_TIME
With .Range("G2:H2").Resize(lastrow - 1)
.Value = .Value
.NumberFormat = "##0.00"
End With
End With
ws_bdc_exit:
Target.Offset(1).Select
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
EDIT #1: I should obtain working hours from Monday to Friday (weekend not included, but i dont know how to do it)
EDIT #2: The difference should be displayed in hours
EDIT #3: Before, i was using this macro (everything was working fine BUT i was not getting the working hours)
Public cVal
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim LastRow
LastRow = Range("A" & Rows.Count).End(xlUp).Row
For i = 2 To LastRow
t1 = TimeValue(CStr(Cells(i, "D").Value))
t2 = TimeValue(CStr(Cells(i, "E").Value))
t3 = TimeValue(CStr(Cells(i, "F").Value))
'input First Response time
If Hour(t2) - Hour(t1) = 0 Then
Cells(i, "G").Value = Round((Minute(t2) - Minute(t1)) / 60, 2)
Else
Cells(i, "G").Value = Hour(t2) - Hour(t1) + Round((Minute(t2) - Minute(t1)) / 60, 2)
End If
'input Elapsed Time
If Hour(t3) - Hour(t1) = 0 Then
Cells(i, "H").Value = Round((Minute(t3) - Minute(t1)) / 60, 2) '- Cells(i, "J").Value - Cells(i, "J").Value
Else
Cells(i, "H").Value = Hour(t3) - Hour(t1) + Round((Minute(t3) - Minute(t1)) / 60, 2) '- Cells(i, "J").Value
End If
Next i
Target.Offset(1).Select
End Sub
I wrote a function which should calculate the working hours, Mon-Fri, only.
Note that in your posted example, some of the dates are on Sat/Sun, so will calculate as zero.
The algorithm:
Calculate workhours for each day that is Mon-Fri as being WORKING_DAY_START -WORKING_DAY_END` hours.
Make an adjustment if the day happens to be the first or last day being calculated.
You can use this function either on the worksheet itself, you can call if from a Macro that fills in the cells with just the value.
Below I will show your original data, plus some extra lines altering your weekend work dates.
Option Explicit
Function elapsedWorkTime(startDT As Date, endDt As Date) As Date
Const WORKING_DAY_START As Date = #9:00:00 AM#
Const WORKING_DAY_END As Date = #6:00:00 PM#
Dim adjTimeStart As Date, adjTimeEnd As Date, totTime As Date
Dim D As Date
For D = DateValue(startDT) To DateValue(endDt)
Select Case Weekday(D)
Case 2 To 6
'Adj for first and last days
If D = DateValue(startDT) Then
If TimeValue(startDT) <= WORKING_DAY_START Then
adjTimeStart = 0
ElseIf TimeValue(startDT) >= WORKING_DAY_END Then
adjTimeStart = WORKING_DAY_START - WORKING_DAY_END
Else
adjTimeStart = WORKING_DAY_START - TimeValue(startDT)
End If
End If
If D = DateValue(endDt) Then
If TimeValue(endDt) >= WORKING_DAY_END Then
adjTimeEnd = 0
ElseIf TimeValue(endDt) <= WORKING_DAY_START Then
adjTimeEnd = WORKING_DAY_START - WORKING_DAY_END
Else
adjTimeEnd = TimeValue(endDt) - WORKING_DAY_END
End If
End If
totTime = totTime + WORKING_DAY_END - WORKING_DAY_START
End Select
Next D
elapsedWorkTime = totTime + adjTimeStart + adjTimeEnd
End Function
EDIT Corrected formatting on screenshot
Note that the formula in the worksheet cell, since you want the output expressed as hours, is something like:
=elapsedWorkTime(C2;D2)*24
Note the discrepancy for 5541. In your example, you show a value of 8,52 for elapsed time. But in your requirements statement you write you want to include working hours only. Working hours end at 18:00 so time spent after that should not be counted.
Maybe there is no need to use VBA.
Use NETWORKDAYS function to count workdays between dates.
Multiply them by working hours a day
Substract work hours from begin and end date (e.g. work started later than workday begins and so on)
Calculate totals.
I'd recommend to do every step in a single cell, in order to check step-by-step logics.
Related
So I have a macro that I only want to run on weekdays. I created the macro that (I'm hoping) will check what day of the week it is and put that into a cell. This is what I have:
Private Sub dayCheck()
If Weekday(Now) = vbMonday Or vbTuesday Or vbWednesday Or vbThursday Or vbFriday Then
Dim BlankRow As Long
BlankRow = Range("A65536").End(xlUp).Row + 1
Cells(BlankRow, 1).Select
Selection.Value = Date
Selection.Offset(0, 1).Value = Time
Selection.Offset(0, 2).Value = WeekdayName(Weekday(Now))
Selection.Offset(0, 3).Value = Environ("Username")
ElseIf Weekday(Now) = vbSaturday Or vbSunday Then
Dim time1, time2
Do
time1 = Weekday(Now)
time2 = vbMonday
Do Until time1 = time2
DoEvents
time1 = Now()
Loop
Loop
End If
Application.OnTime TimeValue("12:00:00"), "dayCheck"
End Sub
My problem is I don't have administrator rights to change the system date. Is there a way I can simulate this through a macro?
You can simulate the date code by formatting the general or number output of the now() function in excel and just add 1 to increment the date. Numbers to the right of the decimal represent the percent of time beyond midnight until the next day.
Today's datetime code is: 43412.37786
Tomorrow is 43413.37786
So your question about testing your code can be answered by creating a for loop with:
Dim Today
Today = Now
For Days = Today To Today + 7 'Tests today through next Thursday.
If Weekday(Days) = ...
Next Days
But Darren's answer looks like it solves your problem, so I'd probably just go with that.
Now gives the date/time, Date gives just the date.
Weekday(Date) returns the current day number of the week with Sunday being 1.
As it's Thursday today Weekday(Now)=vbMonday will return False.
Weekday(Now) = vbMonday Or vbTuesday Or vbWednesday Or vbThursday Or vbFriday returns 7 - I'm not sure why, but it does. The main thing here is it doesn't return TRUE or FALSE.
For that statement to work you'd have to use
Weekday(Now) = vbMonday Or Weekday(Now) = vbTuesday Or Weekday(Now) = vbWednesday Or Weekday(Now) = vbThursday Or Weekday(Now) = vbFriday.
An easier way is Weekday(Date,vbMonday)<=5 - vbMonday numbers the week Monday = 1, Sunday = 7 and it answers the question "Is date a weekday?".
Here's the code:
Sub Test()
'DayCheck Now
'Or
'DayCheck #11/7/2018 6:55:00 PM#
'Or look at next 7 days starting now.
Dim x As Long
Dim StartDate As Date
StartDate = Now
For x = 0 To 6
DayCheck StartDate + x
Next x
End Sub
Sub DayCheck(MyDate As Date)
Dim rLastCell As Range
Dim wrkSht As Worksheet
Set wrkSht = ThisWorkbook.Worksheets("Sheet1")
If Weekday(MyDate, vbMonday) <= 5 Then
Set rLastCell = wrkSht.Cells(Rows.Count, 1).End(xlUp).Offset(1) 'Reference to the blank cell itself.
rLastCell.Resize(, 4) = Array(MyDate, MyDate, MyDate, Environ("Username"))
rLastCell.NumberFormat = "dd-mmm-yy"
rLastCell.Offset(, 1).NumberFormat = "hh:mm AM/PM"
rLastCell.Offset(, 2).NumberFormat = "dddd"
Else 'No need to check if it's a weekend - we know it's not a weekday.
'Just keep running until time1 = 2 and time2 = 2?
'I guess that'll be Midnight on Monday?
End If
End Sub
Note I'm putting the same value in columns A:C - just the date and time.
I then format each cell to show the part of the date & time you're interested in.
I have a worksheet with records (database). In Column B is the date the record was created (dd-MMM-yyyy format). In Column C I have the time it was created (HH:MM 24hr format).
The problem I'm having, is purging the records older than 8 hours from current system time. This code works at purging previous day records for the current finance period, but it is not taking into account 24hr format and after midnight for records older than 8 hours. I have tried many different approaches to this but still unable to figure this out.
This is the code I have since the last time I tried to figure this out:
'------------------------
' Current Finance Period
'------------------------
cSheet = CStr(Format(cStartDate, "dd-MMM-yyyy")) & " - " & CStr(Format(cEndDate, "dd-MMM-yyyy")) `Set the sheet name to use (current finance period)
CreateSheetIf (cSheet) `Create sheet if not exists
cFTarget = wbFinance.Worksheets(cSheet).UsedRange.Rows.Count `count the rows used
Set wscFinance = wbFinance.Worksheets(cSheet)
MRCForm.Caption = "MRC [ Processing... " & cSheet & " Ready to Finance records... Please wait... ]"
Me.sysMsgBox.Value = " Purging records, between " & cSheet & ", marked Ready for Finance..."
Application.ScreenUpdating = False
If cFTarget = 1 Then
If Application.WorksheetFunction.CountA(wscFinance.UsedRange) = 0 Then cFTarget = 0
End If
Source = wsMRC.UsedRange.Rows.Count
Set xRg = wsMRC.Range("AF2:AF" & Source)
Set dRg = wsMRC.Range("B2:B" & Source) `Date column in dd-MMM-yyyy format
Set tRg = wsMRC.Range("C2:C" & Source) `Time column in HH:MM 24hr format
On Error Resume Next
For K = 1 To xRg.Count
If dRg(K).Value = "" Or tRg(K).Value = "" Or xRg(K).Value = "" Then Exit For
If Format(dRg(K).Value, "dd-MMM-yyyy") >= Format(cStartDate, "dd-MMM-yyyy") And Format(dRg(K).Value, "dd-MMM-yyyy") < CStr(Format(Now, "dd-MMM-yyyy")) Then ' If date is within current finance period then
If CStr(xRg(K).Text) = "Y" Then
xRg(K).EntireRow.Copy Destination:=wscFinance.Range("A" & cFTarget + 1)
xRg(K).EntireRow.Delete
cFTotal = cFTotal + 1
MRCForm.Caption = "MRC [ Processing... " & cSheet & " (" & cFTotal & ") Please wait... ]"
If CStr(xRg(K).Value) = "Y" Then
K = K - 1
End If
cFTarget = cFTarget + 1
End If
End If
Next
Source = wsMRC.UsedRange.Rows.Count
Set xRg = wsMRC.Range("AF2:AF" & Source)
Set dRg = wsMRC.Range("B2:B" & Source) `Date column in dd-MMM-yyyy format
Set tRg = wsMRC.Range("C2:C" & Source) `Time column in HH:MM 24hr format
On Error Resume Next
For K = 1 To xRg.Count
If dRg(K).Value = "" Or tRg(K).Value = "" Or xRg(K).Value = "" Then Exit For
If Format(dRg(K).Value, "dd-MMM-yyyy") = CStr(Format(Now, "dd-MMM-yyyy")) And Format(tRg(K).Value, "HH:MM") <= Format(Now - TimeValue("08:00"), "HH:MM") Then ' If time is greater or equal to 8 hours ago then
If CStr(xRg(K).Text) = "Y" Then
xRg(K).EntireRow.Copy Destination:=wscFinance.Range("A" & cFTarget + 1)
xRg(K).EntireRow.Delete
cFTotal = cFTotal + 1
MRCForm.Caption = "MRC [ Processing... " & cSheet & " (" & cFTotal & ") Please wait... ]"
If CStr(xRg(K).Value) = "Y" Then
K = K - 1
End If
cFTarget = cFTarget + 1
End If
End If
Next
wscFinance.Columns("A:AM").AutoFit
Application.ScreenUpdating = True
Application.ScreenUpdating = True
I know the code is not very clean, just trying to get something that will function for now, will try to clean it up at a later date. Might even look at creating Functions as reusable code is more efficient.
Mock-up:
current time 11:45 on 2018.03.06
storing log date in column A
storing log time in column B
Untested code:
Dim i as long, lr as long, y as long, a as long, b as long
lr = cells(rows.count,1).end(xlup).row
For i = lr to 2 Step -1
y = TimeValue(now())-8
If y < 0 Then
a = Date(Now())-1
b = 24 + y 'y should be a negative value
Else
a = Date(Now())
b = y
End If
If Cells(1,1)=a AND Cells(1,2)>=b Then
.Rows(i).Delete
End If
Next i
Intention of this code:
loop through each row and delete the whole row if criteria met
find what 8 hours before Now() was and store as y... with the current time/date it is 03:45 on 2018.03.06, y = 3:45
If we save the current time is 02:00 on 2018.03.06 then y = -6:00
based on y being +/-, you determine the day and time
24 hour based time for where y is negative, so you add the negative number... in the case of y = -6, 24+(-6) = 18, so 18:00 hours, and the previous date (z)
you then assess the current row based on if the date matches AND if the time is less than or equal to z and y, respectively
This should be a starting point.
I have a column of date time. I have to remove the date part. I simple want to run a macro that will do that. WHen I record macro, do the delete and then stop, then run it on the next row, it gives the value below. How does one globalize so I can run on all rows this task?
2017-06-26 14:41:00
the macro is this:
Sub Macro9()
'
' Macro9 Macro
'
'
ActiveCell.FormulaR1C1 = "2:41:00 PM"
ActiveCell.Offset(1, 0).Range("A1").Select
End Sub
Here is a simple macro to accomplish what you are looking to do. I assumed that you wanted to convert from military time to AM/PM. You will have to adjust the locations of cells to fit your spreadsheet. This is just going through all of the values in column A and turning them into just AM/PM time and spitting them out in coulmn B. Instead of looping through all of the rows you could also define your own single input function with the same logic.
Sub test()
Dim dt As String
Dim tm As String
Dim hr As String
Dim row_ct As Integer
row_ct = Range("A1").End(xlDown).Row
For i = 1 To row_ct
dt = Cells(i, 1)
tm = Right(Cells(i, 1), 8)
hr = Left(tm, 2)
If hr < 12 Then
tm = tm & " AM"
ElseIf hr = 12 Then tm = tm & " PM"
ElseIf hr > 12 and hr - 12 < 10 then tm = 0 & (Left(tm, 2) - 12) & Right(tm, 6) & " PM"
Else: tm = left(tm, 2) - 12 & right(tm, 6) & " PM"
End If
Cells(i, 2) = tm
Next i
End Sub
Here is how you can make a custom function that handles this:
Function tm(date_time)
If Left(Right(date_time, 8), 2) < 12 Then
tm = Right(date_time, 8) & " AM"
ElseIf Left(Right(date_time, 8), 2) = 12 Then tm = Right(date_time, 8) & " PM"
ElseIf Left(Right(date_time, 8), 2) > 12 Then tm = Left(Right(date_time, 8), 2)- 12 & Right(date_time, 6) & " PM"`
End If
End Function
Depending on the application, one will probably work better than the other.
I am working on this schedule in Excel that will let me know if am scheduling only one person at a specific shift. So what the macro does it goes over a specific list that I have attached and checks their starting time and ending time. I want the macro to let me know if I schedule someone at a specific time that if there will be overlapping someone else. The following code is working, but now that I am testing to see if it works I put a name, app server they will be running, starting time and ending time. So the macro goes over a loop to check the table that I provide on excel, but I'm getting an error on the time. For example if I put the starting time at 6:00 am and the ending time at 6:30 am and on the table provided I have someone else running that app at rows tart time at 5:59 am to rowendtime= 6:35 am it should show me a "conflict" because they are overlapping but for some reason it is showing "no conflict" Any help would be really appreciated. Here is the code:
Public Sub LoopRows(Appserver As String, StartTime As Date, EndTime As Date)
Dim x As Integer
Dim NumROws As Integer
NumROws = Range("Sheet3!C5").End(xlDown).Row - Range("Sheet3!C5").Row
Worksheets("Sheet3").Activate
Range("Sheet3!C5").Select
msgbox Appserver
msgbox StartTime
msgbox EndTime
For x = 1 To NumROws
ActiveCell.Offset(1, 0).Select
If (ActiveCell.Offset(0, 1).Value = Appserver) Then
Dim RowStartTime As Date
Dim RowEndTime As Date
msgbox ActiveCell.Offset(0, 1).Value
msgbox RowStartTime = Val(ActiveCell.Offset(0, 3).Value)
msgbox RowEndTime = Val(ActiveCell.Offset(0, 4).Value)
If (((EndTime > RowStartTime) And (EndTime < RowEndTime)) Or (((StartTime > RowStartTime) And (StartTime < RowEndTime))) Or (((StartTime < RowStartTime) And (EndTime > RowEndTime)))) Then
msgbox "Conflict"
Else
msgbox "noConflict"
End If
End If
Next
End Sub
Simplify your If condition. It's impossible for two time spans to overlap without one starting time falling between the other span's start and end:
Partial overlap case:
Start1 End1
|----------------------------|
Start2 End2
|---------------------|
Complete overlap case:
Start1 End1
|----------------------------|
Start2 End2
|----------------|
You can make the If condition this:
If (StartTime > RowStartTime) And (StartTime < RowEndTime) Or _
(RowStartTime > StartTime) And (RowStartTime < EndTime) Then
Edit: It should look more like the code below. Note that I'm assuming from the original code that the "Appserver" value is in column "F", starting times are in column "H", and ending times are in column "I". There is also no error checking on the CDate calls (but then again there wasn't on the Val calls either).
Public Sub LoopRows(Appserver As String, StartTime As Date, EndTime As Date)
Dim CurrentRow As Integer, LastRow As Integer
Dim RowStartTime As Date, RowEndTime As Date
With Worksheets("Sheet3")
LastRow = .Range("C" & .Rows.Count).End(xlUp).Row
For CurrentRow = 6 To LastRow
If (.Cells(CurrentRow, 6).Value = Appserver) Then
RowStartTime = CDate(.Cells(CurrentRow, 8).Value)
RowEndTime = CDate(.Cells(CurrentRow, 9).Value)
If (StartTime > RowStartTime) And (StartTime < RowEndTime) Or _
(RowStartTime > StartTime) And (RowStartTime < EndTime) Then
MsgBox "Conflict"
Else
MsgBox "noConflict"
End If
End If
Next
End With
End Sub
I'm trying to calculate the time elapsed with the total amount of months, Days and Hours together using Datediff function. Is it not possible?
DateDiff("d hh", datein, Now)
What can I do?
That's not possible as the interval parameter can only be a single string.
You will have to do a bit more work like get the difference in hours and if it's above 24 convert the part before decimal separator into days
Sub Main()
Dim d1 As Date
d1 = "15/10/2014 08:00:03"
Dim d2 As Date
d2 = Now
Dim hrsDiff As Long
hrsDiff = DateDiff("h", d1, d2)
MsgBox IIf(hrsDiff >= 24, _
hrsDiff \ 24 & " days " & hrsDiff Mod 24 & " hours", _
hrsDiff & " hours")
End Sub
This is rough and ready, but is just directional. You could make a user defined function. This one returns 1:2:22:15 as a string (but you could return a custom class instance with variables for months, days, hours, minutes). It doesn't account for date2 being before date1 (not sure what happens then), nor does it account for date1 only being a partial day (assumes date1 is midnight).
Function MyDateDiff(date1 As Date, date2 As Date) As String
Dim intMonths As Integer
Dim datStartOfLastMonth As Date
Dim datStartOfLastHour As Date
Dim datEndOfMonth As Date
Dim intDays As Integer
Dim intHours As Integer
Dim intMinutes As Integer
Dim strResult As String
' Strip of any time
datStartOfLastMonth = DateSerial(Year(date2), Month(date2), Day(date2))
' check the dates arent in the same month
If Not ((Month(date1) = Month(date2) And Year(date1) = Year(date2))) Then
' how many months are there
intMonths = DateDiff("m", date1, date2)
Debug.Print (intMonths)
' how many days difference are there
intDays = DateDiff("d", DateAdd("m", intMonths, date1), date2)
Debug.Print (intDays)
' how many hours difference are there
intHours = DateDiff("h", datStartOfLastMonth, date2)
Debug.Print (intHours)
' how many minutes different are there
datStartOfLastHour = datStartOfLastMonth + (DatePart("h", date2) / 24)
intMinutes = DateDiff("n", datStartOfLastHour, date2)
Debug.Print (intMinutes)
Else
' Dates are in the same month
intMonths = 0
Debug.Print (intMonths)
' how many days difference are there
intDays = DateDiff("d", date1, date2)
Debug.Print (intDays)
' how many hours difference are there
intHours = DateDiff("h", datStartOfLastMonth, date2)
Debug.Print (intHours)
' how many minutes different are there
datStartOfLastHour = datStartOfLastMonth + (DatePart("h", date2) / 24)
intMinutes = DateDiff("n", datStartOfLastHour, date2)
Debug.Print (intMinutes)
End If
strResult = intMonths & ":" & intDays & ":" & intHours & ":" & intMinutes
MyDateDiff = strResult
End Function
Testing this:
?MyDateDiff("01-SEP-2014", "03-Oct-2014 22:15:33")
Gives:
1:2:22:15
i.e. 1 month, 2 days, 22 minutes and 15 seconds.
Reverse testing this by adding the components back onto date1 gives:
?DateAdd("n",15,DateAdd("h",22,DateAdd("d",2,DateAdd("m",1,"01-SEP-2014"))))
= "03-Oct-2014 22:15:33"
If we try with 2 dates in the same month:
?MyDateDiff("01-SEP-2014", "03-SEP-2014 22:15:33")
We get:
0:2:22:15
Reverse testing this:
?DateAdd("n",15,DateAdd("h",22,DateAdd("d",2,DateAdd("m",0,"01-SEP-2014"))))
Gives:
03/09/2014 22:15:00
But you may want to account for dates being the wrong way round...and you may only want date1 to be counted as a partial date if it starts later in the day....as I say, just a thought.
Regards
i
This may give you some ideas to correct for days in month or leap year Feb
Private Sub CommandButton1_Click()
DoDateA
End Sub
Sub DoDateA()
Dim D1 As Date, D2 As Date, DC As Date, DS As Date
Dim CA: CA = Array("", "yyyy", "m", "d", "h", "n", "s", "s")
Dim Va%(7), Da(7) As Date, Ci%
D1 = Now + Rnd() * 420 ' vary the * factors for range of dates
D2 = Now + Rnd() * 156
If D1 > D2 Then
[b4] = "Larger"
Else
[b4] = " smaller"
DS = D1
D1 = D2
D2 = DS
End If
[d4] = D1
[e4] = D2
DC = D2
For Ci = 1 To 6
Va(Ci) = DateDiff(CA(Ci), DC, D1)
DC = DateAdd(CA(Ci), Va(Ci), DC)
Va(Ci + 1) = DateDiff(CA(Ci + 1), DC, D1)
If Va(Ci + 1) < 0 Then ' added too much
Va(Ci) = Va(Ci) - 1
DC = DateAdd(CA(Ci), -1, DC)
Cells(9, Ci + 3) = Va(Ci + 1)
Cells(8, Ci + 3) = Format(DC, "yyyy:mm:dd hh:mm:ss")
End If
Da(Ci) = DC
Cells(5, Ci + 3) = CA(Ci)
Cells(6, Ci + 3) = Va(Ci)
Cells(7, Ci + 3) = Format(Da(Ci), "yyyy:mm:dd hh:mm:ss")
Cells(10, Ci + 3) = DateDiff(CA(Ci), D2, D1)
Next Ci
End Sub