Merge pull request #1337 from RyanMelenaNoesis/master
[mono.git] / mcs / class / System.Core / System / TimeZoneInfo.Serialization.cs
1 /*
2  * System.TimeZoneInfo.Serialization
3  *
4  * Author(s)
5  *      Sasha Kotlyar <sasha@arktronic.com>
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining
8  * a copy of this software and associated documentation files (the
9  * "Software"), to deal in the Software without restriction, including
10  * without limitation the rights to use, copy, modify, merge, publish,
11  * distribute, sublicense, and/or sell copies of the Software, and to
12  * permit persons to whom the Software is furnished to do so, subject to
13  * the following conditions:
14  * 
15  * The above copyright notice and this permission notice shall be
16  * included in all copies or substantial portions of the Software.
17  * 
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25  */
26
27 #if INSIDE_CORLIB || (NET_3_5 && !NET_4_0 && !MOBILE)
28
29 using System.Collections.Generic;
30 using System.Globalization;
31 using System.Runtime.Serialization;
32 using System.Text;
33
34 namespace System
35 {
36 #if NET_4_0 || !INSIDE_CORLIB
37         public
38 #endif
39         partial class TimeZoneInfo
40         {
41                 public static TimeZoneInfo FromSerializedString (string source)
42                 {
43                         var input = new StringBuilder (source);
44                         var tzId = DeserializeString (ref input);
45                         var offset = DeserializeInt (ref input);
46                         var displayName = DeserializeString (ref input);
47                         var standardName = DeserializeString (ref input);
48                         var daylightName = DeserializeString (ref input);
49                         var rules = new List<TimeZoneInfo.AdjustmentRule> ();
50                         while (input [0] != ';') {
51                                 rules.Add (DeserializeAdjustmentRule (ref input));
52                         }
53                         var offsetSpan = TimeSpan.FromMinutes (offset);
54                         return TimeZoneInfo.CreateCustomTimeZone (tzId, offsetSpan, displayName, standardName, daylightName, rules.ToArray ());
55                 }
56
57                 public string ToSerializedString ()
58                 {
59                         var stb = new StringBuilder ();
60                         var daylightName = (string.IsNullOrEmpty(this.DaylightName) ? this.StandardName : this.DaylightName);
61                         stb.AppendFormat ("{0};{1};{2};{3};{4};", EscapeForSerialization (this.Id), (int)this.BaseUtcOffset.TotalMinutes,
62                                 EscapeForSerialization (this.DisplayName), EscapeForSerialization (this.StandardName), EscapeForSerialization (daylightName));
63
64                         if (this.SupportsDaylightSavingTime) {
65                                 foreach (var rule in this.GetAdjustmentRules()) {
66                                         var start = rule.DateStart.ToString ("MM:dd:yyyy", CultureInfo.InvariantCulture);
67                                         var end = rule.DateEnd.ToString ("MM:dd:yyyy", CultureInfo.InvariantCulture);
68                                         var delta = (int)rule.DaylightDelta.TotalMinutes;
69                                         var transitionStart = SerializeTransitionTime (rule.DaylightTransitionStart);
70                                         var transitionEnd = SerializeTransitionTime (rule.DaylightTransitionEnd);
71                                         stb.AppendFormat ("[{0};{1};{2};{3};{4};]", start, end, delta,
72                                                 transitionStart, transitionEnd);
73                                 }
74                         }
75
76                         stb.Append (";");
77                         return stb.ToString ();
78                 }
79
80                 private static TimeZoneInfo.AdjustmentRule DeserializeAdjustmentRule (ref StringBuilder input)
81                 {
82                         // Similar to: [01:01:0001;12:31:9999;60;[0;01:00:00;3;5;0;];[0;02:00:00;10;5;0;];]
83                         if (input [0] != '[')
84                                 throw new SerializationException ();
85                         input.Remove (0, 1); // [
86                         var dateStart = DeserializeDate (ref input);
87                         var dateEnd = DeserializeDate (ref input);
88                         var delta = DeserializeInt (ref input);
89                         var transitionStart = DeserializeTransitionTime (ref input);
90                         var transitionEnd = DeserializeTransitionTime (ref input);
91                         input.Remove (0, 1); // ]
92                         var deltaSpan = TimeSpan.FromMinutes (delta);
93                         return TimeZoneInfo.AdjustmentRule.CreateAdjustmentRule (dateStart, dateEnd, deltaSpan,
94                                 transitionStart, transitionEnd);
95                 }
96
97                 private static TimeZoneInfo.TransitionTime DeserializeTransitionTime (ref StringBuilder input)
98                 {
99                         if (input [0] != '[' || (input [1] != '0' && input [1] != '1') || input [2] != ';')
100                                 throw new SerializationException ();
101                         var rule = input [1];
102                         input.Remove (0, 3); // [#;
103                         var timeOfDay = DeserializeTime (ref input);
104                         var month = DeserializeInt (ref input);
105                         if (rule == '0') {
106                                 // Floating rule such as: [0;01:00:00;3;5;0;];
107                                 var week = DeserializeInt (ref input);
108                                 var dayOfWeek = DeserializeInt (ref input);
109                                 input.Remove (0, 2); // ];
110                                 return TimeZoneInfo.TransitionTime.CreateFloatingDateRule (timeOfDay, month, week, (DayOfWeek)dayOfWeek);
111                         }
112
113                         // Fixed rule such as: [1;02:15:59.999;6;2;];
114                         var day = DeserializeInt (ref input);
115                         input.Remove (0, 2); // ];
116                         return TimeZoneInfo.TransitionTime.CreateFixedDateRule (timeOfDay, month, day);
117                 }
118
119                 private static string DeserializeString (ref StringBuilder input)
120                 {
121                         var stb = new StringBuilder ();
122                         var isEscaped = false;
123                         int charCount;
124                         for (charCount = 0; charCount < input.Length; charCount++) {
125                                 var inChar = input [charCount];
126                                 if (isEscaped) {
127                                         isEscaped = false;
128                                         stb.Append (inChar);
129                                 } else if (inChar == '\\') {
130                                         isEscaped = true;
131                                         continue;
132                                 } else if (inChar == ';') {
133                                         break;
134                                 } else {
135                                         stb.Append (inChar);
136                                 }
137                         }
138                         input.Remove (0, charCount + 1);
139                         return stb.ToString ();
140                 }
141
142                 private static int DeserializeInt(ref StringBuilder input)
143                 {
144                         int charCount = 0;
145                         while(charCount++ < input.Length)
146                         {
147                                 if (input[charCount] == ';')
148                                         break;
149                         }
150                         int result;
151                         if(!int.TryParse(input.ToString(0, charCount), NumberStyles.Integer, CultureInfo.InvariantCulture, out result))
152                                 throw new SerializationException();
153                         input.Remove(0, charCount + 1);
154                         return result;
155                 }
156
157                 private static DateTime DeserializeDate (ref StringBuilder input)
158                 {
159                         var inChars = new char[11];
160                         input.CopyTo (0, inChars, 0, inChars.Length);
161                         DateTime result;
162                         if (!DateTime.TryParseExact (new string (inChars), "MM:dd:yyyy;", CultureInfo.InvariantCulture, DateTimeStyles.None, out result))
163                                 throw new SerializationException ();
164                         input.Remove (0, inChars.Length);
165                         return result;
166                 }
167
168                 private static DateTime DeserializeTime (ref StringBuilder input)
169                 {
170                         if (input [8] == ';') {
171                                 // Without milliseconds
172                                 var inChars = new char[9];
173                                 input.CopyTo (0, inChars, 0, inChars.Length);
174                                 DateTime result;
175                                 if (!DateTime.TryParseExact (new string (inChars), "HH:mm:ss;", CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result))
176                                         throw new SerializationException ();
177                                 input.Remove (0, inChars.Length);
178                                 return result;
179                         } else if (input [12] == ';') {
180                                 // With milliseconds
181                                 char[] inChars = new char[13];
182                                 input.CopyTo (0, inChars, 0, inChars.Length);
183                                 var inString = new string (inChars);
184                                 DateTime result;
185                                 if (!DateTime.TryParseExact (inString, "HH:mm:ss.fff;", CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result))
186                                         throw new SerializationException ();
187                                 input.Remove (0, inChars.Length);
188                                 return result;
189                         }
190                         throw new SerializationException ();
191                 }
192
193                 private static string EscapeForSerialization (string unescaped)
194                 {
195                         return unescaped.Replace (@"\", @"\\").Replace (";", "\\;");
196                 }
197
198                 private static string SerializeTransitionTime (TimeZoneInfo.TransitionTime transition)
199                 {
200                         string timeOfDay;
201                         if (transition.TimeOfDay.Millisecond > 0)
202                                 timeOfDay = transition.TimeOfDay.ToString ("HH:mm:ss.fff");
203                         else
204                                 timeOfDay = transition.TimeOfDay.ToString ("HH:mm:ss");
205
206                         if (transition.IsFixedDateRule) {
207                                 return string.Format ("[1;{0};{1};{2};]", timeOfDay, transition.Month, transition.Day);
208                         }
209
210                         return string.Format ("[0;{0};{1};{2};{3};]", timeOfDay, transition.Month,
211                                 transition.Week, (int)transition.DayOfWeek);
212                 }
213         }
214 }
215
216 #endif