0
votes

I want to search my Outlook calendar and shared calendars in my Network for appointments to check if there is a free time slot in the different calendars.

It was no problem to get all the Appointment-Items of the calendars which I prefiltered using Items.Restrict(string Filter) to only get Items from Today or newer. My searchpattern looks like that:

"[Start] >= '" + DateTime.Today.ToString("dd.MM.yyyy HH:mm") + "' AND [End] <= '" + DateTime.Parse("01.01.2019").ToString("dd.MM.yyyy HH:mm") + "'";

Everything works fine (Had problems with the Time format dd.MM.yyyy vs. dd/MM/yyyy but this was solved).

After I got my items, I search them to check if the appointment time is free or not, means: If the start or end time overlap with an appointment or if there is an appointment between the start and end time in any of the calendars then, if found, return that appointment, else return null.

This is my search pattern ({0} is temporary start time and {1} is temporary end time):

"(([Start] <= '{0}' AND '{0}' <= [End]) OR 
([Start] <= '{1}' AND '{1}' <= [End])) OR 
(('{0}' <= [Start] AND [Start] <= '{1}') OR 
('{0}' <= [End] AND [End] <= '{1}'))";

For my test I made an appointment Today from 08:00 to 08:30 and searched for the next appointment starting the search at 15:18. So the search should return null because it should not find a time overlap, but it returns the test appointment from the morning.

If I replace the values in the search string with my times and the times of the found appointment ({0} -> 15:18, {1} -> 16:18, [Start] -> 08:00, [End] -> 08:30) I got this:

((04.09.2018 08:00:00 <= '04.09.2018 15:25:09' AND '04.09.2018 15:25:09' <= 04.09.2018 08:30:00) OR (04.09.2018 08:00:00 <= '04.09.2018 16:25:09' AND '04.09.2018 16:25:09' <= 04.09.2018 08:30:00)) 
OR (('04.09.2018 15:25:09' <= 04.09.2018 08:00:00 AND 04.09.2018 08:00:00 <= '04.09.2018 16:25:09') OR ('04.09.2018 15:25:09' <= 04.09.2018 08:30:00 AND 04.09.2018 08:30:00 <= '04.09.2018 16:25:09'))

Checking the logic-statements :

//False OR False is False
(
    //False OR False is False
    (
        //True AND False is False
        (
            //Is True
            04.09.2018 08:00:00 <= '04.09.2018 15:25:09' 
            AND 
            //Is False
            '04.09.2018 15:25:09' <= 04.09.2018 08:30:00
        ) 
        OR 
        //True AND False is False
        (
            //Is True
            04.09.2018 08:00:00 <= '04.09.2018 16:25:09'
            AND 
            //Is False
            '04.09.2018 16:25:09' <= 04.09.2018 08:30:00
        )
    ) 
    OR
    //Fals OR Fals is False 
    (
        //False AND True is False
        (
            //Is Fale
            '04.09.2018 15:25:09' <= 04.09.2018 08:00:00 
            AND 
            //Is True
            04.09.2018 08:00:00 <= '04.09.2018 16:25:09'
        ) 
        OR 
        //False AND True is False
        (
            //Is False
            '04.09.2018 15:25:09' <= 04.09.2018 08:30:00 
            AND 
            //Is True
            04.09.2018 08:30:00 <= '04.09.2018 16:25:09'
        )
    )
)

I just don't get why this Search pattern returns True for that appointment which is not even near the checked time.

If I have no other appointments on that day it works (returning null, not an appointment from the next day).

I already checked the problem with the time-formatting (dd.MM.yyyy vs. dd/MM/yyyy etc.) so I guess that is not the cause of the behaviour (If I use '/' with seconds then the search pattern returns an appointment from Tomorrow, if I use '.' with seconds it returns the Today's appointment).

Is my search pattern wrong? Did I oversee something or is it the time-formatting which works different in the background?

Any ideas? Thanks.

EDIT

Could it be, that this filtering is buggy af? I copy-pasted an example made by Microsoft (msdn). No matter what I do, Outlook.Items.count (before and after Restrict()) is always 2147483647. But when I output the restriced Items the For-Each runs 5 times.

When I read the MAPI-Folder, I presort the items with "[Start] >= DateTime.Now.ToString("g")", sort them and look at the first Item which has Date >= Now, I Restrict this restricted Items with another Filter, when I look at the first Item in the restricted-restricted Items I got a Date which was 5 Years ago.

All Items -> Restricted by [Start] > Now -> Restricted by [Start] >= 08:00 -> Result: Anything from 2013... -.-

1

1 Answers

0
votes

So because the Items.Find() function seems to be buggy or at least no capable of processing more complex things than "1 AND 1" I had to write a "little workaround" (Which I don't like and it seems to be a bit slow if there are a lot of appointments) to solve this problem:

Here is my function to get the Appointment Items from my or a shared calendar (since the .Find() filter seems to work here I filter the items so that I won't get old appointments or appointments from the far future):

Dictionary<DateTime, DateTime> Plunder = new Dictionary<DateTime, DateTime>();
int AppointLength = 60;    

private void GetCalendar(object username, bool Shared)
{
    Outlook.MAPIFolder oCalendar;
    Outlook.Application oApp = new Outlook.Application();
    Outlook.NameSpace oNS = oApp.GetNamespace("mapi");
    oNS.Logon(Missing.Value, Missing.Value, true, true);

    if (Shared)
    {
        Outlook.Recipient oRecip = oNS.CreateRecipient((string)username);
        oCalendar = oNS.GetSharedDefaultFolder(oRecip, Outlook.OlDefaultFolders.olFolderCalendar);
    }
    else
    {
        oCalendar = oNS.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar);
    }

    Outlook.Items oItems = oCalendar.Items;
    oItems.IncludeRecurrences = true;
    oItems.Sort("[Start]");

    string strRestriction = "[Start] >= '" + DateTime.Today.ToString("g") + "' AND [End] <= '" + dtpEndDate.Value.ToString("g") + "'";

    Outlook.Items allNew = oItems.Restrict(strRestriction);

    allNew.Sort("[Start]");

    foreach (Outlook.AppointmentItem appt in allNew)
    {
        if (Plunder.ContainsKey(appt.Start))
        {
            if (Plunder[appt.Start] < appt.End)
            {
                Plunder[appt.Start] = appt.End;
            }
        }
        else
        {
            Plunder.Add(appt.Start, appt.End);
        }
    }
}

After I have all the Appointment-Items from all the calandars I want to search in the Plunder-Dictionary

private DateTime CheckForAppointment(DateTime now)
{
    DateTime then = now.AddMinutes(AppointLength);

    var AppointmentFound = Plunder.Select(i => i).Where(d => 
        ((d.Key < now && now < d.Value) 
        || (d.Key < then && then < d.Value)) 
        || ((now < d.Key && d.Key < then) 
        || (now < d.Value && d.Value < then)));

    if (AppointmentFound.Any())
    {
        now = CheckForAppointment(((KeyValuePair<DateTime, DateTime>)AppointmentFound.First()).Value);
    }

    return now;
}

To get back the next free timespan.

Since this solution seems to be a bit slow, does anyone have a better, faster idea?

Edit:

I could make it faster if I remember the position in the Plunder-Dictionary of the last touched appointment to continue searching from this position. This should work because Plunder is sorted by Date.