如何在使用Outlook C#GetFreeBusy()API时处理夏令时?

时间:2014-10-25 19:03:26

标签: c# outlook timezone vsto dst

我有一个Outlook VSTO插件,我正在使用the GetFreeBusy() API Call l搜索资源日历可用性,在给定日期的情况下,将在接下来的28天内以30分钟的增量搜索(默认情况下)以确定哪些插槽是免费的,哪些是忙碌的。它工作正常,但我正在努力弄清楚如何应对在28天间隔内存在夏令时的情况。

这是我的代码:

   using Microsoft.Office.Interop.Outlook;

   string freeBusy = exchangeUser.GetFreeBusy(startDate, 30, true);

这给了我一个像这样的字符串,以30分钟的增量返回空闲/忙碌可用性28天。

  0000000000000000202222222222000000000000000000000000000000000222222

此字符串长度始终为1344个字符(每天48个插槽* 28天)

其中每个字符代表一个30分钟的插槽,如果时间是空闲的,则显示0。我有以下解析代码that I took from this Microsoft article,它返回一个空闲时隙数组:

    private IEnumerable<DateTime> ParseFreeBusy(string freeBusyString, DateTime startingDate)
    {
        var timeSlots = new List<DateTime>();
        for (int i = 0; i < freeBusyString.Length; i++)
        {
            double slot = i * 30;
            DateTime timeSlot = startingDate.Date.AddMinutes(slot);

            bool isFree = freeBusy.Substring(i, 1) == "0";

            if (isFree)
            {
                timeSlots.Add(timeSlot);
            }
        }
        return timeSlots;
    }

如果我在10月25日插入作为开始日期,当我查看结果时,每件事情都完美排列,直到11月2日凌晨2点(给予夏令时)

根本问题是我的天真代码只是增量并且每个条目都会增加30分钟,因为我只是循环遍历每个插槽并执行此操作:

 startingDate.Date.AddMinutes(slot);

我做了一个测试,并在11月2日凌晨1点到凌晨2点预订了一个日历插槽,这是我从那天开始从GetFreeBusy()获得的

   002222000...

所以使用上面的默认循环(记住,每个字符是30分钟的插槽,0 =免费),这将转换为以下插槽逻辑:

 12L00 AM - free (0)
 12:30 AM - free (0)
  1L00 AM - booked (2)
  1:30 AM - booked (2)
  THESE NEXT TWO "booked" below is really representing the 2nd 1AM - 2AM since we roll the clocks back an hour
  2:00 AM - booked (2)
  2:30 AM - booked (2)
  3:00 AM - free (0)

这是错误的,因为我的代码会显示2AM - 3AM在&#34; real&#34; 2-3A AM是免费的。如果我的解析是正确的并且处理了这个回滚,我最终会得到正确答案:

 12L00 AM - free (0)
 12:30 AM - free (0)
  1L00 AM - booked (2)
  1:30 AM - booked (2)
  IGNORE the second 1AM to 2AM as its already taken care of
  2:00 AM - free (0)
  2:30 AM - free (0)
  3:00 AM - free (0)

有趣的是,无论夏令时如何,所得到的字符串总是长达1344个字符(我预计在那些有夏令时影响的月份会更短或更长)。

有没有人有使用outlook GetFreeBusy()的经验,并了解如何在每日节省时间段时处理这种情况?

我一直在玩一些想法,如:

   var tzInfo = TimeZoneInfo.Local;
   if (tzInfo.IsAmbiguousTime(timeSlot))
   {
          //this would be a time to do something
   }   

或类似

    DaylightTime daylightTime = tz.GetDaylightChanges(minStartTime.Year);
    if (daylightTime.End == proposedTimeSlot)
    {
        daylightSavingsOffset = daylightSavingsOffset + ((daylightTime.Delta.Hours * 60) / meetingDuration);
    }

但是一旦我发现了特殊的插槽,我就不能完全确定这是怎么做的。我无法找到有关这种情况的任何文件或建议。

有什么建议吗?

2 个答案:

答案 0 :(得分:3)

  

有趣的是,无论夏令时如何,所得到的字符串总是长达1344个字符(我预计在那些有夏令时影响的月份会更短或更长)。

这完全合乎逻辑,让我们从GetFreeBusy开始,这是因为结果是基于持续时间和特定时间间隔而不是日期和时间戳,而且我们知道日期和时间是相对于我们基于时区的位置,但是经过的时间和持续时间不是,我们假设我们在10个小时内开会,我们可能在不同的时区,但在10个小时后(相对于我们的位置)我们都应该相互见面,但我们当地时间可能会有很大差异,系统以这种方式工作,因为它应该能够同时在不同时区运行,因此它使用UniversalTime的核心并将其转换回产生结果的当地时间。

现在让我们检查代码,当我们使用startingDate.Date.AddMinutes(slot);时,我们不考虑DateTimeSaving,因为我们在当地时间运行,并且添加是相对于它的,使用UniversalTime我们可以为我们的时间添加和间隔创建一个统一的基点,之后通过将其转换回本地时间,我们可以将日期时间节省到它,

所以我相信这段代码应该按预期工作:

    private static IEnumerable<DateTime> ParseFreeBusy(string freeBusyString, DateTime startingDate)
    {
        var timeSlots = new HashSet<DateTime>();
        var utc = startingDate.ToUniversalTime();
        var timeZone = TimeZone.CurrentTimeZone; //can change to particular time zone, currently set to local timezone of the system

        for (int i = 0; i < freeBusyString.Length; i++)
        {
            double slot = i * 30;
            DateTime timeSlot = utc.AddMinutes(slot);

            bool isFree = freeBusyString.Substring(i, 1) == "0";

            if (isFree)
            {
                var localTimeSlot = timeZone.ToLocalTime(timeSlot);
                timeSlots.Add(localTimeSlot);
            }
        }
        return timeSlots;
    }

注意:: 除了使用UTC时,我将List更改为HashSet,因为如果您在这些特定时间有空闲广告位,则会获得重复的条目,使用HashSet这个问题不会发生。

这是我用来测试它的方法:

    private static void TestFreeSlots()
    {
        var saving = TimeZone.CurrentTimeZone.GetDaylightChanges(DateTime.Now.Year);
        var datetime = new DateTime(saving.End.Year, saving.End.Month, saving.End.Day - 1);

        //you may need to change the string to see effective result
        var result = ParseFreeBusy("0000000000000000000000000000000000000000000000002222000", datetime);
    }

最后这里有一个小样本来演示这里使用的方法

    private static void TestTimeZone()
    {
        var saving = TimeZone.CurrentTimeZone.GetDaylightChanges(DateTime.Now.Year);

        var datetime = new DateTime(saving.End.Year, saving.End.Month, saving.End.Day - 1);
        var utc = datetime.ToUniversalTime();
        var timeZone = TimeZone.CurrentTimeZone;

        for (var i = 0; i < 120; i++)
        {
            var next = timeZone.ToLocalTime(utc);
            Console.WriteLine(next);
            utc = utc.AddMinutes(30);
        }
    }

,您的结果应与此类似:

time zone test

答案 1 :(得分:0)

此第一个函数查找由于DST而Outlook将作为重复项返回的时间段。它可能会有一些重构,但它现在有效:(编辑:我修改了这个函数,因此当你进入DST时它不会删除时隙)。

public static Collection<DateTime> GetDuplicateSlots(
        TimeZoneInfo timeZone, DateTime start, int intervalLength, int numOfIntervals)
    {
        Collection<DateTime> duplicates = new Collection<DateTime>();
        bool dstAtStart = timeZone.IsDaylightSavingTime(start);
        for (int interval = 0; interval < numOfIntervals; interval++)
        {
            DateTime current = start.Date.AddMinutes(interval * intervalLength);
            if (dstAtStart && !timeZone.IsDaylightSavingTime(current))
            {
                duplicates.Add(current);
                duplicates.Add(current.AddMinutes(intervalLength));
                return duplicates;
            }
        }

        return duplicates;  // no duplicates
    }

然后我们只需要在通过string空闲/忙碌时段时调整重复项:

    public static void DisplayFreeBusy(
        string freeBusyString, DateTime start, int intervalLength)
    {
        TimeZoneInfo cst = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
        Collection<DateTime> duplicateSlots = 
            GetDuplicateSlots(cst, start, intervalLength, freeBusyString.Length);
        int duplicatesConsumed = 0;
        for (int slot = 0; slot < freeBusyString.Length; slot++)
        {
            int actualSlot = slot - duplicatesConsumed;
            DateTime slotTime = start.Date.AddMinutes(actualSlot * intervalLength);

            if (duplicatesConsumed != duplicateSlots.Count && 
                duplicateSlots.Contains(slotTime))
            {
                duplicatesConsumed++;
            }
            else
            {
                Console.WriteLine("{0} -- {1}", slotTime, freeBusyString[slot]);
            }
        }
    }

请注意,actualSlot变量对应于时隙,而slot变量仍然对应于忙/闲字符串中的字符。当发现重复时,它被消费了#34;并跳过字符串中的该字符。一旦消耗了重复项,该功能将从该点正常继续。

我住在亚利桑那州,我们没有夏令时,所以我不得不强迫不同的时区。显然,您可以替换当地时区而不是CST。

我用较短的输入字符串对此进行了测试,但我添加了额外的&#39; 2&#39;夏令时插槽的字符。它处理多余的插槽并打印出适当数量的插槽。