/// <summary> /// Parses a single line of an TZDB zone info file. /// </summary> /// <remarks> /// <para> /// TZDB files have a simple line based structure. Each line defines one item. Comments /// start with a hash or pound sign (#) and continue to the end of the line. Blank lines are /// ignored. Of the remaining there are four line types which are determined by the first /// keyword on the line. /// </para> /// <para> /// A line beginning with the keyword <c>Link</c> defines an alias between one time zone and /// another. Both time zones use the same definition but have different names. /// </para> /// <para> /// A line beginning with the keyword <c>Rule</c> defines a daylight savings time /// calculation rule. /// </para> /// <para> /// A line beginning with the keyword <c>Zone</c> defines a time zone. /// </para> /// <para> /// A line beginning with leading whitespace (an empty keyword) defines another part of the /// preceeding time zone. As many lines as necessary to define the time zone can be listed, /// but they must all be together and only the first line can have a name. /// </para> /// </remarks> /// <param name="line">The line to parse.</param> /// <param name="database">The database to fill.</param> /// <return>The zone name just parsed, if any - so that it can be passed into the next call.</return> private string ParseLine(string line, string previousZone, TzdbDatabase database) { // Trim end-of-line comments int index = line.IndexOf("#", StringComparison.Ordinal); if (index >= 0) { line = line.Substring(0, index); } line = line.TrimEnd(); if (line.Length == 0) { // We can still continue with the previous zone return(previousZone); } // Okay, everything left in the line should be "real" now. var tokens = Tokens.Tokenize(line); var keyword = NextString(tokens, "Keyword"); switch (keyword) { case KeywordRule: database.AddRule(ParseRule(tokens)); return(null); case KeywordLink: var alias = ParseLink(tokens); database.AddAlias(alias.Item1, alias.Item2); return(null); case KeywordZone: var name = NextString(tokens, "GetName"); database.AddZone(ParseZone(name, tokens)); return(name); default: if (string.IsNullOrEmpty(keyword)) { if (previousZone == null) { throw new InvalidDataException("Zone continuation provided with no previous zone line"); } database.AddZone(ParseZone(previousZone, tokens)); return(previousZone); } else { throw new InvalidDataException($"Unexpected zone database keyword: {keyword}"); } } }
/// <summary> /// Parses a single line of an TZDB zone info file. /// </summary> /// <remarks> /// <para> /// TZDB files have a simple line based structure. Each line defines one item. Comments /// start with a hash or pound sign (#) and continue to the end of the line. Blank lines are /// ignored. Of the remaining there are four line types which are determined by the first /// keyword on the line. /// </para> /// <para> /// A line beginning with the keyword <c>Link</c> defines an alias between one time zone and /// another. Both time zones use the same definition but have different names. /// </para> /// <para> /// A line beginning with the keyword <c>Rule</c> defines a daylight savings time /// calculation rule. /// </para> /// <para> /// A line beginning with the keyword <c>Zone</c> defines a time zone. /// </para> /// <para> /// A line beginning with leading whitespace (an empty keyword) defines another part of the /// preceeding time zone. As many lines as necessary to define the time zone can be listed, /// but they must all be together and only the first line can have a name. /// </para> /// </remarks> /// <param name="line">The line to parse.</param> /// <param name="database">The database to fill.</param> internal void ParseLine(string line, TzdbDatabase database) { int index = line.IndexOf("#", StringComparison.Ordinal); if (index == 0) { return; } if (index > 0) { line = line.Substring(0, index - 1); } line = line.TrimEnd(); if (line.Length == 0) { return; } var tokens = Tokens.Tokenize(line); var keyword = NextString(tokens, "Keyword"); switch (keyword) { case KeywordRule: database.AddRule(ParseRule(tokens)); break; case KeywordLink: database.AddAlias(ParseLink(tokens)); break; case KeywordZone: var name = NextString(tokens, "GetName"); var namedZone = ParseZone(name, tokens); database.AddZone(namedZone); break; default: if (string.IsNullOrEmpty(keyword)) { var zone = ParseZone(string.Empty, tokens); database.AddZone(zone); } else { throw new InvalidDataException("Unexpected zone database keyword: " + keyword); } break; } }
/// <summary> /// Parses a single line of an TZDB zone info file. /// </summary> /// <remarks> /// <para> /// TZDB files have a simple line based structure. Each line defines one item. Comments /// start with a hash or pound sign (#) and continue to the end of the line. Blank lines are /// ignored. Of the remaining there are four line types which are determined by the first /// keyword on the line. /// </para> /// <para> /// A line beginning with the keyword <c>Link</c> defines an alias between one time zone and /// another. Both time zones use the same definition but have different names. /// </para> /// <para> /// A line beginning with the keyword <c>Rule</c> defines a daylight savings time /// calculation rule. /// </para> /// <para> /// A line beginning with the keyword <c>Zone</c> defines a time zone. /// </para> /// <para> /// A line beginning with leading whitespace (an empty keyword) defines another part of the /// preceeding time zone. As many lines as necessary to define the time zone can be listed, /// but they must all be together and only the first line can have a name. /// </para> /// </remarks> /// <param name="line">The line to parse.</param> /// <param name="database">The database to fill.</param> /// <return>The zone name just parsed, if any - so that it can be passed into the next call.</return> private string ParseLine(string line, string previousZone, TzdbDatabase database) { // Trim end-of-line comments int index = line.IndexOf("#", StringComparison.Ordinal); if (index >= 0) { line = line.Substring(0, index); } line = line.TrimEnd(); if (line.Length == 0) { // We can still continue with the previous zone return previousZone; } // Okay, everything left in the line should be "real" now. var tokens = Tokens.Tokenize(line); var keyword = NextString(tokens, "Keyword"); switch (keyword) { case KeywordRule: database.AddRule(ParseRule(tokens)); return null; case KeywordLink: var alias = ParseLink(tokens); database.AddAlias(alias.Item1, alias.Item2); return null; case KeywordZone: var name = NextString(tokens, "GetName"); database.AddZone(ParseZone(name, tokens)); return name; default: if (string.IsNullOrEmpty(keyword)) { if (previousZone == null) { throw new InvalidDataException("Zone continuation provided with no previous zone line"); } database.AddZone(ParseZone(previousZone, tokens)); return previousZone; } else { throw new InvalidDataException($"Unexpected zone database keyword: {keyword}"); } } }
/// <summary> /// Parses a single line of an TZDB zone info file. /// </summary> /// <remarks> /// <para> /// TZDB files have a simple line based structure. Each line defines one item. Comments /// start with a hash or pound sign (#) and continue to the end of the line. Blank lines are /// ignored. Of the remaining there are four line types which are determined by the first /// keyword on the line. /// </para> /// <para> /// A line beginning with the keyword <c>Link</c> defines an alias between one time zone and /// another. Both time zones use the same definition but have different names. /// </para> /// <para> /// A line beginning with the keyword <c>Rule</c> defines a daylight savings time /// calculation rule. /// </para> /// <para> /// A line beginning with the keyword <c>Zone</c> defines a time zone. /// </para> /// <para> /// A line beginning with leading whitespace (an empty keyword) defines another part of the /// preceeding time zone. As many lines as necessary to define the time zone can be listed, /// but they must all be together and only the first line can have a name. /// </para> /// </remarks> /// <param name="line">The line to parse.</param> /// <param name="database">The database to fill.</param> internal void ParseLine(string line, TzdbDatabase database) { int index = line.IndexOf("#", StringComparison.Ordinal); if (index == 0) { return; } if (index > 0) { line = line.Substring(0, index - 1); } line = line.TrimEnd(); if (line.Length == 0) { return; } var tokens = Tokens.Tokenize(line); var keyword = NextString(tokens, "Keyword"); switch (keyword) { case KeywordRule: database.AddRule(ParseRule(tokens)); break; case KeywordLink: database.AddAlias(ParseLink(tokens)); break; case KeywordZone: var name = NextString(tokens, "GetName"); var namedZone = ParseZone(name, tokens); database.AddZone(namedZone); break; default: if (string.IsNullOrEmpty(keyword)) { var zone = ParseZone(string.Empty, tokens); database.AddZone(zone); } else { throw new InvalidDataException($"Unexpected zone database keyword: {keyword}"); } break; } }