/// <summary>
 /// Returns the next string from the token stream.
 /// </summary>
 /// <param name="tokens">The tokens to parse from.</param>
 /// <param name="name">The name of the expected value, for use in the exception if no value is available.</param>
 private string NextString(Tokens tokens, string name)
 {
     if (!tokens.HasNextToken)
     {
         throw new InvalidDataException($"Missing zone info token: {name}");
     }
     return tokens.NextToken(name);
 }
Exemple #2
0
 private static void AssertTokensEqual(IList<string> expectedTokens, Tokens tokens)
 {
     for (int i = 0; i < expectedTokens.Count; i++)
     {
         Assert.True(tokens.HasNextToken, "Not enough items in enumeration");
         var actual = tokens.NextToken(i.ToString());
         if (actual == null)
         {
             Assert.Fail("The enumeration item at index [" + i + "] is null");
         }
         Assert.AreEqual(expectedTokens[i], actual, "The enumeration item at index [" + i + "] is not correct");
     }
     Assert.False(tokens.HasNextToken, "Too many items in enumeration");
 }
 /// <summary>
 /// Returns the next token, which is optional, converting "-" to null.
 /// </summary>
 /// <param name="tokens">The tokens.</param>
 /// <param name="name">The name of the expected value, for use in the exception if no value is available.</param>
 private string NextOptional(Tokens tokens, string name) => ParserHelper.ParseOptional(NextString(tokens, name));
 /// <summary>
 /// Parses the next token as an offset.
 /// </summary>
 /// <param name="tokens">The tokens.</param>
 /// <param name="name">The name of the expected value, for use in the exception if no value is available.</param>
 private Offset NextOffset(Tokens tokens, string name) => ParserHelper.ParseOffset(NextString(tokens, name));
 /// <summary>
 /// Parses the next token as a month number (1-12).
 /// </summary>
 /// <param name="tokens">The tokens.</param>
 /// <param name="name">The name of the expected value, for use in the exception if no value is available.</param>
 private int NextMonth(Tokens tokens, string name)
 {
     var value = NextString(tokens, name);
     return ParseMonth(value);
 }
        /// <summary>
        ///   Parses a time zone definition and returns the Zone object.
        /// </summary>
        /// <remarks>
        ///   # GMTOFF RULES FORMAT [ UntilYear [ UntilMonth [ UntilDay [ UntilTime [ ZoneCharacter ] ] ] ] ]
        /// </remarks>
        /// <param name="name">The name of the zone being parsed.</param>
        /// <param name="tokens">The tokens to parse.</param>
        /// <returns>The Zone object.</returns>
        internal ZoneLine ParseZone(string name, Tokens tokens)
        {
            var offset = NextOffset(tokens, "Gmt Offset");
            var rules = NextOptional(tokens, "Rules");
            var format = NextString(tokens, "Format");
            int year = NextYear(tokens, Int32.MaxValue);
            
            if (tokens.HasNextToken)
            {
                var until = ParseDateTimeOfYear(tokens, false);
                return new ZoneLine(name, offset, rules, format, year, until);
            }

            return new ZoneLine(name, offset, rules, format, year, ZoneYearOffset.StartOfYear);
        }
        /// <summary>
        /// Parses a daylight savings rule and returns the Rule object.
        /// </summary>
        /// <remarks>
        /// # Rule    NAME    FROM    TO    TYPE    IN    ON    AT    SAVE    LETTER/S
        /// </remarks>
        /// <param name="tokens">The tokens to parse.</param>
        /// <returns>The Rule object.</returns>
        internal RuleLine ParseRule(Tokens tokens)
        {
            var name = NextString(tokens, "GetName");
            int fromYear = NextYear(tokens, 0);

            // This basically doesn't happen these days, but if we have any recurrent rules
            // which start at the dawn of time, make them effective from 1900. This matches
            // zic behaviour in the only cases of this that we've seen, e.g. the systemv rules
            // prior to 2001a.
            if (fromYear == int.MinValue)
            { 
                fromYear = 1900;
            }

            int toYear = NextYear(tokens, fromYear);
            if (toYear < fromYear)
            {
                throw new ArgumentException($"To year cannot be before the from year in a Rule: {toYear} < {fromYear}");
            }
            var type = NextOptional(tokens, "Type");
            var yearOffset = ParseDateTimeOfYear(tokens, true);
            var savings = NextOffset(tokens, "SaveMillis");
            var daylightSavingsIndicator = NextOptional(tokens, "LetterS");
            // The name of the zone recurrence is currently the name of the rule. Later (in ZoneRule.GetRecurrences)
            // it will be replaced with the formatted name. It's not ideal, but it avoids a lot of duplication.
            var recurrence = new ZoneRecurrence(name, savings, yearOffset, fromYear, toYear);
            return new RuleLine(recurrence, daylightSavingsIndicator, type);
        }
 /// <summary>
 /// Parses an alias link and returns the ZoneAlias object.
 /// </summary>
 /// <param name="tokens">The tokens to parse.</param>
 /// <returns>The ZoneAlias object.</returns>
 internal Tuple<string, string> ParseLink(Tokens tokens)
 {
     var existing = NextString(tokens, "Existing");
     var alias = NextString(tokens, "Alias");
     return Tuple.Create(existing, alias);
 }
 /// <summary>
 ///   Nexts the month.
 /// </summary>
 /// <param name="tokens">The tokens.</param>
 /// <param name="name">The name.</param>
 private int NextMonth(Tokens tokens, string name)
 {
     var value = NextString(tokens, name);
     int result = ParseMonth(value);
     return result == 0 ? 1 : result;
 }
Exemple #10
0
 /// <summary>
 /// Returns the next token, which is optional, converting "-" to null.
 /// </summary>
 /// <param name="tokens">The tokens.</param>
 /// <param name="name">The name of the expected value, for use in the exception if no value is available.</param>
 private string?NextOptional(Tokens tokens, string name) => ParserHelper.ParseOptional(NextString(tokens, name));
Exemple #11
0
 /// <summary>
 /// Parses the next token as an offset.
 /// </summary>
 /// <param name="tokens">The tokens.</param>
 /// <param name="name">The name of the expected value, for use in the exception if no value is available.</param>
 private Offset NextOffset(Tokens tokens, string name) => ParserHelper.ParseOffset(NextString(tokens, name));
Exemple #12
0
        /// <summary>
        /// Parses the next token as a month number (1-12).
        /// </summary>
        /// <param name="tokens">The tokens.</param>
        /// <param name="name">The name of the expected value, for use in the exception if no value is available.</param>
        private int NextMonth(Tokens tokens, string name)
        {
            var value = NextString(tokens, name);

            return(ParseMonth(value));
        }
Exemple #13
0
        /// <summary>
        /// Parses the ZoneYearOffset for a rule or zone. This is something like "3rd Sunday of October at 2am".
        /// </summary>
        /// <remarks>
        /// IN ON AT
        /// </remarks>
        /// <param name="tokens">The tokens to parse.</param>
        /// <param name="forRule">True if this is for a Rule line, in which case ON/AT are mandatory;
        /// false for a Zone line, in which case it's part of "until" and they're optional</param>
        /// <returns>The ZoneYearOffset object.</returns>
        internal ZoneYearOffset ParseDateTimeOfYear(Tokens tokens, bool forRule)
        {
            var mode      = ZoneYearOffset.StartOfYear.Mode;
            var timeOfDay = ZoneYearOffset.StartOfYear.TimeOfDay;

            int monthOfYear = NextMonth(tokens, "MonthOfYear");

            int  dayOfMonth       = 1;
            int  dayOfWeek        = 0;
            bool advanceDayOfWeek = false;
            bool addDay           = false;

            if (tokens.HasNextToken || forRule)
            {
                var on = NextString(tokens, "On");
                if (on.StartsWith("last", StringComparison.Ordinal))
                {
                    dayOfMonth = -1;
                    dayOfWeek  = ParseDayOfWeek(on.Substring(4));
                }
                else
                {
                    int index = on.IndexOf(">=", StringComparison.Ordinal);
                    if (index > 0)
                    {
                        dayOfMonth       = Int32.Parse(on.Substring(index + 2), CultureInfo.InvariantCulture);
                        dayOfWeek        = ParseDayOfWeek(on.Substring(0, index));
                        advanceDayOfWeek = true;
                    }
                    else
                    {
                        index = on.IndexOf("<=", StringComparison.Ordinal);
                        if (index > 0)
                        {
                            dayOfMonth = Int32.Parse(on.Substring(index + 2), CultureInfo.InvariantCulture);
                            dayOfWeek  = ParseDayOfWeek(on.Substring(0, index));
                        }
                        else
                        {
                            try
                            {
                                dayOfMonth = Int32.Parse(on, CultureInfo.InvariantCulture);
                                dayOfWeek  = 0;
                            }
                            catch (FormatException e)
                            {
                                throw new ArgumentException($"Unparsable ON token: {on}", e);
                            }
                        }
                    }
                }

                if (tokens.HasNextToken || forRule)
                {
                    var atTime = NextString(tokens, "AT");
                    if (!string.IsNullOrEmpty(atTime))
                    {
                        if (Char.IsLetter(atTime[atTime.Length - 1]))
                        {
                            char zoneCharacter = atTime[atTime.Length - 1];
                            mode   = ConvertModeCharacter(zoneCharacter);
                            atTime = atTime.Substring(0, atTime.Length - 1);
                        }
                        if (atTime == "24:00")
                        {
                            timeOfDay = LocalTime.Midnight;
                            addDay    = true;
                        }
                        // As of TZDB 2018f, Japan's fallback transitions occur at 25:00. We can't
                        // represent this entirely accurately, but this is as close as we can approximate it.
                        else if (atTime == "25:00")
                        {
                            timeOfDay = new LocalTime(1, 0);
                            addDay    = true;
                        }
                        else
                        {
                            timeOfDay = ParserHelper.ParseTime(atTime);
                        }
                    }
                }
            }
            return(new ZoneYearOffset(mode, monthOfYear, dayOfMonth, dayOfWeek, advanceDayOfWeek, timeOfDay, addDay));
        }
 /// <summary>
 /// Parses an alias link and returns the ZoneAlias object.
 /// </summary>
 /// <param name="tokens">The tokens to parse.</param>
 /// <returns>The ZoneAlias object.</returns>
 internal ZoneAlias ParseLink(Tokens tokens)
 {
     var existing = NextString(tokens, "Existing");
     var alias = NextString(tokens, "Alias");
     return new ZoneAlias(existing, alias);
 }
        /// <summary>
        /// Parses the ZoneYearOffset for a rule or zone. This is something like "3rd Sunday of October at 2am".
        /// </summary>
        /// <remarks>
        /// IN ON AT
        /// </remarks>
        /// <param name="tokens">The tokens to parse.</param>
        /// <param name="forRule">True if this is for a Rule line, in which case ON/AT are mandatory;
        /// false for a Zone line, in which case it's part of "until" and they're optional</param>
        /// <returns>The ZoneYearOffset object.</returns>
        internal ZoneYearOffset ParseDateTimeOfYear(Tokens tokens, bool forRule)
        {
            var mode = ZoneYearOffset.StartOfYear.Mode;
            var timeOfDay = ZoneYearOffset.StartOfYear.TimeOfDay;

            int monthOfYear = NextMonth(tokens, "MonthOfYear");

            int dayOfMonth = 1;
            int dayOfWeek = 0;
            bool advanceDayOfWeek = false;
            bool addDay = false;

            if (tokens.HasNextToken || forRule)
            {
                var on = NextString(tokens, "On");
                if (on.StartsWith("last", StringComparison.Ordinal))
                {
                    dayOfMonth = -1;
                    dayOfWeek = ParseDayOfWeek(on.Substring(4));
                }
                else
                {
                    int index = on.IndexOf(">=", StringComparison.Ordinal);
                    if (index > 0)
                    {
                        dayOfMonth = Int32.Parse(on.Substring(index + 2), CultureInfo.InvariantCulture);
                        dayOfWeek = ParseDayOfWeek(on.Substring(0, index));
                        advanceDayOfWeek = true;
                    }
                    else
                    {
                        index = on.IndexOf("<=", StringComparison.Ordinal);
                        if (index > 0)
                        {
                            dayOfMonth = Int32.Parse(on.Substring(index + 2), CultureInfo.InvariantCulture);
                            dayOfWeek = ParseDayOfWeek(on.Substring(0, index));
                        }
                        else
                        {
                            try
                            {
                                dayOfMonth = Int32.Parse(on, CultureInfo.InvariantCulture);
                                dayOfWeek = 0;
                            }
                            catch (FormatException e)
                            {
                                throw new ArgumentException($"Unparsable ON token: {on}", e);
                            }
                        }
                    }
                }

                if (tokens.HasNextToken || forRule)
                {
                    var atTime = NextString(tokens, "AT");
                    if (!string.IsNullOrEmpty(atTime))
                    {
                        if (Char.IsLetter(atTime[atTime.Length - 1]))
                        {
                            char zoneCharacter = atTime[atTime.Length - 1];
                            mode = ConvertModeCharacter(zoneCharacter);
                            atTime = atTime.Substring(0, atTime.Length - 1);
                        }
                        if (atTime == "24:00")
                        {
                            timeOfDay = LocalTime.Midnight;
                            addDay = true;
                        }
                        else
                        {
                            timeOfDay = ParserHelper.ParseTime(atTime);
                        }
                    }
                }
            }
            return new ZoneYearOffset(mode, monthOfYear, dayOfMonth, dayOfWeek, advanceDayOfWeek, timeOfDay, addDay);
        }
 /// <summary>
 /// Parses the next string from the token stream as a year.
 /// </summary>
 /// <param name="tokens">The tokens.</param>
 /// <param name="defaultValue">The default value to return if the year isn't specified.</param>
 private static int NextYear(Tokens tokens, int defaultValue)
 {
     int result = defaultValue;
     string text;
     if (tokens.TryNextToken(out text))
     {
         result = ParserHelper.ParseYear(text, defaultValue);
     }
     return result;
 }
 /// <summary>
 ///   Parses a daylight savings rule and returns the Rule object.
 /// </summary>
 /// <remarks>
 ///   # Rule    NAME    FROM    TO    TYPE    IN    ON    AT    SAVE    LETTER/S
 /// </remarks>
 /// <param name="tokens">The tokens to parse.</param>
 /// <returns>The Rule object.</returns>
 internal ZoneRule ParseRule(Tokens tokens)
 {
     var name = NextString(tokens, "GetName");
     int fromYear = NextYear(tokens, "FromYear", 0);
     int toYear = NextYear(tokens, "ToYear", fromYear);
     if (toYear < fromYear)
     {
         throw new ArgumentException("To year cannot be before the from year in a Rule: " + toYear + " < " + fromYear);
     }
     /* string type = */
     NextOptional(tokens, "Type");
     var yearOffset = ParseDateTimeOfYear(tokens, true);
     var savings = NextOffset(tokens, "SaveMillis");
     var letterS = NextOptional(tokens, "LetterS");
     var recurrence = new ZoneRecurrence(name, savings, yearOffset, fromYear, toYear);
     return new ZoneRule(recurrence, letterS);
 }