예제 #1
0
        /// <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);
        }
예제 #2
0
        /// <summary>
        /// Renseigne une zone en fonction de sa description
        /// </summary>
        /// <param name="zoneDescription">Ensemble de paramètres décrivant la zone</param>
        /// <param name="zone"><see cref="TzTimeZone"/> à renseigner en fonction de la description spécifiée.</param>
        /// <param name="continuation">Indique que la description est un ajout ou non à la zone spécifiée.</param>
        /// <returns><see cref="bool"/> spécifiant qu'un complément de description est attendu ou non.</returns>
        public static bool GetZoneSub(List <string> zoneDescription, ref TzTimeZone zone, bool continuation)
        {
            int i_gmtoff, i_rule, i_format;
            int i_untilyear, i_untilmonth;
            int i_untilday, i_untiltime;

            TzTimeZone zcont = zone;

            if (continuation)
            {
                i_gmtoff     = 0;
                i_rule       = 1;
                i_format     = 2;
                i_untilyear  = 3;
                i_untilmonth = 4;
                i_untilday   = 5;
                i_untiltime  = 6;
            }
            else
            {
                i_gmtoff     = 2;
                i_rule       = 3;
                i_format     = 4;
                i_untilyear  = 5;
                i_untilmonth = 6;
                i_untilday   = 7;
                i_untiltime  = 8;
                zcont.Name   = zoneDescription[1];
            }

            TzTimeZoneRule zoneRule = new TzTimeZoneRule();

            zoneRule.GmtOffset = TzUtilities.GetHMS(zoneDescription[i_gmtoff]);
            zoneRule.Format    = zoneDescription[i_format];
            zoneRule.RuleName  = zoneDescription[i_rule];

            bool hashuntil = zoneDescription.Count > i_untilyear;

            if (hashuntil)
            {
                zoneRule.Until      = new TzTimeZoneRuleUntil();
                zoneRule.Until.Year = Convert.ToInt32(zoneDescription[i_untilyear], CultureInfo.InvariantCulture);

                SetRuleSub(zoneRule.Until,
                           (zoneDescription.Count > i_untilmonth) ? zoneDescription[i_untilmonth] : "Jan",
                           (zoneDescription.Count > i_untilday) ? zoneDescription[i_untilday] : "1",
                           (zoneDescription.Count > i_untiltime) ? zoneDescription[i_untiltime] : "0");
            }
            zcont.ZoneRules.Add(zoneRule);

            return(hashuntil);
        }
예제 #3
0
        /// <summary>
        /// Construit l'ensemble des règles chargées
        /// </summary>
        /// <remarks>
        /// La date de début d'une zone ne correspond pas forcement à la date de fin de zone précédente. Exemple avec Pacific/Samoa ou
        /// on perd 1 journée le 30 décembre 2011
        /// Zone Pacific/Apia	 12:33:04 -	LMT	1892 Jul  5
        ///	-11:26:56 -	LMT	1911
        ///	-11:30	-	-1130	1950
        ///	-11:00	WS	-11/-10	2011 Dec 29 24:00
        ///	 13:00	WS	+13/+14
        ///
        /// Certaines dates de fin peuvent coincider avec des règles de changement
        /// Exemple : America/Grand_Turk
        /// -5:00	US	E%sT	2015 Nov Sun>=1 2:00 coincide avec la régle Rule	US	2007	max	-	Nov	Sun>=1	2:00	0	S
        /// -4:00	-	AST	2018 Mar 11 3:00
        /// 1/11/2015 01:00 EDT (sunday locale) => 05:00u
        /// 1/11/2015 02:00 AST (sunday locale) => 06:00u
        ///
        /// -4:00	-	AST	2018 Mar 11 3:00 coincide avec la régle Rule	US	2007	max	-	Mar	Sun>=8	2:00 (07:00 +5)	1:00	D
        /// -5:00	US	E%sT
        /// 11/03/2018 02:00 AST => 06:00u
        /// 11/03/2018 03:00 EDT => 07:00u
        /// 11/03/2018 04:00 EDT => 08:00u
        ///
        /// La date de début UTC de la zone suivante est la date de fin UTC de la zone précédente, ce n'est pas vrai pour ce qui concerne les
        /// dates locales. Exemple de samoa qui avance de 24 heures.
        ///
        /// Date de début : On construit la date à partir de la date utc précédente et on applique la régle courante pour obtenir la date locale
        /// Date de fin : On construit la date à partir de la date until en appliquant le changement DST précédent strictement inférieure
        /// </remarks>
        private static void BuildZone()
        {
            var ienum = _zones.GetEnumerator();

            while (ienum.MoveNext())
            {
                TzTimeZone temp = ienum.Current.Value;

                TimeSpan stdoff = TimeSpan.Zero;

                // Coordonnées de la zone
                var tz = _countryCode.SelectMany(e => e.TZone).FirstOrDefault(e => e.TZName == temp.Name);
                temp.Coordinates = tz.coordinates;
                temp.Comment     = tz.comment;

                for (int index = 0; index < temp.ZoneRules.Count; index++)
                {
                    TzTimeZoneRule zr = temp.ZoneRules[index];

                    // Zone avec un décalage fixe
                    if (zr.RuleName != "-" && !_rules.ContainsKey(zr.RuleName))
                    {
                        zr.FixedStandardOffset = TzUtilities.GetHMS(zr.RuleName);

                        // Dans ce cas précis le formatage % n'est pas autorisé
                        if (zr.Format.Contains("%"))
                        {
                            throw new ArgumentException("%s in ruleless zone " + temp.Name + "(" + temp.Filename + "," + temp.LineNumber + ")");
                        }
                    }

                    #region Start date
                    if (index == 0)
                    {
                        zr.StartZone = TzTimeZoneRuleDate.MinValue;
                    }
                    else
                    {
                        DateTime previousUTCDate        = temp.ZoneRules[index - 1].EndZone.UtcDate;
                        TimeSpan previousStandardOffset = temp.ZoneRules[index - 1].EndZone.StandardOffset;

                        if (zr.RuleName == "-")
                        {
                            zr.StartZone = new TzTimeZoneRuleDate(previousUTCDate, zr.GmtOffset, TimeSpan.Zero);
                        }
                        else if (!_rules.ContainsKey(zr.RuleName))
                        {
                            zr.StartZone = new TzTimeZoneRuleDate(previousUTCDate, zr.GmtOffset, zr.FixedStandardOffset);
                        }
                        else
                        {
                            // Il faut rechercher si pour la date UTC on aurait une régle qui s'applique
                            Rule lastRule = TzTimeZone.GetRuleAtPoint(_rules[zr.RuleName], previousUTCDate, zr.GmtOffset, previousStandardOffset);
                            zr.StartZone = new TzTimeZoneRuleDate(previousUTCDate, zr.GmtOffset, lastRule?.StandardOffset ?? previousStandardOffset);
                        }
                    }
                    #endregion

                    #region End date
                    if (zr.Until == null)
                    {
                        zr.EndZone = TzTimeZoneRuleDate.MaxValue;
                    }
                    else if (zr.RuleName == "-")
                    {
                        stdoff     = TimeSpan.Zero;
                        zr.EndZone = new TzTimeZoneRuleDate(TzUtilities.GetDateTime(zr.Until, zr.Until.Year, zr.GmtOffset, stdoff, DateTimeKind.Utc), zr.GmtOffset, stdoff);
                    }
                    else if (!_rules.ContainsKey(zr.RuleName))
                    {
                        // Zone avec décalage fixe
                        stdoff     = zr.FixedStandardOffset;
                        zr.EndZone = new TzTimeZoneRuleDate(TzUtilities.GetDateTime(zr.Until, zr.Until.Year, zr.GmtOffset, stdoff, DateTimeKind.Utc), zr.GmtOffset, stdoff);
                    }
                    else
                    {
                        Rule lastRule = TzTimeZone.GetLastStandardOffset(_rules[zr.RuleName], zr.Until, zr.StartZone, zr.GmtOffset, RuleSearchKind.LessThan);
                        if (lastRule != null)
                        {
                            stdoff = lastRule.StandardOffset;
                        }
                        zr.EndZone = new TzTimeZoneRuleDate(TzUtilities.GetDateTime(zr.Until, zr.Until.Year, zr.GmtOffset, stdoff, DateTimeKind.Utc), zr.GmtOffset, stdoff);
                    }
                    #endregion
                }
            }
        }
예제 #4
0
        /// <summary>
        /// Renseigne une zone en fonction de sa description
        /// </summary>
        /// <param name="zoneDescription">Ensemble de paramètres décrivant la zone</param>
        /// <param name="zone"><see cref="TzTimeZone"/> à renseigner en fonction de la description spécifiée.</param>
        /// <param name="continuation">Indique que la description est un ajout ou non à la zone spécifiée.</param>
        /// <returns><see cref="bool"/> spécifiant qu'un complément de description est attendu ou non.</returns>
        public static bool GetZoneSub(List<string> zoneDescription, ref TzTimeZone zone, bool continuation)
        {
            int i_gmtoff, i_rule, i_format;
            int i_untilyear, i_untilmonth;
            int i_untilday, i_untiltime;

            TzTimeZone zcont = zone;

            if (continuation) {
                i_gmtoff = 0;
                i_rule = 1;
                i_format = 2;
                i_untilyear = 3;
                i_untilmonth = 4;
                i_untilday = 5;
                i_untiltime = 6;
            }
            else {
                i_gmtoff = 2;
                i_rule = 3;
                i_format = 4;
                i_untilyear = 5;
                i_untilmonth = 6;
                i_untilday = 7;
                i_untiltime = 8;
                zcont.Name = zoneDescription[1];
            }

            TzTimeZoneRule zoneRule = new TzTimeZoneRule();
            zoneRule.GmtOffset = TzUtilities.GetHMS(zoneDescription[i_gmtoff]);
            zoneRule.Format = zoneDescription[i_format];
            zoneRule.RuleName = zoneDescription[i_rule];

            bool hashuntil = zoneDescription.Count > i_untilyear;
            if (hashuntil) {
                zoneRule.Until = new TzTimeZoneRuleUntil();
                zoneRule.Until.Year = Convert.ToInt32(zoneDescription[i_untilyear]);

                SetRuleSub(zoneRule.Until,
                    (zoneDescription.Count > i_untilmonth) ? zoneDescription[i_untilmonth] : "Jan",
                    (zoneDescription.Count > i_untilday) ? zoneDescription[i_untilday] : "1",
                    (zoneDescription.Count > i_untiltime) ? zoneDescription[i_untiltime] : "0");
            }
            zcont.ZoneRules.Add(zoneRule);

            return hashuntil;
        }
예제 #5
0
        /// <summary>
        /// Obtient les dates de changements de l'année spécifiée
        /// </summary>
        /// <param name="year">Année de recherche</param>
        /// <returns><see cref="DateTime">DateTime[]</see> des changements qui surviennent dans l'année spécifié.</returns>
        internal DateTime[] GetDayChangeTime(int year)
        {
            List <DateTime> dates = new List <DateTime>();

            for (int index = ZoneRules.Count - 1; index >= 0; index--)
            {
                TzTimeZoneRule temp      = ZoneRules[index];
                DateTime       startTime = temp.StartZone.ToLocalTime();
                DateTime       endTime   = temp.EndZone.ToLocalTime();

                if (startTime.Year <= year && endTime.Year >= year)
                {
                    if (TzTimeInfo.Rules.ContainsKey(temp.RuleName))
                    {
                        List <Rule> rules = TzTimeInfo.Rules[temp.RuleName];

                        foreach (Rule rule in rules)
                        {
                            if (rule.AtKind == TimeKind.LocalWallTime)
                            {
                                // La règle est applicable dans le TimeKind recherché, on compare les années directement
                                if (rule.LowerYear <= year && rule.HighYear >= year && TzUtilities.IsYearType(year, rule.YearType))
                                {
                                    DateTime date = TzUtilities.GetDateTime(rule, year, temp.GmtOffset, TimeSpan.Zero, DateTimeKind.Local);
                                    if (date >= startTime && date <= endTime && !dates.Contains(date))
                                    {
                                        dates.Add(date);
                                    }
                                }
                            }
                            else
                            {
                                // La règle doit être convertie en local, l'application de GMT+DST peut faire en sorte
                                // qu'on change d'année entre utc et local
                                if (rule.LowerYear <= year && rule.HighYear >= year && TzUtilities.IsYearType(year, rule.YearType))
                                {
                                    TimeSpan stdoff = GetLastSaveOffset(year, rules, rule, temp.StartZone, temp.GmtOffset);
                                    DateTime date   = TzUtilities.GetDateTime(rule, year, temp.GmtOffset, stdoff, DateTimeKind.Local);
                                    if (date.Year == year && date >= startTime && date <= endTime && !dates.Contains(date))
                                    {
                                        dates.Add(date);
                                        continue;
                                    }
                                }

                                // La règle n'est pas applicable en prenant l'année courante mais peut le devenir si
                                // on est à cheval sur deux années. Année N-1 et règle en décembre ou année N+1 et règle en Janvier
#pragma warning disable CA1814 // Préférer les tableaux en escalier aux tableaux multidimensionnels
                                int[,] yrefs = { { year - 1, 12 }, { year + 1, 1 } };
#pragma warning restore CA1814 // Préférer les tableaux en escalier aux tableaux multidimensionnels
                                for (int y = 0; y < yrefs.GetLength(0); y++)
                                {
                                    if (rule.LowerYear <= yrefs[y, 0] && rule.HighYear >= yrefs[y, 0] && rule.Month == yrefs[y, 1] && TzUtilities.IsYearType(yrefs[y, 0], rule.YearType))
                                    {
                                        TimeSpan stdoff = GetLastSaveOffset(yrefs[y, 0], rules, rule, temp.StartZone, temp.GmtOffset);
                                        DateTime date   = TzUtilities.GetDateTime(rule, yrefs[y, 0], temp.GmtOffset, stdoff, DateTimeKind.Local);
                                        if (date.Year == year && date >= startTime && date <= endTime && !dates.Contains(date))
                                        {
                                            dates.Add(date);
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            return(dates.ToArray());
        }
예제 #6
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));
                }
            }
        }