vbaoutlookcalendar

Auto calender appointment not booking correctly. Does respect start but does not look at End time


If you have an appointment on let's say 30-11 at 11h00 to 12h00 it will only look at 11h00 and adds the 20 default time and inserts a appointment at 11h20 to 11h40 instead of 12h00 to 12h20.

Debug print info

dtTimeToCheck: 15:00:00
[Start] >= '2024-12-06 12:00 ' AND [End] <= '2024-12-07 12:00 '

Start of function loop.
 argCheckDate....: 06-12-24 15:00:00

 argCheckDate.....: 06-12-24 15:00:00
 CheckAvailability: False
End of function loop.
Appointment time :06-12-24 at: 15:00:00
False
2024/12/06
 dtTimeToCheck: 15:00:00
[Start] >= '2024-12-06 12:00 ' AND [End] <= '2024-12-07 12:00 '

Start of function loop.
 argCheckDate....: 06-12-24 15:00:00

 argCheckDate.....: 06-12-24 15:00:00
 CheckAvailability: False
End of function loop.
Appointment time :06-12-24 at: 15:00:00
False

Have tried changing duration as long and even duration = DateAdd("n", duration, argChkTime) As well as suggestions by Nitton but wasn't unfortunately able to find a solution.

Option Explicit

' If already booked for that time (11h00 + 20 minutes) return true, and check 11h20 + 20...
Function testReverseDate()
    Dim sDate As Date
    Dim sTime As Date
    Dim sEmail As String
    Dim sName As String
    Dim sLocation As String
    Dim sRemark As String
    sDate = "06-12-2024"
    sDate = Format(sDate, "yyyy,mm,dd")
    sTime = "15:00"
    sName = "Just A Name"
    sEmail = "anon@email.com"
    sLocation = "My Location"
    sRemark = "My Remark"
    Debug.Print (sDate)
    Call BlockNextFreeSlot(sDate, sName, sEmail, sLocation, sTime, sRemark)
End Function

Sub BlockNextFreeSlot(dtDateToCheck As Date, sName, _
  sEmail, strLocation, sTime, sRemark)
  ' Set the minimum duration for a time slot to 30 minutes.
  Dim min_Duration_for_slot
  min_Duration_for_slot = 20 / (24 * 60)
  ' Get the end time for the work day from the UserForm.
  Dim WorkendTime As Date
  WorkendTime = "16:00"

  ' Get the duration of the appointment from the UserForm.
  Dim TDuration As Date

  TDuration = 20 / (24 * 60) ' Default duration is 20 minutes.
  ' If the appointment duration is less than the minimum slot duration, set it as the new minimum.
  If TDuration < min_Duration_for_slot Then min_Duration_for_slot = TDuration
  ' Get the start time of the appointment from the UserForm.
  Dim dtTimeToCheck As Date
  dtTimeToCheck = Format(sTime, "hh:mm")
  ' Check if the time slot is already taken, and if so, find the next available time slot.
  Dim SlotIsTaken As Boolean
  SlotIsTaken = True
  Do Until Not SlotIsTaken Or dtTimeToCheck > WorkendTime
    SlotIsTaken = CheckAvailability(dtDateToCheck, dtTimeToCheck + TDuration, TDuration)
    Debug.Print (SlotIsTaken)
    Debug.Print (dtDateToCheck & " " & dtTimeToCheck & " " & dtTimeToCheck + TDuration)
    If SlotIsTaken Then
        ' Set the start time to the next available time slot.
        dtTimeToCheck = DateAdd("n", min_Duration_for_slot, dtTimeToCheck)
    End If
  Loop
    If SlotIsTaken Then
            'No slots open, search next day
            dtTimeToCheck = DateAdd("n", min_Duration_for_slot, dtTimeToCheck)
            Debug.Print ("Time to Check: " & dtTimeToCheck)
            Debug.Print ("---------")
            Debug.Print ("Date to Check: " & dtDateToCheck)
            Debug.Print ("---------")
            NextDaySlot = True
        Call BlockNextFreeSlot(dtDateToCheck + 1, sName, sEmail, strLocation, sTime, sRemark)
    Else
        If dtTimeToCheck = sTime And NextDaySlot = False Then
            If CreateAppointment(dtDateToCheck, dtTimeToCheck, sEmail, sName, strLocation, sRemark) Then
                'Create appointement as requested on same day, at requested time
                Debug.Print "Appointment time :" & dtDateToCheck & " at: " & dtTimeToCheck
                
            End If
        Else
            'Create appointement but was resceduled slots taken, send email with rescedule
            If CreateAppointment(dtDateToCheck, dtTimeToCheck, sEmail, sName, strLocation, sRemark) Then
                Debug.Print "Appointment time :" & dtDateToCheck & " at: " & dtTimeToCheck
            End If
            Debug.Print dtTimeToCheck
        End If
    End If
    Debug.Print ("if next day, send notification")
    Debug.Print (NextDaySlot)

End Sub

Public Function CheckAvailability(ByVal argChkDate As Date, _
    ByVal argChkTime As Date, ByVal duration As Date) As Boolean

    ' duration As Date ?
    ' Since Outlook constants used, code is in Outlook
    ' olFolderCalendar, olAppointment and olMeetingRequest

    Dim oApptItem As AppointmentItem
    Dim oFolder As Folder
    Dim oMeetingoApptItem As MeetingItem
    Dim oObject As Object
    Dim ItemstoCheck As Items
    Dim strRestriction As String
    Dim FilteredItemstoCheck As Items
    Dim argCheckDate As Date
    Dim daStart As String
    Dim daEnd As String
   
    'Combine the date and time arguments
    argCheckDate = argChkDate + argChkTime
    duration = DateAdd("n", duration, argChkTime)
    'Avoid past booking of calendar
    If argCheckDate < Now Then
        CheckAvailability = True
        GoTo FUNCEXIT
    End If
    
    'Get the default calendar folder
    Set oFolder = Session.GetDefaultFolder(olFolderCalendar)
   'Get all items in the calendar folder
    Set ItemstoCheck = oFolder.Items
   'Include recurring appointments
    ItemstoCheck.IncludeRecurrences = True
   'Sort the items by start date
    ItemstoCheck.Sort "[Start]"
    'Filter the items by the given date range
    ' argCheckDate and hh:mm
    daStart = Format(argCheckDate, "dd/mm/yyyy hh:mm AMPM")
    daEnd = Format(argCheckDate + 1, "dd/mm/yyyy hh:mm AMPM")

    ' If US date format fails when day is less than 13
    '  DDDDD HH:NN
    '  yyyy-mm-dd hh:mm AM/PM
    strRestriction = "[Start] >= '" & daStart & "' AND [End] <= '" & daEnd & "'"
    Debug.Print (strRestriction)
    Set FilteredItemstoCheck = ItemstoCheck.Restrict(strRestriction)
    'Check if there is a conflicting appointment
    CheckAvailability = False

    For Each oObject In FilteredItemstoCheck
            If (oObject.Start = argCheckDate) _
              Or oObject.End = (argCheckDate + duration) _
              Or (argCheckDate > oObject.Start And argCheckDate < oObject.End) _
              Or DateAdd("n", duration, argCheckDate) > oObject.Start And DateAdd("n", duration, argCheckDate) < oObject.End _
              Or oObject.Start > argCheckDate And oObject.Start < DateAdd("n", duration, argCheckDate) Then

                CheckAvailability = True
                Exit For
            End If

    Next oObject
Debug.Print
Debug.Print " argCheckDate.....: " & argCheckDate
Debug.Print " CheckAvailability: " & CheckAvailability
Debug.Print "End of function loop."

FUNCEXIT:
    'Cleanup
    Set oMeetingoApptItem = Nothing
    Set oFolder = Nothing
    Set oApptItem = Nothing
    Set oObject = Nothing
End Function

Solution

  • I returned argChkDate to the filter.

    Duration variables declared as Long not Date.

    I broke the If statement into multiple parts to see what it did. You could apply DateAdd to the original single If.

    Option Explicit
    
    ' If already booked for that time (11h00 + 20 minutes) return true, and check 11h20 + 20...
    Sub testFreeslotCalender()
        'Call BlockNextFreeSlot("30-11-2024", "Names", "anon@email.com", "Location", "11:00", "My Remark")
    
        ' Date format in a string is unreliable
        '  Test with d less than 13 and m <> d
        Call BlockNextFreeSlot("2024-12-06", "Names", "anon@email.com", "Location", "11:00", "My Remark")
    End Sub
    
    Sub BlockNextFreeSlot(dtDateToCheck As Date, sName, sEmail, strLocation, sTime, sRemark)
    
        ' Set the minimum duration for a time slot to 30 minutes.    
        Dim min_Duration_for_slot As Long
        min_Duration_for_slot = 30
        
        ' Get the end time for the work day from the UserForm.
        Dim WorkendTime As Date
        WorkendTime = "16:00"
        
        Dim TDuration As Long
        TDuration = 20
        
        ' If the appointment duration is less than the minimum slot duration, set it as the new minimum.
        If TDuration < min_Duration_for_slot Then min_Duration_for_slot = TDuration
        
        ' Get the start time of the appointment from the UserForm.
        Dim dtTimeToCheck As Date
        dtTimeToCheck = Format(sTime, "hh:mm")
        Debug.Print " dtTimeToCheck: " & dtTimeToCheck
        
        ' Check if the time slot is already taken, and if so, find the next available time slot.
        Dim SlotIsTaken As Boolean
        SlotIsTaken = True
        
        Do Until Not SlotIsTaken Or dtTimeToCheck > WorkendTime
        
            Debug.Print " dtTimeToCheck: " & dtTimeToCheck
            
            SlotIsTaken = CheckAvailability(dtDateToCheck, dtTimeToCheck, TDuration)
            Debug.Print " SlotIsTaken: " & SlotIsTaken
            
            If SlotIsTaken Then
                ' Set the start time to the next available time slot.
                dtTimeToCheck = DateAdd("n", min_Duration_for_slot, dtTimeToCheck)
                Debug.Print " dtTimeToCheck: " & dtTimeToCheck
            End If
            
        Loop
      
        If SlotIsTaken Then
            Debug.Print ("....Busy")
        Else
            Debug.Print ("Creating an appointement")
            Debug.Print dtTimeToCheck
        End If
    
    End Sub
    
    
    Public Function CheckAvailability(ByVal argChkDate As Date, _
        ByVal argChkTime As Date, ByVal duration As Long) As Boolean
    
        Dim oFolder As folder
        Dim oObject As Object
        Dim ItemstoCheck As Items
        
        Dim strRestriction As String
        Dim FilteredItemstoCheck As Items
        
        Dim argCheckDate As Date
        
        Dim daStart As String
        Dim daEnd As String
        
        'Combine the date and time arguments
        argCheckDate = argChkDate + argChkTime
        
        'Avoid past booking of calendar
        If argCheckDate < Now Then
            CheckAvailability = True
            GoTo FUNCEXIT
        End If
        
        'Get the default calendar folder
        Set oFolder = Session.GetDefaultFolder(olFolderCalendar)
        
       'Get all items in the calendar folder
        Set ItemstoCheck = oFolder.Items
        
       'Include recurring appointments
        ItemstoCheck.IncludeRecurrences = True
        
       'Sort the items by start date
        ItemstoCheck.Sort "[Start]"
        
        'Filter the items for given day
        ' Note: returned to argChkDate
        daStart = Format(argChkDate, "yyyy/mm/dd hh:mm AMPM")
        daEnd = Format(argChkDate + 1, "yyyy/mm/dd hh:mm AMPM")
    
        strRestriction = "[Start] >= '" & daStart & "' AND [End] <= '" & daEnd & "'"
        Debug.Print strRestriction
        
        Set FilteredItemstoCheck = ItemstoCheck.Restrict(strRestriction)
        
        'Check if there is a conflicting appointment
        CheckAvailability = False
        
        Debug.Print
        Debug.Print "Start of function loop."
        Debug.Print " argCheckDate....: " & argCheckDate
        
        For Each oObject In FilteredItemstoCheck
            
            If oObject.Start = argCheckDate Then
                Debug.Print " oObject.Start...: " & oObject.Start
                Debug.Print " argCheckDate....: " & argCheckDate
                CheckAvailability = True
                Debug.Print "Condition 1"
                Exit For
            End If
            
            If oObject.End = (argCheckDate + duration) Then
                CheckAvailability = True
                Debug.Print "Condition 2"
                Exit For
            End If
            
            If argCheckDate > oObject.Start Then
                Debug.Print " oObject.Start...: " & oObject.Start
                Debug.Print " argCheckDate....: " & argCheckDate
                    
                If argCheckDate < oObject.End Then
                    Debug.Print " oObject.End.....: " & oObject.End
                    Debug.Print " argCheckDate....: " & argCheckDate
                    CheckAvailability = True
                    Debug.Print "Condition 3"
                    Exit For
                End If
            End If
            
            If DateAdd("n", duration, argCheckDate) > oObject.Start Then
                Debug.Print " (argCheckDate + duration): " & (argCheckDate + duration)
                Debug.Print " oObject.Start...: " & oObject.Start
                
                If DateAdd("n", duration, argCheckDate) < oObject.End Then
                    CheckAvailability = True
                    Debug.Print "Condition 4"
                    Exit For
                End If
            End If
            
            If oObject.Start > argCheckDate Then
                If oObject.Start < DateAdd("n", duration, argCheckDate) Then
                    CheckAvailability = True
                    Debug.Print "Condition 5"
                    Exit For
                End If
            End If
            
        Next oObject
    
    Debug.Print
    Debug.Print " argCheckDate.....: " & argCheckDate
    Debug.Print " CheckAvailability: " & CheckAvailability
    Debug.Print "End of function loop."
    
    FUNCEXIT:
        'Cleanup
        Set oFolder = Nothing
        Set oObject = Nothing
        
    End Function
    

    Apparently the manipulated string is not recognized as a date.

    ' If already booked for that time (11h00 + 20 minutes) return true, and check 11h20 + 20...
    Sub testFreeslotCalender_CDate()
        'Call BlockNextFreeSlot("30-11-2024", "Names", "anon@email.com", "Location", "11:00", "My Remark")
        ' Date format in a string is unreliable
        '  Test with d less than 13 and m <> d
        Dim sDate As String
        sDate = "06-12-2024"
        sDate = Split(sDate, "-")(2) & "/" & Split(sDate, "-")(1) & "/" & Split(sDate, "-")(0)
        Call BlockNextFreeSlot(CDate(sDate), "Names", "anon@email.com", "Location", "11:00", "My Remark")
    End Sub