/// <summary> /// Obtient la description de zone correspondant à la date spécifiée /// </summary> /// <param name="point"><see cref="DateTime"/> spécifié.</param> /// <returns><see cref="ZoneRuleAssociate"/> contenant le <see cref="DateTime"/> spécifié</returns> private ZoneRuleAssociate GetZoneRule(DateTime point) { if (point.Kind == DateTimeKind.Unspecified) { throw new ArgumentException("Unspecified date time kind", nameof(point)); } ZoneRuleAssociate za = new ZoneRuleAssociate(); za.standardOffset = TimeSpan.Zero; TimeSpan stdoff = TimeSpan.Zero; bool utc = (point.Kind == DateTimeKind.Utc); // Traduction de la date en règle TzTimeZoneRuleUntil rulePoint = new TzTimeZoneRuleUntil(); rulePoint.Year = point.Year; rulePoint.At = point.TimeOfDay; rulePoint.AtKind = (utc) ? TimeKind.UniversalTime : TimeKind.LocalWallTime; rulePoint.Month = point.Month; rulePoint.Day = new DayOfRule(DayWorkKind.Dom, point.DayOfWeek, point.Day); for (int index = ZoneRules.Count - 1; index >= 0; index--) { TzTimeZoneRule temp = ZoneRules[index]; DateTime startTime = (utc) ? temp.StartZone.UtcDate : temp.StartZone.ToLocalTime(); DateTime endTime = (utc) ? temp.EndZone.UtcDate : temp.EndZone.ToLocalTime(); stdoff = temp.StartZone.StandardOffset; if (point >= startTime && point < endTime) { if (!TzTimeInfo.Rules.ContainsKey(temp.RuleName)) { // Max time za.zoneRule = temp; za.standardOffset = temp.FixedStandardOffset; break; } else { // Trouver la dernière règle applicable au point Rule lastRule = GetLastStandardOffset(TzTimeInfo.Rules[temp.RuleName], rulePoint, temp.StartZone, temp.GmtOffset, RuleSearchKind.LessThanOrEqual); za.zoneRule = temp; za.standardOffset = (lastRule == null) ? stdoff : lastRule.StandardOffset; za.Letter = (lastRule == null) ? string.Empty : lastRule.Letter; break; } } } return(za); }
/// <summary> /// Obtient la description de zone correspondant à la date spécifiée /// </summary> /// <param name="point"><see cref="DateTime"/> spécifié.</param> /// <returns><see cref="ZoneRuleAssociate"/> contenant le <see cref="DateTime"/> spécifié</returns> private ZoneRuleAssociate GetZoneRule(DateTime point) { if (point.Kind == DateTimeKind.Unspecified) throw new ArgumentException("Unspecified date time kind", "point"); ZoneRuleAssociate za = new ZoneRuleAssociate(); za.standardOffset = TimeSpan.Zero; TimeSpan gmtoff = TimeSpan.Zero; TimeSpan stdoff = TimeSpan.Zero; bool utc = (point.Kind == DateTimeKind.Utc); // Traduction de la date en règle TzTimeZoneRuleUntil rulePoint = new TzTimeZoneRuleUntil(); rulePoint.Year = point.Year; rulePoint.At = point.TimeOfDay; rulePoint.AtKind = (utc) ? TimeKind.UniversalTime : TimeKind.LocalWallTime; rulePoint.Month = point.Month; rulePoint.Day = new DayOfRule(DayWorkKind.Dom, point.DayOfWeek, point.Day); for (int index = ZoneRules.Count-1; index >= 0; index--) { TzTimeZoneRule temp = ZoneRules[index]; gmtoff = temp.GmtOffset; DateTime startTime = (utc) ? temp.StartZone.UtcDate : temp.StartZone.ToLocalTime(); DateTime endTime = (utc) ? temp.EndZone.UtcDate : temp.EndZone.ToLocalTime(); stdoff = temp.StartZone.StandardOffset; if (point >= startTime && point < endTime) { if (!TzTimeInfo.Rules.ContainsKey(temp.RuleName)) { // Max time za.zoneRule = temp; za.standardOffset = temp.FixedStandardOffset; break; } else { // Trouver la dernière règle applicable au point Rule lastRule = GetLastStandardOffset(TzTimeInfo.Rules[temp.RuleName], rulePoint, temp.StartZone, temp.GmtOffset); za.zoneRule = temp; za.standardOffset = (lastRule == null) ? stdoff : lastRule.StandardOffset; za.Letter = (lastRule == null) ? string.Empty : lastRule.Letter; break; } } } return za; }
/// <summary> /// Obtient la règle applicable à un point /// </summary> /// <param name="rules">Ensemble des règles à parcourir</param> /// <param name="date"><see cref="TzTimeZoneRuleUntil"/> représentant la date du ponit</param> /// <param name="startZone">Limite inférieure de validité des règles</param> /// <param name="gmtOffset"></param> /// <returns></returns> internal static Rule GetLastStandardOffset(List<Rule> rules, TzTimeZoneRuleUntil date, TzTimeZoneRuleDate startZone, TimeSpan gmtOffset) { Rule lastRule = null; TimeSpan range = TimeSpan.MaxValue; DateTime ruleTime; // En fonction du kind de la date Until on compare des dates UTC ou des dates locales DateTimeKind kind = (date.AtKind == TimeKind.LocalWallTime) ? DateTimeKind.Local : DateTimeKind.Utc; DateTime startTimeZone = (kind == DateTimeKind.Local) ? startZone.ToLocalTime() : startZone.UtcDate; // On pré-calcule la date de la règle dans le type permettant l'absence du daylight saving time // Une date en LocalWallTime sera exprimée en local // Une date en LocalStandardTime sera exprimée en UTC // Une date en UTC sera exprimée en UTC DateTime dateTime = TzUtilities.GetDateTime(date, date.Year, gmtOffset, TimeSpan.Zero, kind); int bestYear = -1; int yref = 0; // On parcourt toutes les règles car rien ne nous assure quelles sont dans un ordre spécifique for(int i=rules.Count - 1; i >= 0; i--) { Rule rule = rules[i]; // Les années ne pouvant pas rivaliser avec la meilleure année obtenue sont ignorées if (rule.HighYear < bestYear) continue; // Certaines règles sont déclenchées le 1er janvier de l'année, avec le décalage // GMT on peut se retrouver dans le cas LowerYear = date.Year+1. // Exemple : je cherche la règle applicable le 31 décembre 2010 23:00 local, si on est sur une règle // applicable le 1 janvier 2011 utc alors le décalage gmt peut faire en sorte que la règle soit retenue if (rule.LowerYear == date.Year + 1 && rule.Month == 1 && date.Month == 12 && rule.AtKind != date.AtKind) { yref = rule.LowerYear; } else { // Année de début supérieure au point recherché, la règle n'est pas applicable if (rule.LowerYear > date.Year) continue; // On prend l'année qui convient le mieux au point yref = (rule.HighYear > date.Year) ? date.Year : rule.HighYear; // Cas particulier des changements à cheval sur deux années // Si les dates à comparer ne sont pas de même kind la règle applicable en année N // peut se retrouver applicable en N-1 if (yref == date.Year && yref < rule.HighYear && date.AtKind != rule.AtKind) { if (rule.Month == 1 && date.Month == 12) yref++; } // Au pire il faudra calculer la date pour l'année désirée et l'année précédente // Par exemple si on recherche la dernière règle applicable au mois de janvier il // faudra remonter au mois x de l'année précédente // Exmple : on a une règle définie pour le mois de mars de 2000 à 2050, on recherche // pour un point en janvier 2010. La date précédemment retenue est 2010, mais comme // janvier < mars si on reste en 2010 elle sera exclue. Il faut démarrer en 2009 pour // cette règle // On économise l'examen de l'année yref si la règle est applicable après le point recherché // Seul une comparaison de même kind peut être effectuée. if (yref == date.Year && rule.AtKind == date.AtKind && (rule.Month > date.Month || (rule.Month == date.Month && rule.Day.DayWork == DayWorkKind.Dom && date.Day.DayWork == DayWorkKind.Dom && rule.Day.DayOfMonth > date.Day.DayOfMonth)) ) { if (yref > rule.LowerYear) yref--; else continue; } } for (; yref >= rule.LowerYear; yref--) { if (!TzUtilities.IsYearType(yref, rule.YearType)) continue; // On est obligé de rechercher la règle précédente si pour comparer les dates // on a besoin du daylight saving time if ((rule.AtKind == TimeKind.LocalWallTime && kind == DateTimeKind.Utc) || (rule.AtKind != TimeKind.LocalWallTime && kind == DateTimeKind.Local)) { TimeSpan stdoff = GetLastSaveOffset(yref, rules, rule, startZone, gmtOffset); ruleTime = TzUtilities.GetDateTime(rule, yref, gmtOffset, stdoff, kind); } else { // Si les deux règles sont en locales ou utc/standardoffset on peut les comparer directement ruleTime = TzUtilities.GetDateTime(rule, yref, gmtOffset, TimeSpan.Zero, kind); } Debug.Assert(ruleTime.Kind == dateTime.Kind); // Si la date d'application de la règle est supérieure au point recherché alors // on recherche pour l'année précédente si on n'a pas atteint la borne inférieure if (ruleTime > dateTime && ruleTime.Year > rule.LowerYear) { continue; } // Si la règle est comprise dans les bornes [startTimeZone, dateTime] alors elle // est applicable, on la retient si elle est meilleure que celle déjà retenue. if (ruleTime >= startTimeZone && dateTime >= ruleTime) { TimeSpan diff = dateTime - ruleTime; if (range > diff) { lastRule = rule; range = diff; bestYear = ruleTime.Year; } } break; } } return lastRule; }
/// <summary> /// Obtient la règle applicable à un point /// </summary> /// <param name="rules">Ensemble des règles à parcourir</param> /// <param name="date"><see cref="TzTimeZoneRuleUntil"/> représentant la date du point</param> /// <param name="startZone">Limite inférieure de validité des règles</param> /// <param name="gmtOffset"></param> /// <param name="ruleSearch">Indique si on recherche une régle inférieure ou égal au point</param> /// <returns></returns> internal static Rule GetLastStandardOffset(List <Rule> rules, TzTimeZoneRuleUntil date, TzTimeZoneRuleDate startZone, TimeSpan gmtOffset, RuleSearchKind ruleSearch) { Rule lastRule = null; TimeSpan range = TimeSpan.MaxValue; DateTime ruleTime; // En fonction du kind de la date Until on compare des dates UTC ou des dates locales DateTimeKind kind = (date.AtKind == TimeKind.LocalWallTime) ? DateTimeKind.Local : DateTimeKind.Utc; DateTime startTimeZone = (kind == DateTimeKind.Local) ? startZone.ToLocalTime() : startZone.UtcDate; // On pré-calcule la date de la règle dans le type permettant l'absence du daylight saving time // Une date en LocalWallTime sera exprimée en local // Une date en LocalStandardTime sera exprimée en UTC // Une date en UTC sera exprimée en UTC DateTime dateTime = TzUtilities.GetDateTime(date, date.Year, gmtOffset, TimeSpan.Zero, kind); int bestYear = -1; int yref = 0; // On parcourt toutes les règles car rien ne nous assure quelles sont dans un ordre spécifique for (int i = rules.Count - 1; i >= 0; i--) { Rule rule = rules[i]; // Les années ne pouvant pas rivaliser avec la meilleure année obtenue sont ignorées if (rule.HighYear < bestYear) { continue; } // Certaines règles sont déclenchées le 1er janvier de l'année, avec le décalage // GMT on peut se retrouver dans le cas LowerYear = date.Year+1. // Exemple : je cherche la règle applicable le 31 décembre 2010 23:00 local, si on est sur une règle // applicable le 1 janvier 2011 utc alors le décalage gmt peut faire en sorte que la règle soit retenue if (rule.LowerYear == date.Year + 1 && rule.Month == 1 && date.Month == 12 && rule.AtKind != date.AtKind) { yref = rule.LowerYear; } else { // Année de début supérieure au point recherché, la règle n'est pas applicable if (rule.LowerYear > date.Year) { continue; } // On prend l'année qui convient le mieux au point yref = (rule.HighYear > date.Year) ? date.Year : rule.HighYear; // Cas particulier des changements à cheval sur deux années // Si les dates à comparer ne sont pas de même kind la règle applicable en année N // peut se retrouver applicable en N-1 if (yref == date.Year && yref < rule.HighYear && date.AtKind != rule.AtKind) { if (rule.Month == 1 && date.Month == 12) { yref++; } } // Au pire il faudra calculer la date pour l'année désirée et l'année précédente // Par exemple si on recherche la dernière règle applicable au mois de janvier il // faudra remonter au mois x de l'année précédente // Exmple : on a une règle définie pour le mois de mars de 2000 à 2050, on recherche // pour un point en janvier 2010. La date précédemment retenue est 2010, mais comme // janvier < mars si on reste en 2010 elle sera exclue. Il faut démarrer en 2009 pour // cette règle // On économise l'examen de l'année yref si la règle est applicable après le point recherché // Seul une comparaison de même kind peut être effectuée. if (yref == date.Year && rule.AtKind == date.AtKind && (rule.Month > date.Month || (rule.Month == date.Month && rule.Day.DayWork == DayWorkKind.Dom && date.Day.DayWork == DayWorkKind.Dom && rule.Day.DayOfMonth > date.Day.DayOfMonth)) ) { if (yref > rule.LowerYear) { yref--; } else { continue; } } } for (; yref >= rule.LowerYear; yref--) { if (!TzUtilities.IsYearType(yref, rule.YearType)) { continue; } // On est obligé de rechercher la règle précédente si pour comparer les dates // on a besoin du daylight saving time if ((rule.AtKind == TimeKind.LocalWallTime && kind == DateTimeKind.Utc) || (rule.AtKind != TimeKind.LocalWallTime && kind == DateTimeKind.Local)) { TimeSpan stdoff = GetLastSaveOffset(yref, rules, rule, startZone, gmtOffset); ruleTime = TzUtilities.GetDateTime(rule, yref, gmtOffset, stdoff, kind); } else { // Si les deux règles sont en locales ou utc/standardoffset on peut les comparer directement ruleTime = TzUtilities.GetDateTime(rule, yref, gmtOffset, TimeSpan.Zero, kind); } Debug.Assert(ruleTime.Kind == dateTime.Kind); // Si la date d'application de la règle est supérieure au point recherché alors // on recherche pour l'année précédente si on n'a pas atteint la borne inférieure if (ruleTime > dateTime && ruleTime.Year > rule.LowerYear) { continue; } // Si la règle est comprise dans les bornes [startTimeZone, dateTime] ou [startTimeZone, dateTime[ alors elle // est applicable, on la retient si elle est meilleure que celle déjà retenue. if (ruleTime >= startTimeZone && ((ruleTime <= dateTime && ruleSearch == RuleSearchKind.LessThanOrEqual) || (ruleTime < dateTime && ruleSearch == RuleSearchKind.LessThan))) { TimeSpan diff = dateTime - ruleTime; if (range > diff) { lastRule = rule; range = diff; bestYear = ruleTime.Year; } } break; } } return(lastRule); }