我有一个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);
}
但是一旦我发现了特殊的插槽,我就不能完全确定这是怎么做的。我无法找到有关这种情况的任何文件或建议。
有什么建议吗?
答案 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);
}
}
,您的结果应与此类似:
答案 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;夏令时插槽的字符。它处理多余的插槽并打印出适当数量的插槽。