Пример #1
0
        static TzTimeZoneRuleDate()
        {
            MinValue = new TzTimeZoneRuleDate();
            MinValue._utc = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            MinValue._local = new DateTime(1, 1, 1, 0, 0, 0, DateTimeKind.Local);
            MinValue._stdoff = TimeSpan.Zero;
            MinValue._gmtoff = TimeSpan.Zero;

            MaxValue = new TzTimeZoneRuleDate();
            MaxValue._utc = new DateTime(9999, 12, 31, 23, 59, 59, 999, DateTimeKind.Utc);
            MaxValue._local = new DateTime(9999, 12, 31, 23, 59, 59, 999, DateTimeKind.Local);
            MaxValue._stdoff = TimeSpan.Zero;
            MaxValue._gmtoff = TimeSpan.Zero;
        }
Пример #2
0
        private static bool Op(DateTime date1, TzTimeZoneRuleDate date2, Func <DateTime, DateTime, bool> func)
        {
            if (date2 == null)
            {
                throw new ArgumentNullException(nameof(date2));
            }

            if (date1.Kind == DateTimeKind.Unspecified)
            {
                throw new ArgumentException("Unspecified kind date", nameof(date1));
            }

            if (date1.Kind == DateTimeKind.Local)
            {
                return(func(date1, date2._local));
            }

            return(func(date1, date2.UtcDate));
        }
Пример #3
0
        /// <summary>
        /// Détermine si une valeur de date et d'heure pour le fuseau courant est ambigue
        /// </summary>
        /// <param name="dateTime">Valeur de date et d'heure</param>
        /// <returns>true si le paramètre dateTime est ambigu, sinon false</returns>
        //public bool IsAmbiguousTime(DateTime dateTime) {
        //    if (dateTime.Kind == DateTimeKind.Utc) return false;
        //    if (dateTime.Kind == DateTimeKind.Unspecified) throw new ArgumentException("Unspecified date time kind", "datetime");
        //    ZoneRuleAssociate zr = GetZoneRule(dateTime);
        //    Debug.Assert(zr.zoneRule != null);
        //    return false;
        //}
        /// <summary>
        /// Obtient pour une année donnée l'ensemble des dates de changement avec leur gmt et standard offset
        /// applicable avant cette date.
        /// </summary>
        /// <param name="year"></param>
        private void UpdateDateChange(int year)
        {
            lock (_zoneDates) {
                if (!_zoneDates.ContainsKey(year)) {
                    Dictionary<DateTime, TzTimeZoneRuleDate> dico = new Dictionary<DateTime, TzTimeZoneRuleDate>();

                    _zoneDates.Add(year, new List<TzTimeZoneRuleDate>());

                    for (int index = ZoneRules.Count - 1; index >= 0; index--) {
                        TzTimeZoneRule temp = ZoneRules[index];

                        if ((temp.StartZone.UtcDate.Year <= year && temp.EndZone.UtcDate.Year >= year) ||
                            (temp.StartZone.ToLocalTime().Year <= year && temp.EndZone.ToLocalTime().Year >= year)) {
                            if (TzTimeInfo.Rules.ContainsKey(temp.RuleName)) {
                                List<Rule> rules = TzTimeInfo.Rules[temp.RuleName];

                                foreach (Rule rule in rules) {
                                    // On calcule sur trois années pour couvrir les changements d'heure de fin d'année et début d'année
                                    for (int yearRef = year - 1; yearRef <= year + 1; yearRef++) {
                                        if (rule.LowerYear <= yearRef && rule.HighYear >= yearRef && TzUtilities.IsYearType(yearRef, rule.YearType)) {
                                            // Standard offset applicable à cette règle
                                            TimeSpan stdoff = GetLastSaveOffset(yearRef, rules, rule, temp.StartZone, temp.GmtOffset);
                                            // Date UTC et Local d'application de cette règle
                                            DateTime utc = TzUtilities.GetDateTime(rule, yearRef, temp.GmtOffset, stdoff, DateTimeKind.Utc);
                                            DateTime local = TzUtilities.GetDateTime(rule, yearRef, temp.GmtOffset, stdoff, DateTimeKind.Local);

                                            // Contient les dates de la règle avec le décalage gmt et standard applicable AVANT cette date
                                            TzTimeZoneRuleDate ruleDate = new TzTimeZoneRuleDate(utc, local, temp.GmtOffset, stdoff);
                                            if (ruleDate > temp.StartZone && ruleDate <= temp.EndZone) {
                                                if (!dico.ContainsKey(ruleDate.UtcDate))
                                                    dico.Add(ruleDate.UtcDate, ruleDate);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // On ajoute les changements induits par les date until de zone
                    for (int index = ZoneRules.Count - 1; index >= 0; index--) {
                        TzTimeZoneRule temp = ZoneRules[index];

                        if (temp.EndZone.UtcDate.Year == year || temp.EndZone.ToLocalTime().Year == year) {
                            if (!dico.ContainsKey(temp.EndZone.UtcDate))
                                dico.Add(temp.EndZone.UtcDate, temp.EndZone);
                        }
                    }

                    _zoneDates[year].AddRange(dico.Values.OrderBy(e => e.UtcDate));
                }
            }
        }
Пример #4
0
        /// <summary>
        /// Obtient le standard offset applicable à une règle
        /// </summary>
        /// <param name="year"></param>
        /// <param name="rules"></param>
        /// <param name="rule"></param>
        /// <param name="zoneDate"></param>
        /// <param name="gmtOffset"></param>
        /// <returns></returns>
        /// <remarks>Cette fonction compare des règles définies, comme un changement d'heure ne se
        /// produit pas en même temps qu'un autre on peut appliquer le stdoff entre règles sans
        /// se soucier de point recouvrant.
        /// </remarks>
        private static TimeSpan GetLastSaveOffset(int year, List<Rule> rules, Rule rule, TzTimeZoneRuleDate zoneDate, TimeSpan gmtOffset)
        {
            int bestyear = -1;

            TimeSpan range = TimeSpan.MaxValue;
            TimeSpan stdoff = zoneDate.StandardOffset;

            for (int index = rules.Count - 1; index >= 0; index--) {
                if (rules[index] == rule) continue;

                if (rules[index].LowerYear > year || rules[index].HighYear < bestyear) {
                    continue;
                }

                int yref = (rules[index].HighYear > year) ? year : rules[index].HighYear;

                if (yref == year && rules[index].AtKind == rule.AtKind &&
                    (rules[index].Month > rule.Month || (rules[index].Month == rule.Month && rules[index].Day.DayWork == DayWorkKind.Dom && rule.Day.DayWork == DayWorkKind.Dom && rules[index].Day.DayOfMonth > rule.Day.DayOfMonth))) {
                    if (yref > rules[index].LowerYear)
                        yref--;
                    else
                        continue;
                }

                for (; yref >= rules[index].LowerYear; yref--) {
                    if (!TzUtilities.IsYearType(yref, rules[index].YearType)) continue;

                    DateTime precedent;
                    DateTime current;

                    // On compare la date avec la date limite de zone
                    if (rules[index].AtKind == TimeKind.LocalWallTime) {
                        precedent = TzUtilities.GetDateTime(rules[index], yref, gmtOffset, TimeSpan.Zero, DateTimeKind.Local);
                        if (zoneDate.ToLocalTime() > precedent)
                            break;
                        // On exprime la date courante de la règle à vérifier en local
                        current = TzUtilities.GetDateTime(rule, year, gmtOffset, rules[index].StandardOffset, DateTimeKind.Local);
                    }
                    else {
                        precedent = TzUtilities.GetDateTime(rules[index], yref, gmtOffset, TimeSpan.Zero, DateTimeKind.Utc);
                        if (zoneDate.UtcDate > precedent)
                            break;
                        // On exprime la date courante de la règle à vérifier en utc
                        current = TzUtilities.GetDateTime(rule, year, gmtOffset, rules[index].StandardOffset, DateTimeKind.Utc);
                    }

                    if (precedent > current) {
                        continue;
                    }
                    else {
                        TimeSpan diff = current - precedent;
                        if (range > diff) {
                            stdoff = rules[index].StandardOffset;
                            range = diff;
                            bestyear = precedent.Year;
                        }
                        break;
                    }
                }
            }

            return stdoff;
        }
Пример #5
0
        /// <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;
        }
Пример #6
0
        private static bool Op(DateTime date1, TzTimeZoneRuleDate date2, Func<DateTime, DateTime, bool> func)
        {
            if (date2 == null)
                throw new ArgumentNullException("date2");

            if (date1.Kind == DateTimeKind.Unspecified)
                throw new ArgumentException("Unspecified kind date", "date1");

            if (date1.Kind == DateTimeKind.Local)
                return func(date1, date2._local);

            return func(date1, date2._utc);
        }
Пример #7
0
        /// <summary>
        /// Obtient le standard offset applicable à une règle
        /// </summary>
        /// <param name="year"></param>
        /// <param name="rules"></param>
        /// <param name="rule"></param>
        /// <param name="zoneDate"></param>
        /// <param name="gmtOffset"></param>
        /// <returns></returns>
        /// <remarks>Cette fonction compare des règles définies, comme un changement d'heure ne se
        /// produit pas en même temps qu'un autre on peut appliquer le stdoff entre règles sans
        /// se soucier de point recouvrant.
        /// </remarks>
        private static TimeSpan GetLastSaveOffset(int year, List <Rule> rules, Rule rule, TzTimeZoneRuleDate zoneDate, TimeSpan gmtOffset)
        {
            int bestyear = -1;

            TimeSpan range  = TimeSpan.MaxValue;
            TimeSpan stdoff = zoneDate.StandardOffset;

            for (int index = rules.Count - 1; index >= 0; index--)
            {
                if (rules[index] == rule)
                {
                    continue;
                }

                if (rules[index].LowerYear > year || rules[index].HighYear < bestyear)
                {
                    continue;
                }

                int yref = (rules[index].HighYear > year) ? year : rules[index].HighYear;

                if (yref == year && rules[index].AtKind == rule.AtKind &&
                    (rules[index].Month > rule.Month || (rules[index].Month == rule.Month && rules[index].Day.DayWork == DayWorkKind.Dom && rule.Day.DayWork == DayWorkKind.Dom && rules[index].Day.DayOfMonth > rule.Day.DayOfMonth)))
                {
                    if (yref > rules[index].LowerYear)
                    {
                        yref--;
                    }
                    else
                    {
                        continue;
                    }
                }

                for (; yref >= rules[index].LowerYear; yref--)
                {
                    if (!TzUtilities.IsYearType(yref, rules[index].YearType))
                    {
                        continue;
                    }

                    DateTime precedent;
                    DateTime current;

                    // On compare la date avec la date limite de zone
                    if (rules[index].AtKind == TimeKind.LocalWallTime)
                    {
                        precedent = TzUtilities.GetDateTime(rules[index], yref, gmtOffset, TimeSpan.Zero, DateTimeKind.Local);
                        if (zoneDate.ToLocalTime() > precedent)
                        {
                            break;
                        }
                        // On exprime la date courante de la règle à vérifier en local
                        current = TzUtilities.GetDateTime(rule, year, gmtOffset, rules[index].StandardOffset, DateTimeKind.Local);
                    }
                    else
                    {
                        precedent = TzUtilities.GetDateTime(rules[index], yref, gmtOffset, TimeSpan.Zero, DateTimeKind.Utc);
                        if (zoneDate.UtcDate > precedent)
                        {
                            break;
                        }
                        // On exprime la date courante de la règle à vérifier en utc
                        current = TzUtilities.GetDateTime(rule, year, gmtOffset, rules[index].StandardOffset, DateTimeKind.Utc);
                    }

                    if (precedent > current)
                    {
                        continue;
                    }
                    else
                    {
                        TimeSpan diff = current - precedent;
                        if (range > diff)
                        {
                            stdoff   = rules[index].StandardOffset;
                            range    = diff;
                            bestyear = precedent.Year;
                        }
                        break;
                    }
                }
            }

            return(stdoff);
        }
Пример #8
0
        /// <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);
        }
Пример #9
0
        //public bool IsAmbiguousTime(DateTime dateTime) {
        //    if (dateTime.Kind == DateTimeKind.Utc) return false;
        //    if (dateTime.Kind == DateTimeKind.Unspecified) throw new ArgumentException("Unspecified date time kind", "datetime");

        //    ZoneRuleAssociate zr = GetZoneRule(dateTime);
        //    Debug.Assert(zr.zoneRule != null);

        //    return false;
        //}

        /// <summary>
        /// Obtient pour une année donnée l'ensemble des dates de changement avec leur gmt et standard offset
        /// applicable avant cette date.
        /// </summary>
        /// <param name="year"></param>
        private void UpdateDateChange(int year)
        {
            lock (_zoneDates)
            {
                if (!_zoneDates.ContainsKey(year))
                {
                    Dictionary <DateTime, TzTimeZoneRuleDate> dico = new Dictionary <DateTime, TzTimeZoneRuleDate>();

                    _zoneDates.Add(year, new List <TzTimeZoneRuleDate>());

                    for (int index = ZoneRules.Count - 1; index >= 0; index--)
                    {
                        TzTimeZoneRule temp = ZoneRules[index];

                        if ((temp.StartZone.UtcDate.Year <= year && temp.EndZone.UtcDate.Year >= year) ||
                            (temp.StartZone.ToLocalTime().Year <= year && temp.EndZone.ToLocalTime().Year >= year))
                        {
                            if (TzTimeInfo.Rules.ContainsKey(temp.RuleName))
                            {
                                List <Rule> rules = TzTimeInfo.Rules[temp.RuleName];

                                foreach (Rule rule in rules)
                                {
                                    // On calcule sur trois années pour couvrir les changements d'heure de fin d'année et début d'année
                                    for (int yearRef = year - 1; yearRef <= year + 1; yearRef++)
                                    {
                                        if (rule.LowerYear <= yearRef && rule.HighYear >= yearRef && TzUtilities.IsYearType(yearRef, rule.YearType))
                                        {
                                            // Standard offset applicable à cette règle
                                            TimeSpan stdoff = GetLastSaveOffset(yearRef, rules, rule, temp.StartZone, temp.GmtOffset);
                                            // Date UTC et Local d'application de cette règle
                                            DateTime utc   = TzUtilities.GetDateTime(rule, yearRef, temp.GmtOffset, stdoff, DateTimeKind.Utc);
                                            DateTime local = TzUtilities.GetDateTime(rule, yearRef, temp.GmtOffset, stdoff, DateTimeKind.Local);

                                            // Contient les dates de la règle avec le décalage gmt et standard applicable AVANT cette date
                                            TzTimeZoneRuleDate ruleDate = new TzTimeZoneRuleDate(utc, local, temp.GmtOffset, stdoff);
                                            if (ruleDate > temp.StartZone && ruleDate <= temp.EndZone)
                                            {
                                                if (!dico.ContainsKey(ruleDate.UtcDate))
                                                {
                                                    dico.Add(ruleDate.UtcDate, ruleDate);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // On ajoute les changements induits par les date until de zone
                    for (int index = ZoneRules.Count - 1; index >= 0; index--)
                    {
                        TzTimeZoneRule temp = ZoneRules[index];

                        if (temp.EndZone.UtcDate.Year == year || temp.EndZone.ToLocalTime().Year == year)
                        {
                            if (!dico.ContainsKey(temp.EndZone.UtcDate))
                            {
                                dico.Add(temp.EndZone.UtcDate, temp.EndZone);
                            }
                        }
                    }

                    _zoneDates[year].AddRange(dico.Values.OrderBy(e => e.UtcDate));
                }
            }
        }