/// <summary> /// Converts the String to a DateTime equivalent using the specified time zone /// </summary> /// <param name="s"></param> /// <param name="timeZone"></param> /// <param name="dateTimeKind"></param> /// <returns></returns> public static DateTime ToDateTime(string s, TzTimeZone timeZone, DateTimeKind dateTimeKind) { if (string.IsNullOrEmpty(s)) throw new ArgumentException(nameof(s)); if (timeZone == null) throw new ArgumentNullException(nameof(timeZone)); if (dateTimeKind == DateTimeKind.Unspecified) throw new ArgumentException(nameof(dateTimeKind)); System.Text.RegularExpressions.Match m = dateRegex.Match(s); if (m.Success) { DateTime result = new DateTime(Convert.ToInt32(m.Groups["YEAR"].Value), Convert.ToInt32(m.Groups["MONTH"].Value), Convert.ToInt32(m.Groups["DAY"].Value), Convert.ToInt32(m.Groups["HOUR"].Value), Convert.ToInt32(m.Groups["MINUTE"].Value), Convert.ToInt32(m.Groups["SEC"].Value), DateTimeKind.Local); if (m.Groups["MS"].Success) { result = result.AddMilliseconds(Convert.ToInt32(m.Groups["MS"])); } // Date is local, check return if (m.Groups["UTC"].Success) { if (dateTimeKind == DateTimeKind.Utc) return DateTime.SpecifyKind(result, DateTimeKind.Utc); else return timeZone.ToLocalTime(DateTime.SpecifyKind(result, DateTimeKind.Utc)); } else { // Date string is local, check offset if (m.Groups["SGN"].Success) { // Translate in utc if (m.Groups["SGN"].Value == "+") result = result.AddHours(-Convert.ToInt32(m.Groups["SH"].Value)).AddMinutes(-Convert.ToInt32(m.Groups["SM"].Value)); else result = result.AddHours(Convert.ToInt32(m.Groups["SH"].Value)).AddMinutes(Convert.ToInt32(m.Groups["SM"].Value)); if (dateTimeKind == DateTimeKind.Utc) return DateTime.SpecifyKind(result, DateTimeKind.Utc); else return timeZone.ToLocalTime(DateTime.SpecifyKind(result, DateTimeKind.Utc)); } else { // Zone unspecified, suppose to be local if (dateTimeKind == DateTimeKind.Utc) return timeZone.ToUniversalTime(result); else return result; } } } throw new ArgumentException(string.Format("{0} is not a valid format", s)); }
/// <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> /// Obtient 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="append">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> /// <remarks> /// The format of each line is: /// Zone NAME GMTOFF RULES FORMAT [UNTIL] /// </remarks> public static bool GetZone(List <string> zoneDescription, ref TzTimeZone zone, bool append) { if (!append && (zoneDescription.Count < 5 || zoneDescription.Count > 9)) { throw new ArgumentException("Wrong number of fields on zone line"); } if (append && (zoneDescription.Count < 3 || zoneDescription.Count > 7)) { throw new ArgumentException("Wrong number of fields on zone continuation line"); } return(GetZoneSub(zoneDescription, ref zone, append)); }
/// <summary> /// Returns the local time in the specified time zone that correspond to the specified date in the current time zone. /// </summary> /// <param name="datetime">A date and time in the current time zone</param> /// <param name="zone">The time zone in which the local time is converted.</param> /// <returns>A local <see cref="DateTime"/> in the specified time zone.</returns> public DateTime ToTimeZone(DateTime datetime, TzTimeZone zone) { if (zone == null) { throw new ArgumentNullException(nameof(zone)); } if (datetime.Kind == DateTimeKind.Unspecified) { throw new ArgumentException("Unspecified date time kind", nameof(datetime)); } if (datetime.Kind == DateTimeKind.Utc) { return(datetime); } DateTime utc = this.ToUniversalTime(datetime); return(zone.ToLocalTime(utc)); }
/// <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> /// Charge un ensemble de zone/rule/link /// </summary> /// <param name="reader"></param> /// <param name="filename"></param> private static void LoadFile(TextReader reader, string filename) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } if (filename == null) { throw new ArgumentNullException(nameof(filename)); } string line = null; bool zoneContinuation = false; TzTimeZone lastZone = null; int index = 0; while ((line = reader.ReadLine()) != null) { index++; if (string.IsNullOrEmpty(line)) { continue; } List <string> cutline = TzUtilities.GetFields(line, -1); if (cutline == null || cutline.Count == 0) { continue; } else { if (zoneContinuation) { zoneContinuation = TzUtilities.GetZone(cutline, ref lastZone, true); } else { switch (cutline[0]) { case RULE: Debug.Assert(cutline.Count == 10); Rule r = TzUtilities.GetRule(cutline); //if (!filename.StartsWith("solar") && r.StandardOffset != TimeSpan.Zero && r.StandardOffset != TimeSpan.FromHours(1)) // Console.WriteLine(r.ToString()); r.Filename = filename; r.LineNumber = index; if (!_rules.ContainsKey(r.Name)) { _rules.Add(r.Name, new List <Rule>()); } _rules[r.Name].Add(r); break; case LINK: Debug.Assert(cutline.Count == 3); _links.Add(cutline[2], cutline[1]); break; case ZONE: TzTimeZone z = new TzTimeZone(); z.Filename = filename; z.LineNumber = index; zoneContinuation = TzUtilities.GetZone(cutline, ref z, false); _zones.Add(z.Name, z); lastZone = z; break; } } } } }
/// <summary> /// Returns the local time in the specified time zone that correspond to the specified date in the current time zone. /// </summary> /// <param name="datetime">A date and time in the current time zone</param> /// <param name="zone">The time zone in which the local time is converted.</param> /// <returns>A local <see cref="DateTime"/> in the specified time zone.</returns> public DateTime ToTimeZone(DateTime datetime, TzTimeZone zone) { if (datetime.Kind == DateTimeKind.Unspecified) throw new ArgumentException("Unspecified date time kind", "datetime"); if (datetime.Kind == DateTimeKind.Utc) return datetime; DateTime utc = this.ToUniversalTime(datetime); return zone.ToLocalTime(utc); }
/// <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 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="append">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> /// <remarks> /// The format of each line is: /// Zone NAME GMTOFF RULES FORMAT [UNTIL] /// </remarks> public static bool GetZone(List<string> zoneDescription, ref TzTimeZone zone, bool append) { if (!append && (zoneDescription.Count < 5 || zoneDescription.Count > 9)) { throw new ArgumentException("Wrong number of fields on zone line"); } if (append && (zoneDescription.Count < 3 || zoneDescription.Count > 7)) { throw new ArgumentException("Wrong number of fields on zone continuation line"); } return GetZoneSub(zoneDescription, ref zone, append); }
/// <summary> /// Converts the String to a DateTime equivalent using the specified time zone /// </summary> /// <param name="s"></param> /// <param name="timeZone"></param> /// <param name="dateTimeKind"></param> /// <returns></returns> public static DateTime ToDateTime(string s, TzTimeZone timeZone, DateTimeKind dateTimeKind) { if (string.IsNullOrEmpty(s)) { throw new ArgumentNullException(nameof(s)); } if (timeZone == null) { throw new ArgumentNullException(nameof(timeZone)); } if (dateTimeKind == DateTimeKind.Unspecified) { throw new ArgumentException("Datetime kind unspecified", nameof(dateTimeKind)); } System.Text.RegularExpressions.Match m = dateRegex.Match(s); if (m.Success) { DateTime result = new DateTime(Convert.ToInt32(m.Groups["YEAR"].Value, CultureInfo.InvariantCulture), Convert.ToInt32(m.Groups["MONTH"].Value, CultureInfo.InvariantCulture), Convert.ToInt32(m.Groups["DAY"].Value, CultureInfo.InvariantCulture), Convert.ToInt32(m.Groups["HOUR"].Value, CultureInfo.InvariantCulture), Convert.ToInt32(m.Groups["MINUTE"].Value, CultureInfo.InvariantCulture), Convert.ToInt32(m.Groups["SEC"].Value, CultureInfo.InvariantCulture), DateTimeKind.Local); if (m.Groups["MS"].Success) { result = result.AddMilliseconds(Convert.ToInt32(m.Groups["MS"], CultureInfo.InvariantCulture)); } // Date is local, check return if (m.Groups["UTC"].Success) { if (dateTimeKind == DateTimeKind.Utc) { return(DateTime.SpecifyKind(result, DateTimeKind.Utc)); } else { return(timeZone.ToLocalTime(DateTime.SpecifyKind(result, DateTimeKind.Utc))); } } else { // Date string is local, check offset if (m.Groups["SGN"].Success) { // Translate in utc if (m.Groups["SGN"].Value == "+") { result = result.AddHours(-Convert.ToInt32(m.Groups["SH"].Value, CultureInfo.InvariantCulture)) .AddMinutes(-Convert.ToInt32(m.Groups["SM"].Value, CultureInfo.InvariantCulture)); } else { result = result.AddHours(Convert.ToInt32(m.Groups["SH"].Value, CultureInfo.InvariantCulture)) .AddMinutes(Convert.ToInt32(m.Groups["SM"].Value, CultureInfo.InvariantCulture)); } if (dateTimeKind == DateTimeKind.Utc) { return(DateTime.SpecifyKind(result, DateTimeKind.Utc)); } else { return(timeZone.ToLocalTime(DateTime.SpecifyKind(result, DateTimeKind.Utc))); } } else { // Zone unspecified, suppose to be local if (dateTimeKind == DateTimeKind.Utc) { return(timeZone.ToUniversalTime(result)); } else { return(result); } } } } throw new ArgumentException($"{s} is an invalid format"); }
/// <summary> /// Charge un ensemble de zone/rule/link /// </summary> /// <param name="reader"></param> /// <param name="filename"></param> private static void LoadFile(TextReader reader, string filename) { if (reader == null) throw new ArgumentNullException("reader"); string line = null; bool zoneContinuation = false; TzTimeZone lastZone = null; int index = 0; while ((line = reader.ReadLine()) != null) { index++; if (string.IsNullOrEmpty(line)) continue; List<string> cutline = TzUtilities.GetFields(line, -1); if (cutline == null || cutline.Count == 0) continue; else { if (zoneContinuation) { zoneContinuation = TzUtilities.GetZone(cutline, ref lastZone, true); } else { switch (cutline[0]) { case RULE: Debug.Assert(cutline.Count == 10); Rule r = TzUtilities.GetRule(cutline); //if (!filename.StartsWith("solar") && r.StandardOffset != TimeSpan.Zero && r.StandardOffset != TimeSpan.FromHours(1)) // Console.WriteLine(r.ToString()); r.Filename = filename; r.LineNumber = index; if (!_rules.ContainsKey(r.Name)) _rules.Add(r.Name, new List<Rule>()); _rules[r.Name].Add(r); break; case LINK: Debug.Assert(cutline.Count == 3); _links.Add(cutline[2], cutline[1]); break; case ZONE: TzTimeZone z = new TzTimeZone(); z.Filename = filename; z.LineNumber = index; zoneContinuation = TzUtilities.GetZone(cutline, ref z, false); _zones.Add(z.Name, z); lastZone = z; break; } } } } }