How to not duplicate calendar appointments? - vba

I found VBA code that checks my schedule and creates an all-day 'busy' appointment if I exceed a certain amount of time in meetings on a given day.
The code works, but creates a duplicate event each time it runs.
I want to add a condition to see if an event already exists with the subject line "%hours of appt today" to prevent duplicate calendar events.
Sub BlockMoreCalendarAppts()
Dim myAcct As Outlook.Recipient
Dim myFB As String
Dim tDate As Date
Dim d As Long
Dim i As Long
Dim test As String
Dim oAppt As AppointmentItem
' it needs to know whose FB to check. Set this as your email address
Set myAcct = Session.CreateRecipient("myemailaddress")
' days to check (checks calendar for (7) days from todays date)
For d = 0 To 7
tDate = Date + d
' use start of working day or there about
' false counts tenetive and oof as busy
myFB = myAcct.FreeBusy(tDate + #7:30:00 AM#, 5, False)
' this gets the # of 5 min periods before the start time
i = (TimeValue(tDate + #7:30:00 AM#) * 288)
' only count free busy for 9 hours from start + at least 1 additional 5 min period
' (in my example, my day starts at 7:30 and ends at 4:30, so counting 9 hours)
' # of min in working day / 5
' skips busy times in the evening
test = Mid(myFB, i, 545 / 5)
CountOccurrences = UBound(Split(test, "1")) 'busy, oof or tentative
CountO = UBound(Split(test, "0")) ' free
'round to hours for subject
times = Round(((CountOccurrences * 5) / 60), 2)
' create all day busy event when I am scheduled
' to be in meetings for 5 or more hours per day
' there are 12 5 minute periods per hour
' 60 = 5 hours, 48 = 4 hours, 72 = 6 hours
If CountOccurrences >= 60 Then
' check to make sure an all day event wasn't already created
' to mark a day as busy
' default calendar
Set oAppt = Application.CreateItem(olAppointmentItem)
With oAppt
.Subject = times & " hours of appt today"
.Start = tDate
.ReminderSet = False
.Categories = "Full Day"
.AllDayEvent = True
.BusyStatus = olBusy
.Save
End With

I am using this code to search for appointments on a specific date and with a specific subject.
Public objCalendar As Outlook.folder
Public CalendarItems As Outlook.Items
Public oAppt As Outlook.AppointmentItem
Function Calendar_ApptExists(appt_Date As Date, appt_subject$) As Boolean
Set objCalendar = Session.GetDefaultFolder(olFolderCalendar)
Set CalendarItems = objCalendar.Items
' Items in calendar
Debug.Print "Items in Calendar: " & CalendarItems.Count
' Search calendar
For Each oAppt In CalendarItems
Debug.Print oAppt.Start, oAppt.Subject
' Check is appointment already exists
If (Int(oAppt.Start) = appt_Date) And oAppt.Subject = appt_subject Then
MsgBox "Already exists an appointment with '" & oAppt.Subject & "'", vbExclamation
Calendar_ApptExists = True
Exit Function
End If
Next
End Function

The code works, but creates a duplicate event each time it runs.
That is because in the code you create a new appointment item:
' default calendar
Set oAppt = Application.CreateItem(olAppointmentItem)
With oAppt
.Subject = times & " hours of appt today"
.Start = tDate
.ReminderSet = False
.Categories = "Full Day"
.AllDayEvent = True
.BusyStatus = olBusy
.Save
End With
Instead, you need to use Find/FindNext or Restrict methods of the Items class to find any appointment for the specified time frame. Read more about these methods in the following articles:
How To: Retrieve Outlook calendar items using Find and FindNext methods
How To: Use Restrict method in Outlook to get calendar items

Related

How can I filter out certain appointments in Outlook macros?

I have too many meetings at work, so I thought I might use an Outlook VBA macro to block my calendar when I have too many meetings, so I can use the rest of the time to actually get things done.
`Sub BlockMoreCalendarAppts()
Dim myAcct As Outlook.Recipient
Dim myFB As String
Dim tDate As Date
Dim d As Long
Dim i As Long
Dim test As String
Dim oAppt As AppointmentItem
' it needs to know whose FB to check
Set myAcct = Session.CreateRecipient("name.lastname#domain.com")
' days to check
For d = 0 To 5
tDate = Date + d
' use start of working day or there about
' false counts tenetive and oof as busy
myFB = myAcct.FreeBusy(tDate + #9:30:00 AM#, 5, False)
' this gets the # of 5 min periods before the start time
i = (TimeValue(tDate + #9:30:00 AM#) * 288)
' only count free busy for 7:10 hours from start + at least 1 additional 5 min period
' # of min in working day / 5
' skips busy times in the evening
test = Mid(myFB, i, 435 / 5)
CountOccurrences = UBound(Split(test, "1")) ' busy, oof or tentative
' theoretical WHERE statement goes here?
CountO = UBound(Split(test, "0")) ' free
'round to hours for subject
times = Round(((CountOccurrences * 5) / 60), 2)
' create all day busy event
' there are 12 5 minute periods per hour
' 60 = 5 hours
If CountOccurrences >= 60 Then
' default calendar
Set oAppt = Application.CreateItem(olAppointmentItem)
With oAppt
.Subject = times & " hours of appt today"
.Start = tDate
.ReminderSet = False
.Categories = "Full Day"
.AllDayEvent = True
.BusyStatus = olBusy
.Save
End With
End If
' check next day
Next d
End Sub`
The logic is that if I have more than 5 hours of meetings in a day, it sets an all day appointment and marks me as busy.
I tested the attached macro, which works, but, I want to filter out appointments which contain certain words in the subject. For example, "Lunch" or "Focus". In other words, I don't want "Lunch" to be counted in my five hours of meetings.
I have not figured out how to use a WHERE function to filter particular appointments for that day. Any help would be appreciated.
This is based (heavily) on the great work of Diane Poremsky at https://www.slipstick.com/outlook/calendar/limit-number-appointments-day/
If you switch to looping through appointments then you could add them all up like that? Pseudocode:
' Set the start and end times for the day
Dim startTime As Date
startTime = DateSerial(year, month, day) + TimeValue("9:00 AM")
Dim endTime As Date
endTime = DateSerial(year, month, day) + TimeValue("5:00 PM")
' Set the search criteria for the appointments
Dim filter As String
filter = "[Start] >= '" & startTime & "' AND [End] <= '" & endTime & "'" & _
"AND [Subject] NOT Like '*focus*'"
' Get the calendar folder for the default account
Dim calendarFolder As Folder
Set calendarFolder = Application.Session.GetDefaultFolder(olFolderCalendar)
' Set the current appointment to the first appointment of the day
Dim currentAppointment As AppointmentItem
Set currentAppointment = calendarFolder.Items.Find(filter)
' Loop through all appointments on the day
Do While Not (currentAppointment Is Nothing)
' Process the current appointment
' ...
' Get the next appointment
Set currentAppointment = calendarFolder.Items.FindNext
Loop

Add a DeferredDeliveryTime of two days to arrive at 9am

I am trying to get the time to change to 9am. It hasn't worked or has removed the preceding DateAdd("d", 2, now) which assigns it for 2 days time.
replyEmail.DeferredDeliveryTime = DateAdd("d", 2, Now)
I tried different DateTime functions, defining certain values, e.g. Date + Time("09:00:00")
After playing around for two days, it appears I've stumbled upon my answer.
replyEmail.DeferredDeliveryTime = DateAdd("d", 2, Date) + DateAdd("n", 0, #9:00:00 AM#)
My issue was finding the right object and then how to join the two so the function didn't override itself causing only the date or time to be correct, but not both.
I'm sure there is a way to make this look cleaner and if I discover it, I'll post but this does the job for the time being.
Option Explicit
Sub deferredDelivery_2D9H()
Dim mItem As MailItem
Dim d2_9AM As Date
Debug.Print "Date + 2 time format......: " & Format(Date + 2, "ddddd hh:nn")
d2_9AM = DateAdd("h", 9, Date + 2)
Debug.Print "d2_9AM....................: " & d2_9AM
Set mItem = CreateItem(olMailItem)
mItem.DeferredDeliveryTime = d2_9AM
Debug.Print "mItem.DeferredDeliveryTime: " & mItem.DeferredDeliveryTime
mItem.Display
' "Do not deliver before" entry in Options Tracking Properties dialog
'ActiveInspector.CommandBars.ExecuteMso ("DelayDeliveryOutlook")
' Options Tracking Properties dialog
ActiveInspector.CommandBars.ExecuteMso ("MessageOptions")
End Sub

How to round time to the nearest quarter hour in word

I need to round time to the nearest quarter hour in a word document. I am not very good at coding.
After a fair bit of searching I have found some vba code but it doesn't quite work. The code is:
Sub Time()
Dim num() As String
Dim tod() As String
Dim temp As String
num = Split(Time, ":")
tod = Split(num(2), " ")
If Val(num(1)) < 15 Then
temp = "00"
ElseIf Val(num(1)) < 30 Then
temp = "15"
ElseIf Val(num(1)) < 45 Then
temp = "30"
ElseIf Val(num(1)) < 60 Then
temp = "45"
End If
gettime = num(0) + ":" + temp + ":00 " + tod(1)
End Function
End Sub
When I try to run it I get a message:
"Compile Error: Expected function or variable"
and "Time" on the fifth line of the code is highlighted which I think is where the program stops running.
The rest of the code in the form is as follows:
This module doesn't affect the time rounding issue but I am including it so as not to leave anything out.
Option Explicit
Sub ClusterCheck()
Dim i As Integer, k As Integer, iCluster As Integer, bResult As Boolean
Dim sFieldNameNo As String, sName As String
On Error Resume Next ' If the first formfield is a checkbox, this will bypass the error that Word returns
sName = Selection.FormFields(1).Name ' Get the name of the formfield
bResult = ActiveDocument.FormFields(sName).CheckBox.Value ' Get the result of the current formfield
sFieldNameNo = Number(sName) ' Get generic number
sName = Left(sName, Len(sName) - Len(sFieldNameNo)) ' Get generic name
' Determine how many fields are within the cluster group
iCluster = 1
Do Until ActiveDocument.Bookmarks.Exists(sName & iCluster) = False
iCluster = iCluster + 1
Loop
iCluster = iCluster - 1
' If the check field is true, turn all of the other check fields to false
Application.ScreenUpdating = False
If bResult = True Then
For k = 1 To iCluster
If k <> sFieldNameNo Then ActiveDocument.FormFields(sName & k).Result = False
Next
End If
Application.ScreenUpdating = True
End Sub
This is the Number module:
Option Explicit
Function Number(ByVal sNumber As String) As String
' This module finds the form fields number within the field name
' Loops through the field name until it only has the number
Do Until IsNumeric(sNumber) = True Or sNumber = ""
sNumber = Right(sNumber, Len(sNumber) - 1)
Loop
Number = sNumber
End Function
This is the protection module:
Option Explicit
Sub Protect()
ActiveDocument.Protect Password:="wup13", NoReset:=True, Type:=wdAllowOnlyFormFields
End Sub
Sub Unprotect()
ActiveDocument.Unprotect Password:="wup13"
End Sub
This is the code that activates on opening and closing the document:
Option Explicit
Sub Document_Open()
' Zooms to page width, turns on Hidden Text, and turns off ShowAll and Table Gridlines
With ActiveWindow.View
.Zoom.PageFit = wdPageFitBestFit
.ShowHiddenText = True
.TableGridlines = False
.ShowAll = False
End With
Options.UpdateFieldsAtPrint = False
End Sub
Sub Document_Close()
' Turn on ShowAll and Table Gridlines
With ActiveWindow.View
.ShowAll = True
.TableGridlines = True
End With
Options.UpdateFieldsAtPrint = True
End Sub
That's all the code in the form. I am not great at VBA but am hoping I can solve this issue (with a little help).
DETAILS OF EXTRA DUTY FORM
Persons details
Family name:
Given name(s):
Level:
No.:
Location:
Cost Centre Code:
Time worked
Were any days of the extra duty performed on a designated public/show holiday? Yes 0 No 0
If yes enter holiday date/details:
Time commenced: [Text Form Field]
Date:
Time ceased: [Text Form Field]
Date:
Total Overtime claimed:
Are you a shift worker? Yes 0 No 0
Details of extra duty performed:
Vehicle details
Car: Yes 0 No 0
Motorcycle: Yes 0 No 0
Registration no.:
Fleet no.:
Stationary vehicle hours:
Yes 0 No 0 (only use for stationary duties)
Vehicle odometer start:
Odometer finish:
Total kms:
Client’s details
Company/Organisation name:
Phone no.:
Contact name:
Job no.:
Payment for special services
Was payment received in advance? Yes 0 No 0
If Yes– Amount:
Receipt no.:
Date:
If No– Amount:
Invoice no.:
Date:
I, , certify the above information to be true
(Signature) (Date)
Manager certification (Checked with roster and certified correct)
(Signature) (Date)
The code from vbforums gives me a subscript out of range error when used as recommended.
In the VBA IDE you can get explanations of what keywords do by placing the cursor on a keyword and pressing F1. This will bring up the MS help page for that particular keyword.
In the OP code the main procedure is 'Time'. This will cause problems for VBA because this is the same as the Time keyword so we would effectively be saying
time(time)
and VBA will stop with an error because the second use of time will be interpreted as the sub time and not the VBA time function so you will get the error message 'Argument not optional'.
The code below will provide what the OP has requested.
Option Explicit
Sub test_gettime()
Dim myTime As String
myTime = Now()
Debug.Print myTime
Debug.Print Format(myTime, "hh:mm:ss")
Debug.Print gettime(Format(myTime, "hh:mm:ss"))
' without the format statement we should also get the date
myTime = Now()
Debug.Print
Debug.Print myTime
Debug.Print gettime(myTime)
End Sub
Public Function gettime(this_time As String) As String
Dim myTimeArray() As String
Dim myQuarterHour As String
myTimeArray = Split(this_time, ":")
' Note that myTimeArray has not been converted to numbers
' Comparison of strings works by comparing the ascii values of each character
' in turn until the requested logic is satisfied
Select Case myTimeArray(1)
Case Is < "15"
myQuarterHour = "00"
Case Is < "30"
myQuarterHour = "15"
Case Is < "45"
myQuarterHour = "30"
Case Is < "60"
myQuarterHour = "45"
Case Else
Debug.Print "More than 60 minutes in the hour??"
End Select
gettime = myTimeArray(0) + ":" + myQuarterHour + ":00 "
End Function

Duration from Each Hour in 24 hours work

I have a a MS access table (MasterLogs)with structure as Date, UserName, Position, loginTime, logoutTime.(Login and logout field type is date&time)
I want to extract data from this table to another table (PositionHrs)where the columns are
Date
Username
Position
0000-0059
0100-0159
0200-0259
...
until 2300-2359 (which are basically like time duration).
In the Masterlogs table, the records are like 01-Aug-17, Username, position and there will be login logout times (eg:login 01-Aug-17 00:05 and logout time 01-Aug-17 02:45)
I am looking for a VBA code which can read the records in masterlogs and split the minutes of duration to corresponding column in the positionHrs table.
So that table should have a record for the username in 0000-0059 column with a value of 00:54 (54 minutes duration during 0000 to 0059 time and in the 0100-0159 column 01:00 and in the 0200-0259 column the remaining duration 00:45
Hope I have explained well.
Can anyone please help me to fix this ?
Thank you for responding braX. The below code add a record with duration for each person in the corresponding hour. if the person has continuous duration in the next hours it doesn't add those duration to corresponding hour columns.
Sub HourlyDuration()
Dim rs1 As Recordset
Dim rs2 As Recordset
Set rs1 = CurrentDb.OpenRecordset("MasterLogs")
Set rs2 = CurrentDb.OpenRecordset("PositionHrs")
Do While Not rs1.EOF
'below for adding minutes in rs2![0000-0059] "hour1"
If Format(rs1![On], "hh:nn") >= #12:00:00 AM# And Format(rs1![On], "hh:nn") <= #12:59:59 AM# Then
Dim St As String
St = Format(TimeValue("01:00:00") - TimeValue(RoundTime(rs1!On, 60)), "hh:nn")
'roundtime is a function to round time to the next minute (time field is dd-mmm-yy hh:nn:ss)
rs2.AddNew
rs2![Date] = rs1![Date]
rs2![0000-0059] = St
rs2!Position = rs1!Position
' rs2!Staff = rs1!Staff
rs2.Update
End If
'for rs2![0100-0159] "hour2"
If Format(rs1![On], "hh:nn") >= #1:00:00 AM# And Format(rs1![On], "hh:nn") <= #1:59:59 AM# Then
Dim st1 As String
st1 = Format(TimeValue("02:00:00") - TimeValue(RoundTime(rs1!On, 60)), "hh:nn")
rs2.AddNew
rs2![Date] = rs1![Date]
rs2![0100-0159] = st1
rs2!Position = rs1!Position
'rs2!Staff = rs1!Staff
rs2.Update
End If
' same conditions repeated for all hours, and then
rs1.MoveNext
Loop
MsgBox ("Finished")
rs1.Close
Set rs1 = Nothing
rs2.Close
Set rs2 = Nothing
End sub

How can I get the work week and day number in VB (outlook 2010)?

We send out a daily email using Outlook that contains the work week and day in the format of WW.D Our work week starts on Monday, so like today would be 50.2 How can I add this programatically using VB?
Sub MakeItem()
Dim objMail As MailItem
Set newItem = Application.CreateItemFromTemplate("C:\Users\Update.oft")
' Work week number
WW = 50.2
' Setup Subject replacing the <SHIFT>
newItem.Subject = Replace("<WorkWeek> Shift Passdown ", "<WorkWeek>", WW)
newItem.Display
Set newItem = Nothing
End Sub
You can do it like this:
Dim cal As New GregorianCalendar()
Dim week As Integer = cal.GetWeekOfYear(Date.Now, CalendarWeekRule.FirstFullWeek, DayOfWeek.Monday)
Dim day As Integer = cal.GetDayOfWeek(Date.Now)
Dim workDayId As String = String.Format("{0:00}.{1}", week, day)