/// <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> /// 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); }
/// <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 } } }
/// <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; }
/// <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()); }
//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)); } } }