Skip to content
This repository has been archived by the owner on Jul 22, 2024. It is now read-only.

Fixed several issues with the creation of the TimeZoneDefinition object #208

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion ComplexProperties/TimeZones/AbsoluteDateTransition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,14 @@ internal override void WriteElementsToXml(EwsServiceXmlWriter writer)
{
base.WriteElementsToXml(writer);

// Write the DateTime element as a datetime value formatted with no time zone conversions.
// We must not pass the dateTime value to WriteElementValue as a DateTime value, because
// WriteElementValue would convert the DateTime value to UTC using the time zone
// on the ExchangeService object. No time zone conversions should be done on transition objects.
writer.WriteElementValue(
XmlNamespace.Types,
XmlElementNames.DateTime,
this.dateTime);
dateTime.ToString("yyyy-MM-ddTHH:mm:ss", CultureInfo.InvariantCulture));
}

/// <summary>
Expand Down
130 changes: 104 additions & 26 deletions ComplexProperties/TimeZones/TimeZoneDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,39 +93,25 @@ internal TimeZoneDefinition(TimeZoneInfo timeZoneInfo)
this.Id = timeZoneInfo.Id;
this.Name = timeZoneInfo.DisplayName;

// TimeZoneInfo only supports one standard period, which bias is the time zone's base
// TimeZoneInfo only supports one standard period, whose bias is the time zone's base
// offset to UTC.
TimeZonePeriod standardPeriod = new TimeZonePeriod();
standardPeriod.Id = TimeZonePeriod.StandardPeriodId;
standardPeriod.Name = TimeZonePeriod.StandardPeriodName;
standardPeriod.Bias = -timeZoneInfo.BaseUtcOffset;

TimeZoneInfo.AdjustmentRule[] adjustmentRules = timeZoneInfo.GetAdjustmentRules();
TimeZonePeriod standardPeriod = this.CreateStandardPeriod(timeZoneInfo);

TimeZoneTransition transitionToStandardPeriod = new TimeZoneTransition(this, standardPeriod);
TimeZoneInfo.AdjustmentRule[] adjustmentRules = timeZoneInfo.GetAdjustmentRules();

if (adjustmentRules.Length == 0)
{
this.periods.Add(standardPeriod.Id, standardPeriod);

// If the time zone info doesn't support Daylight Saving Time, we just need to
// create one transition to one group with one transition to the standard period.
TimeZoneTransitionGroup transitionGroup = new TimeZoneTransitionGroup(this, "0");
transitionGroup.Transitions.Add(transitionToStandardPeriod);

this.transitionGroups.Add(transitionGroup.Id, transitionGroup);

TimeZoneTransition initialTransition = new TimeZoneTransition(this, transitionGroup);

this.transitions.Add(initialTransition);
this.transitions.Add(new TimeZoneTransition(this,
CreateTransitionGroupToPeriod(standardPeriod)));
}
else
{
for (int i = 0; i < adjustmentRules.Length; i++)
{
TimeZoneTransitionGroup transitionGroup = new TimeZoneTransitionGroup(this, this.transitionGroups.Count.ToString());
transitionGroup.InitializeFromAdjustmentRule(adjustmentRules[i], standardPeriod);

transitionGroup.InitializeFromAdjustmentRule(timeZoneInfo, adjustmentRules[i]);
this.transitionGroups.Add(transitionGroup.Id, transitionGroup);

TimeZoneTransition transition;
Expand All @@ -137,17 +123,15 @@ internal TimeZoneDefinition(TimeZoneInfo timeZoneInfo)
// period and a group containing the transitions mapping to the adjustment rule.
if (adjustmentRules[i].DateStart > DateTime.MinValue.Date)
{
TimeZoneTransition transitionToDummyGroup = new TimeZoneTransition(
this,
this.CreateTransitionGroupToPeriod(standardPeriod));

this.transitions.Add(transitionToDummyGroup);
// Add the dummy transition for the standard period.
this.transitions.Add(new TimeZoneTransition(this,
this.CreateTransitionGroupToPeriod(standardPeriod)));

// Add the transition corresponding with the adjustment rule's start date.
AbsoluteDateTransition absoluteDateTransition = new AbsoluteDateTransition(this, transitionGroup);
absoluteDateTransition.DateTime = adjustmentRules[i].DateStart;

transition = absoluteDateTransition;
this.periods.Add(standardPeriod.Id, standardPeriod);
}
else
{
Expand All @@ -156,6 +140,17 @@ internal TimeZoneDefinition(TimeZoneInfo timeZoneInfo)
}
else
{
if ((adjustmentRules[i - 1].DateEnd.Year + 1) != adjustmentRules[i].DateStart.Year)
{
// If the next adjustment rule does not start the year after the previous adjustment rule ends,
// we need to add a dummy transition to cover the years in between.
AbsoluteDateTransition transitionToDummyGroup = new AbsoluteDateTransition(this,
CreateTransitionGroupToPeriod(standardPeriod));
transitionToDummyGroup.DateTime = adjustmentRules[i - 1].DateEnd.AddDays(1);

this.transitions.Add(transitionToDummyGroup);
}

AbsoluteDateTransition absoluteDateTransition = new AbsoluteDateTransition(this, transitionGroup);
absoluteDateTransition.DateTime = adjustmentRules[i].DateStart;

Expand Down Expand Up @@ -183,6 +178,89 @@ internal TimeZoneDefinition(TimeZoneInfo timeZoneInfo)
}
}

/// <summary>
/// Creates the standard period for the time zone and adds it to the Period collection.
/// </summary>
/// <param name="timeZoneInfo">The TimeZoneInfo object whose standard period is to be created.</param>
/// <returns>The standard period that was created.</returns>
private TimeZonePeriod CreateStandardPeriod(TimeZoneInfo timeZoneInfo)
{
TimeZonePeriod standardPeriod = new TimeZonePeriod();
standardPeriod.Id = TimeZonePeriod.StandardPeriodId;
standardPeriod.Name = TimeZonePeriod.StandardPeriodName;
standardPeriod.Bias = -timeZoneInfo.BaseUtcOffset;

this.periods.Add(standardPeriod.Id, standardPeriod);

return standardPeriod;
}

/// <summary>
/// Searches the period collection for a standard period that matches the specifications of the provided adjustment rule.
/// If one is found, it is returned.
/// If none are found, a new one is created, added to the collection and returned.
/// </summary>
/// <param name="timeZoneInfo">The TimeZoneInfo object that the adjustmentRule applies to.</param>
/// <param name="adjustmentRule">The AdjustmentRule object that we need a period for.</param>
/// <returns>The TimeZonePeriod object that was found or created.</returns>
internal TimeZonePeriod CreateStandardPeriodForAdjustmentRule(TimeZoneInfo timeZoneInfo, TimeZoneInfo.AdjustmentRule adjustmentRule)
{
var baseUtcOffsetDelta = adjustmentRule.GetBaseUtcOffsetDelta();

if (baseUtcOffsetDelta == TimeSpan.Zero)
{
// The standard bias of this adjustment rule matches the standard bias for the time zone. Use the standard period.
return this.periods[TimeZonePeriod.StandardPeriodId];
}
else
{
TimeSpan periodBias = -timeZoneInfo.BaseUtcOffset - baseUtcOffsetDelta;
String periodId = String.Format("{0}{1}", TimeZonePeriod.StandardPeriodId, (int)periodBias.TotalMinutes);

TimeZonePeriod period;
if (!this.periods.TryGetValue(periodId, out period))
{
period = new TimeZonePeriod();
period.Id = periodId;
period.Name = TimeZonePeriod.StandardPeriodName;
period.Bias = periodBias;

this.periods.Add(periodId, period);
}

return period;
}
}

/// <summary>
/// Searches the period collection for a daylight period that matches the specifications of the provided adjustment rule.
/// If one is found, it is returned.
/// If none are found, a new one is created, added to the collection and returned.
/// </summary>
/// <param name="timeZoneInfo">The TimeZoneInfo object that the adjustmentRule applies to.</param>
/// <param name="adjustmentRule">The AdjustmentRule object that we need a period for.</param>
/// <returns>The TimeZonePeriod object that was found or created.</returns>
internal TimeZonePeriod CreateDaylightPeriodForAdjustmentRule(TimeZoneInfo timeZoneInfo, TimeZoneInfo.AdjustmentRule adjustmentRule)
{
var baseUtcOffsetDelta = adjustmentRule.GetBaseUtcOffsetDelta();

TimeSpan periodBias = -timeZoneInfo.BaseUtcOffset - baseUtcOffsetDelta - adjustmentRule.DaylightDelta;
String periodId = String.Format("{0}{1}", TimeZonePeriod.DaylightPeriodId, (int)periodBias.TotalMinutes);

TimeZonePeriod period;
if (!this.periods.TryGetValue(periodId, out period))
{
period = new TimeZonePeriod();
period.Id = periodId;
period.Name = TimeZonePeriod.DaylightPeriodName;
period.Bias = periodBias;

this.periods.Add(periodId, period);
}

return period;
}

/// <summary>
/// Adds a transition group with a single transition to the specified period.
/// </summary>
Expand Down
48 changes: 48 additions & 0 deletions ComplexProperties/TimeZones/TimeZoneInfoExtensionMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Reflection;

namespace Microsoft.Exchange.WebServices.Data
{
/// <summary>
/// Utility class for declaring time zone related extension methods.
/// </summary>
public static class TimeZoneInfoExtensionMethods
{
/// <summary>
/// Extension method to return the internal BaseUtcOffsetDelta property value from an AdjustmentRule.
/// </summary>
/// <param name="adjustmentRule">The adjustement rule whose BaseUtcOffsetDelta value should be returned.</param>
/// <returns>A TimeSpan value that reprensents the AdjustmentRule's BaseUtcOffsetDelta value.</returns>
public static TimeSpan GetBaseUtcOffsetDelta(this TimeZoneInfo.AdjustmentRule adjustmentRule)
{
BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
PropertyInfo property = typeof(TimeZoneInfo.AdjustmentRule).GetProperty("BaseUtcOffsetDelta", bindFlags);
return (TimeSpan)property.GetValue(adjustmentRule, null);
}

/// <summary>
/// Extension method to determine if two TransitionTime values reference the same day.
/// </summary>
/// <param name="thisTransitionTime">The first TransitionTime to compare.</param>
/// <param name="otherTransitionTime">The second TransitionTime to compare.</param>
/// <returns>True if both TransitionTime objects reference the same day, otherwise false.</returns>
public static bool HasSameDate(this TimeZoneInfo.TransitionTime thisTransitionTime, TimeZoneInfo.TransitionTime otherTransitionTime)
{
if (thisTransitionTime.IsFixedDateRule && otherTransitionTime.IsFixedDateRule)
{
return (thisTransitionTime.Month == otherTransitionTime.Month)
&& (thisTransitionTime.Day == otherTransitionTime.Day);
}
else if (!thisTransitionTime.IsFixedDateRule && !otherTransitionTime.IsFixedDateRule)
{
return (thisTransitionTime.Month == otherTransitionTime.Month)
&& (thisTransitionTime.Week == otherTransitionTime.Week)
&& (thisTransitionTime.DayOfWeek == otherTransitionTime.DayOfWeek);
}
else
{
return false;
}
}
}
}
57 changes: 17 additions & 40 deletions ComplexProperties/TimeZones/TimeZoneTransitionGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,62 +114,39 @@ internal override void WriteElementsToXml(EwsServiceXmlWriter writer)
/// <summary>
/// Initializes this transition group based on the specified asjustment rule.
/// </summary>
/// <param name="timeZoneInfo">The time zone that the adjustment rule applies to.</param>
/// <param name="adjustmentRule">The adjustment rule to initialize from.</param>
/// <param name="standardPeriod">A reference to the pre-created standard period.</param>
internal virtual void InitializeFromAdjustmentRule(TimeZoneInfo.AdjustmentRule adjustmentRule, TimeZonePeriod standardPeriod)
internal virtual void InitializeFromAdjustmentRule(TimeZoneInfo timeZoneInfo, TimeZoneInfo.AdjustmentRule adjustmentRule)
{
if (adjustmentRule.DaylightDelta.TotalSeconds == 0)
if ((adjustmentRule.DaylightDelta == TimeSpan.Zero)
|| adjustmentRule.DaylightTransitionStart.HasSameDate(adjustmentRule.DaylightTransitionEnd))
{
// If the time zone info doesn't support Daylight Saving Time, we just need to
// create one transition to one group with one transition to the standard period.
TimeZonePeriod standardPeriodToSet = new TimeZonePeriod();
standardPeriodToSet.Id = string.Format(
"{0}/{1}",
standardPeriod.Id,
adjustmentRule.DateStart.Year);
standardPeriodToSet.Name = standardPeriod.Name;
standardPeriodToSet.Bias = standardPeriod.Bias;
this.timeZoneDefinition.Periods.Add(standardPeriodToSet.Id, standardPeriodToSet);

this.transitionToStandard = new TimeZoneTransition(this.timeZoneDefinition, standardPeriodToSet);
this.transitions.Add(this.transitionToStandard);
// If the admustment rule does not support daylight savings time,
// the transition must target a transition group that targets a single period.
// If the adjustment rule supports daylight savings time, but the DST start and end are on the same day,
// we treat is as through there is no DST period at all. This is because EWS would reject it as an invalid
// time zone. This is true for the "Sao Tome Standard Time" time zone, which has a DST period that lasts only
// one hour (sounds strange, but there is actually a valid reason for it).
this.transitionToStandard = new TimeZoneTransition(this.timeZoneDefinition, this.timeZoneDefinition.CreateStandardPeriodForAdjustmentRule(timeZoneInfo, adjustmentRule));
}
else
{
TimeZonePeriod daylightPeriod = new TimeZonePeriod();

// Generate an Id of the form "Daylight/2008"
daylightPeriod.Id = string.Format(
"{0}/{1}",
TimeZonePeriod.DaylightPeriodId,
adjustmentRule.DateStart.Year);
daylightPeriod.Name = TimeZonePeriod.DaylightPeriodName;
daylightPeriod.Bias = standardPeriod.Bias - adjustmentRule.DaylightDelta;

this.timeZoneDefinition.Periods.Add(daylightPeriod.Id, daylightPeriod);

// If the adjustment rule supports daylight savings time, the transition must target a standard transition group.
// with two transitions; one to DST, and a second back to Standard time.
this.transitionToDaylight = TimeZoneTransition.CreateTimeZoneTransition(
this.timeZoneDefinition,
daylightPeriod,
timeZoneDefinition.CreateDaylightPeriodForAdjustmentRule(timeZoneInfo, adjustmentRule),
adjustmentRule.DaylightTransitionStart);

TimeZonePeriod standardPeriodToSet = new TimeZonePeriod();
standardPeriodToSet.Id = string.Format(
"{0}/{1}",
standardPeriod.Id,
adjustmentRule.DateStart.Year);
standardPeriodToSet.Name = standardPeriod.Name;
standardPeriodToSet.Bias = standardPeriod.Bias;
this.timeZoneDefinition.Periods.Add(standardPeriodToSet.Id, standardPeriodToSet);

this.transitionToStandard = TimeZoneTransition.CreateTimeZoneTransition(
this.timeZoneDefinition,
standardPeriodToSet,
timeZoneDefinition.CreateStandardPeriodForAdjustmentRule(timeZoneInfo, adjustmentRule),
adjustmentRule.DaylightTransitionEnd);

this.transitions.Add(this.transitionToDaylight);
this.transitions.Add(this.transitionToStandard);
}

this.transitions.Add(this.transitionToStandard);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion Core/Requests/SetUserPhotoRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ private static SetUserPhotoResponse SetResultOrDefault(Func<object> serviceRespo
{
return (SetUserPhotoResponse)serviceResponseFactory();
}
catch (ServiceRequestException ex)
catch (ServiceRequestException)
{
throw;
}
Expand Down
Loading