diff options
Diffstat (limited to 'src/mscorlib/src/System/TimeZoneInfo.StringSerializer.cs')
-rw-r--r-- | src/mscorlib/src/System/TimeZoneInfo.StringSerializer.cs | 625 |
1 files changed, 625 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/TimeZoneInfo.StringSerializer.cs b/src/mscorlib/src/System/TimeZoneInfo.StringSerializer.cs new file mode 100644 index 0000000000..9c1d5c3502 --- /dev/null +++ b/src/mscorlib/src/System/TimeZoneInfo.StringSerializer.cs @@ -0,0 +1,625 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.Serialization; +using System.Text; + +namespace System +{ + public sealed partial class TimeZoneInfo + { + /// <summary> + /// Used to serialize and deserialize TimeZoneInfo objects based on the custom string serialization format. + /// </summary> + private struct StringSerializer + { + private enum State + { + Escaped = 0, + NotEscaped = 1, + StartOfToken = 2, + EndOfLine = 3 + } + + private readonly string _serializedText; + private int _currentTokenStartIndex; + private State _state; + + // the majority of the strings contained in the OS time zones fit in 64 chars + private const int InitialCapacityForString = 64; + private const char Esc = '\\'; + private const char Sep = ';'; + private const char Lhs = '['; + private const char Rhs = ']'; + private const string DateTimeFormat = "MM:dd:yyyy"; + private const string TimeOfDayFormat = "HH:mm:ss.FFF"; + + /// <summary> + /// Creates the custom serialized string representation of a TimeZoneInfo instance. + /// </summary> + public static string GetSerializedString(TimeZoneInfo zone) + { + StringBuilder serializedText = StringBuilderCache.Acquire(); + + // + // <_id>;<_baseUtcOffset>;<_displayName>;<_standardDisplayName>;<_daylightDispayName> + // + SerializeSubstitute(zone.Id, serializedText); + serializedText.Append(Sep); + serializedText.Append(zone.BaseUtcOffset.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + serializedText.Append(Sep); + SerializeSubstitute(zone.DisplayName, serializedText); + serializedText.Append(Sep); + SerializeSubstitute(zone.StandardName, serializedText); + serializedText.Append(Sep); + SerializeSubstitute(zone.DaylightName, serializedText); + serializedText.Append(Sep); + + AdjustmentRule[] rules = zone.GetAdjustmentRules(); + foreach (AdjustmentRule rule in rules) + { + serializedText.Append(Lhs); + serializedText.Append(rule.DateStart.ToString(DateTimeFormat, DateTimeFormatInfo.InvariantInfo)); + serializedText.Append(Sep); + serializedText.Append(rule.DateEnd.ToString(DateTimeFormat, DateTimeFormatInfo.InvariantInfo)); + serializedText.Append(Sep); + serializedText.Append(rule.DaylightDelta.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + serializedText.Append(Sep); + // serialize the TransitionTime's + SerializeTransitionTime(rule.DaylightTransitionStart, serializedText); + serializedText.Append(Sep); + SerializeTransitionTime(rule.DaylightTransitionEnd, serializedText); + serializedText.Append(Sep); + if (rule.BaseUtcOffsetDelta != TimeSpan.Zero) + { + // Serialize it only when BaseUtcOffsetDelta has a value to reduce the impact of adding rule.BaseUtcOffsetDelta + serializedText.Append(rule.BaseUtcOffsetDelta.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + serializedText.Append(Sep); + } + if (rule.NoDaylightTransitions) + { + // Serialize it only when NoDaylightTransitions is true to reduce the impact of adding rule.NoDaylightTransitions + serializedText.Append('1'); + serializedText.Append(Sep); + } + serializedText.Append(Rhs); + } + serializedText.Append(Sep); + + return StringBuilderCache.GetStringAndRelease(serializedText); + } + + /// <summary> + /// Instantiates a TimeZoneInfo from a custom serialized string. + /// </summary> + public static TimeZoneInfo GetDeserializedTimeZoneInfo(string source) + { + StringSerializer s = new StringSerializer(source); + + string id = s.GetNextStringValue(); + TimeSpan baseUtcOffset = s.GetNextTimeSpanValue(); + string displayName = s.GetNextStringValue(); + string standardName = s.GetNextStringValue(); + string daylightName = s.GetNextStringValue(); + AdjustmentRule[] rules = s.GetNextAdjustmentRuleArrayValue(); + + try + { + return new TimeZoneInfo(id, baseUtcOffset, displayName, standardName, daylightName, rules, disableDaylightSavingTime: false); + } + catch (ArgumentException ex) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData"), ex); + } + catch (InvalidTimeZoneException ex) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData"), ex); + } + } + + private StringSerializer(string str) + { + _serializedText = str; + _currentTokenStartIndex = 0; + _state = State.StartOfToken; + } + + /// <summary> + /// Appends the String to the StringBuilder with all of the reserved chars escaped. + /// + /// ";" -> "\;" + /// "[" -> "\[" + /// "]" -> "\]" + /// "\" -> "\\" + /// </summary> + private static void SerializeSubstitute(string text, StringBuilder serializedText) + { + foreach (char c in text) + { + if (c == Esc || c == Lhs || c == Rhs || c == Sep) + { + serializedText.Append('\\'); + } + serializedText.Append(c); + } + } + + /// <summary> + /// Helper method to serialize a TimeZoneInfo.TransitionTime object. + /// </summary> + private static void SerializeTransitionTime(TransitionTime time, StringBuilder serializedText) + { + serializedText.Append(Lhs); + serializedText.Append(time.IsFixedDateRule ? '1' : '0'); + serializedText.Append(Sep); + serializedText.Append(time.TimeOfDay.ToString(TimeOfDayFormat, DateTimeFormatInfo.InvariantInfo)); + serializedText.Append(Sep); + serializedText.Append(time.Month.ToString(CultureInfo.InvariantCulture)); + serializedText.Append(Sep); + if (time.IsFixedDateRule) + { + serializedText.Append(time.Day.ToString(CultureInfo.InvariantCulture)); + serializedText.Append(Sep); + } + else + { + serializedText.Append(time.Week.ToString(CultureInfo.InvariantCulture)); + serializedText.Append(Sep); + serializedText.Append(((int)time.DayOfWeek).ToString(CultureInfo.InvariantCulture)); + serializedText.Append(Sep); + } + serializedText.Append(Rhs); + } + + /// <summary> + /// Helper function to determine if the passed in string token is allowed to be preceeded by an escape sequence token. + /// </summary> + private static void VerifyIsEscapableCharacter(char c) + { + if (c != Esc && c != Sep && c != Lhs && c != Rhs) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidEscapeSequence", c)); + } + } + + /// <summary> + /// Helper function that reads past "v.Next" data fields. Receives a "depth" parameter indicating the + /// current relative nested bracket depth that _currentTokenStartIndex is at. The function ends + /// successfully when "depth" returns to zero (0). + /// </summary> + private void SkipVersionNextDataFields(int depth /* starting depth in the nested brackets ('[', ']')*/) + { + if (_currentTokenStartIndex < 0 || _currentTokenStartIndex >= _serializedText.Length) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + State tokenState = State.NotEscaped; + + // walk the serialized text, building up the token as we go... + for (int i = _currentTokenStartIndex; i < _serializedText.Length; i++) + { + if (tokenState == State.Escaped) + { + VerifyIsEscapableCharacter(_serializedText[i]); + tokenState = State.NotEscaped; + } + else if (tokenState == State.NotEscaped) + { + switch (_serializedText[i]) + { + case Esc: + tokenState = State.Escaped; + break; + + case Lhs: + depth++; + break; + case Rhs: + depth--; + if (depth == 0) + { + _currentTokenStartIndex = i + 1; + if (_currentTokenStartIndex >= _serializedText.Length) + { + _state = State.EndOfLine; + } + else + { + _state = State.StartOfToken; + } + return; + } + break; + + case '\0': + // invalid character + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + + default: + break; + } + } + } + + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + /// <summary> + /// Helper function that reads a string token from the serialized text. The function + /// updates <see cref="_currentTokenStartIndex"/> to point to the next token on exit. + /// Also <see cref="_state"/> is set to either <see cref="State.StartOfToken"/> or + /// <see cref="State.EndOfLine"/> on exit. + /// </summary> + private string GetNextStringValue() + { + // first verify the internal state of the object + if (_state == State.EndOfLine) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + if (_currentTokenStartIndex < 0 || _currentTokenStartIndex >= _serializedText.Length) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + State tokenState = State.NotEscaped; + StringBuilder token = StringBuilderCache.Acquire(InitialCapacityForString); + + // walk the serialized text, building up the token as we go... + for (int i = _currentTokenStartIndex; i < _serializedText.Length; i++) + { + if (tokenState == State.Escaped) + { + VerifyIsEscapableCharacter(_serializedText[i]); + token.Append(_serializedText[i]); + tokenState = State.NotEscaped; + } + else if (tokenState == State.NotEscaped) + { + switch (_serializedText[i]) + { + case Esc: + tokenState = State.Escaped; + break; + + case Lhs: + // '[' is an unexpected character + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + + case Rhs: + // ']' is an unexpected character + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + + case Sep: + _currentTokenStartIndex = i + 1; + if (_currentTokenStartIndex >= _serializedText.Length) + { + _state = State.EndOfLine; + } + else + { + _state = State.StartOfToken; + } + return StringBuilderCache.GetStringAndRelease(token); + + case '\0': + // invalid character + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + + default: + token.Append(_serializedText[i]); + break; + } + } + } + // + // we are at the end of the line + // + if (tokenState == State.Escaped) + { + // we are at the end of the serialized text but we are in an escaped state + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidEscapeSequence", string.Empty)); + } + + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + /// <summary> + /// Helper function to read a DateTime token. + /// </summary> + private DateTime GetNextDateTimeValue(string format) + { + string token = GetNextStringValue(); + DateTime time; + if (!DateTime.TryParseExact(token, format, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out time)) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + return time; + } + + /// <summary> + /// Helper function to read a TimeSpan token. + /// </summary> + private TimeSpan GetNextTimeSpanValue() + { + int token = GetNextInt32Value(); + try + { + return new TimeSpan(hours: 0, minutes: token, seconds: 0); + } + catch (ArgumentOutOfRangeException e) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData"), e); + } + } + + /// <summary> + /// Helper function to read an Int32 token. + /// </summary> + private int GetNextInt32Value() + { + string token = GetNextStringValue(); + int value; + if (!int.TryParse(token, NumberStyles.AllowLeadingSign /* "[sign]digits" */, CultureInfo.InvariantCulture, out value)) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + return value; + } + + /// <summary> + /// Helper function to read an AdjustmentRule[] token. + /// </summary> + private AdjustmentRule[] GetNextAdjustmentRuleArrayValue() + { + List<AdjustmentRule> rules = new List<AdjustmentRule>(1); + int count = 0; + + // individual AdjustmentRule array elements do not require semicolons + AdjustmentRule rule = GetNextAdjustmentRuleValue(); + while (rule != null) + { + rules.Add(rule); + count++; + + rule = GetNextAdjustmentRuleValue(); + } + + // the AdjustmentRule array must end with a separator + if (_state == State.EndOfLine) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + if (_currentTokenStartIndex < 0 || _currentTokenStartIndex >= _serializedText.Length) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + return count != 0 ? rules.ToArray() : null; + } + + /// <summary> + /// Helper function to read an AdjustmentRule token. + /// </summary> + private AdjustmentRule GetNextAdjustmentRuleValue() + { + // first verify the internal state of the object + if (_state == State.EndOfLine) + { + return null; + } + + if (_currentTokenStartIndex < 0 || _currentTokenStartIndex >= _serializedText.Length) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + // check to see if the very first token we see is the separator + if (_serializedText[_currentTokenStartIndex] == Sep) + { + return null; + } + + // verify the current token is a left-hand-side marker ("[") + if (_serializedText[_currentTokenStartIndex] != Lhs) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + _currentTokenStartIndex++; + + DateTime dateStart = GetNextDateTimeValue(DateTimeFormat); + DateTime dateEnd = GetNextDateTimeValue(DateTimeFormat); + TimeSpan daylightDelta = GetNextTimeSpanValue(); + TransitionTime daylightStart = GetNextTransitionTimeValue(); + TransitionTime daylightEnd = GetNextTransitionTimeValue(); + TimeSpan baseUtcOffsetDelta = TimeSpan.Zero; + int noDaylightTransitions = 0; + + // verify that the string is now at the right-hand-side marker ("]") ... + + if (_state == State.EndOfLine || _currentTokenStartIndex >= _serializedText.Length) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + // Check if we have baseUtcOffsetDelta in the serialized string and then deserialize it + if ((_serializedText[_currentTokenStartIndex] >= '0' && _serializedText[_currentTokenStartIndex] <= '9') || + _serializedText[_currentTokenStartIndex] == '-' || _serializedText[_currentTokenStartIndex] == '+') + { + baseUtcOffsetDelta = GetNextTimeSpanValue(); + } + + // Check if we have NoDaylightTransitions in the serialized string and then deserialize it + if ((_serializedText[_currentTokenStartIndex] >= '0' && _serializedText[_currentTokenStartIndex] <= '1')) + { + noDaylightTransitions = GetNextInt32Value(); + } + + if (_state == State.EndOfLine || _currentTokenStartIndex >= _serializedText.Length) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + if (_serializedText[_currentTokenStartIndex] != Rhs) + { + // skip ahead of any "v.Next" data at the end of the AdjustmentRule + // + // FUTURE: if the serialization format is extended in the future then this + // code section will need to be changed to read the new fields rather + // than just skipping the data at the end of the [AdjustmentRule]. + SkipVersionNextDataFields(1); + } + else + { + _currentTokenStartIndex++; + } + + // create the AdjustmentRule from the deserialized fields ... + + AdjustmentRule rule; + try + { + rule = AdjustmentRule.CreateAdjustmentRule(dateStart, dateEnd, daylightDelta, daylightStart, daylightEnd, baseUtcOffsetDelta, noDaylightTransitions > 0); + } + catch (ArgumentException e) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData"), e); + } + + // finally set the state to either EndOfLine or StartOfToken for the next caller + if (_currentTokenStartIndex >= _serializedText.Length) + { + _state = State.EndOfLine; + } + else + { + _state = State.StartOfToken; + } + return rule; + } + + /// <summary> + /// Helper function to read a TransitionTime token. + /// </summary> + private TransitionTime GetNextTransitionTimeValue() + { + // first verify the internal state of the object + + if (_state == State.EndOfLine || + (_currentTokenStartIndex < _serializedText.Length && _serializedText[_currentTokenStartIndex] == Rhs)) + { + // + // we are at the end of the line or we are starting at a "]" character + // + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + if (_currentTokenStartIndex < 0 || _currentTokenStartIndex >= _serializedText.Length) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + // verify the current token is a left-hand-side marker ("[") + + if (_serializedText[_currentTokenStartIndex] != Lhs) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + _currentTokenStartIndex++; + + int isFixedDate = GetNextInt32Value(); + + if (isFixedDate != 0 && isFixedDate != 1) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + TransitionTime transition; + + DateTime timeOfDay = GetNextDateTimeValue(TimeOfDayFormat); + timeOfDay = new DateTime(1, 1, 1, timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); + + int month = GetNextInt32Value(); + + if (isFixedDate == 1) + { + int day = GetNextInt32Value(); + + try + { + transition = TransitionTime.CreateFixedDateRule(timeOfDay, month, day); + } + catch (ArgumentException e) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData"), e); + } + } + else + { + int week = GetNextInt32Value(); + int dayOfWeek = GetNextInt32Value(); + + try + { + transition = TransitionTime.CreateFloatingDateRule(timeOfDay, month, week, (DayOfWeek)dayOfWeek); + } + catch (ArgumentException e) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData"), e); + } + } + + // verify that the string is now at the right-hand-side marker ("]") ... + + if (_state == State.EndOfLine || _currentTokenStartIndex >= _serializedText.Length) + { + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + if (_serializedText[_currentTokenStartIndex] != Rhs) + { + // skip ahead of any "v.Next" data at the end of the AdjustmentRule + // + // FUTURE: if the serialization format is extended in the future then this + // code section will need to be changed to read the new fields rather + // than just skipping the data at the end of the [TransitionTime]. + SkipVersionNextDataFields(1); + } + else + { + _currentTokenStartIndex++; + } + + // check to see if the string is now at the separator (";") ... + bool sepFound = false; + if (_currentTokenStartIndex < _serializedText.Length && + _serializedText[_currentTokenStartIndex] == Sep) + { + // handle the case where we ended on a ";" + _currentTokenStartIndex++; + sepFound = true; + } + + if (!sepFound) + { + // we MUST end on a separator + throw new SerializationException(Environment.GetResourceString("Serialization_InvalidData")); + } + + // finally set the state to either EndOfLine or StartOfToken for the next caller + if (_currentTokenStartIndex >= _serializedText.Length) + { + _state = State.EndOfLine; + } + else + { + _state = State.StartOfToken; + } + return transition; + } + } + } +} |