【问题标题】:How to handle daylight saving when using the Outlook C# GetFreeBusy() API?使用 Outlook C# GetFreeBusy() API 时如何处理夏令时?
【发布时间】:2014-12-21 08:47:30
【问题描述】:

我有一个 Outlook VSTO 插件,我正在使用 the GetFreeBusy() API Calll 搜索资源日历可用性,给定一个日期,它将在接下来的 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)

这是错误的,因为当“真正的”2-3A AM 空闲时,我的代码会显示 2AM - 3AM 已预订。如果我的解析是正确的并处理了这个回滚,我最终会得到这个正确的答案:

 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);
    }

但我不完全确定一旦我检测到“特殊插槽”后该怎么办,而且我找不到任何关于这种情况的文档或建议。

有什么建议吗?

【问题讨论】:

    标签: c# outlook timezone vsto dst


    【解决方案1】:

    第一个函数查找由于 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 变量仍然对应于忙/闲字符串中的一个字符。当找到重复项时,它被“消耗”并且字符串中的那个字符被跳过。一旦副本被消耗,该函数将从该点正常继续。

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

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

    【讨论】:

      【解决方案2】:

      有趣的是,无论夏令时如何,生成的字符串始终为 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);
              }
          }
      

      你的结果应该是这样的:

      【讨论】:

        猜你喜欢
        • 2013-03-18
        • 2019-12-16
        • 2015-02-04
        • 2022-07-20
        • 2015-04-09
        • 2018-04-18
        • 2014-09-30
        • 2021-03-09
        • 2019-09-29
        相关资源
        最近更新 更多