Merge pull request #656 from LogosBible/collection_lock
[mono.git] / mcs / class / corlib / System / DateTimeOffset.cs
1 /*
2  * System.DateTimeOffset.cs
3  *
4  * Author(s)
5  *      Stephane Delcroix <stephane@delcroix.org>
6  *      Marek Safar (marek.safar@gmail.com)
7  *
8  *  Copyright (C) 2007 Novell, Inc (http://www.novell.com) 
9  *  Copyright 2012 Xamarin, Inc (http://www.xamarin.com) 
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining
12  * a copy of this software and associated documentation files (the
13  * "Software"), to deal in the Software without restriction, including
14  * without limitation the rights to use, copy, modify, merge, publish,
15  * distribute, sublicense, and/or sell copies of the Software, and to
16  * permit persons to whom the Software is furnished to do so, subject to
17  * the following conditions:
18  * 
19  * The above copyright notice and this permission notice shall be
20  * included in all copies or substantial portions of the Software.
21  * 
22  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29  */
30
31
32 using System.Globalization;
33 using System.Runtime.InteropServices;
34 using System.Runtime.Serialization;
35 using System.Text;
36
37 namespace System
38 {
39         [Serializable]
40         [StructLayout (LayoutKind.Auto)]
41         public struct DateTimeOffset : IComparable, IFormattable, ISerializable, IDeserializationCallback, IComparable<DateTimeOffset>, IEquatable<DateTimeOffset>
42         {
43 #if MONOTOUCH
44                 static DateTimeOffset () {
45                         if (MonoTouchAOTHelper.FalseFlag) {
46                                 var comparer = new System.Collections.Generic.GenericComparer <DateTimeOffset> ();
47                                 var eqcomparer = new System.Collections.Generic.GenericEqualityComparer <DateTimeOffset> ();
48                         }
49                 }
50 #endif
51                 public static readonly DateTimeOffset MaxValue = new DateTimeOffset (DateTime.MaxValue, TimeSpan.Zero);
52                 public static readonly DateTimeOffset MinValue = new DateTimeOffset (DateTime.MinValue, TimeSpan.Zero);
53                 
54                 DateTime dt;
55                 TimeSpan utc_offset;
56         
57                 public DateTimeOffset (DateTime dateTime)
58                 {
59                         dt = dateTime;
60
61                         if (dateTime.Kind == DateTimeKind.Utc)
62                                 utc_offset = TimeSpan.Zero;
63                         else 
64                                 utc_offset = TimeZone.CurrentTimeZone.GetUtcOffset (dateTime);
65                                 
66                         if (UtcDateTime < DateTime.MinValue || UtcDateTime > DateTime.MaxValue)
67                                 throw new ArgumentOutOfRangeException ("The UTC date and time that results from applying the offset is earlier than MinValue or later than MaxValue.");
68
69                 }
70                 
71                 public DateTimeOffset (DateTime dateTime, TimeSpan offset)
72                 {
73                         if (dateTime.Kind == DateTimeKind.Utc && offset != TimeSpan.Zero)
74                                 throw new ArgumentException ("dateTime.Kind equals Utc and offset does not equal zero.");
75
76                         if (dateTime.Kind == DateTimeKind.Local && offset != TimeZone.CurrentTimeZone.GetUtcOffset (dateTime))
77                                 throw new ArgumentException ("dateTime.Kind equals Local and offset does not equal the offset of the system's local time zone.");
78
79                         if (offset.Ticks % TimeSpan.TicksPerMinute != 0)
80                                 throw new ArgumentException ("offset is not specified in whole minutes.");
81
82                         if (offset < new TimeSpan (-14, 0 ,0) || offset > new TimeSpan (14, 0, 0))
83                                 throw new ArgumentOutOfRangeException ("offset is less than -14 hours or greater than 14 hours.");
84
85                         dt = dateTime;
86                         utc_offset = offset;
87
88                         if (UtcDateTime < DateTime.MinValue || UtcDateTime > DateTime.MaxValue)
89                                 throw new ArgumentOutOfRangeException ("The UtcDateTime property is earlier than MinValue or later than MaxValue.");
90                 }
91
92                 public DateTimeOffset (long ticks, TimeSpan offset) : this (new DateTime (ticks), offset)
93                 {
94                 }
95
96                 public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, TimeSpan offset) : 
97                         this (new DateTime (year, month, day, hour, minute, second), offset)
98                 {
99                 }
100
101                 public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, int millisecond, TimeSpan offset) :
102                         this (new DateTime (year, month, day, hour, minute, second, millisecond), offset)
103                 {
104                 }
105
106                 public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, TimeSpan offset) :
107                         this (new DateTime (year, month, day, hour, minute, second, millisecond, calendar), offset)
108                 {
109                 }
110
111                 public DateTimeOffset Add (TimeSpan timeSpan)
112                 {
113                         return new DateTimeOffset (dt.Add (timeSpan).Ticks, utc_offset);
114                 }
115         
116                 public DateTimeOffset AddDays (double days)
117                 {
118                         return new DateTimeOffset (dt.AddDays (days).Ticks, utc_offset);        
119                 }
120                 
121                 public DateTimeOffset AddHours (double hours)
122                 {
123                         return new DateTimeOffset (dt.AddHours (hours).Ticks, utc_offset);
124                 }
125
126                 public static DateTimeOffset operator + (DateTimeOffset dateTimeOffset, TimeSpan timeSpan)
127                 {
128                         return dateTimeOffset.Add (timeSpan);
129                 }
130
131                 public DateTimeOffset AddMilliseconds (double milliseconds)
132                 {
133                         return new DateTimeOffset (dt.AddMilliseconds (milliseconds).Ticks, utc_offset);
134                 }
135
136                 public DateTimeOffset AddMinutes (double minutes)
137                 {
138                         return new DateTimeOffset (dt.AddMinutes (minutes).Ticks, utc_offset);  
139                 }
140
141                 public DateTimeOffset AddMonths (int months)
142                 {
143                         return new DateTimeOffset (dt.AddMonths (months).Ticks, utc_offset);
144                 }
145
146                 public DateTimeOffset AddSeconds (double seconds)
147                 {
148                         return new DateTimeOffset (dt.AddSeconds (seconds).Ticks, utc_offset);
149                 }
150
151                 public DateTimeOffset AddTicks (long ticks)
152                 {
153                         return new DateTimeOffset (dt.AddTicks (ticks).Ticks, utc_offset);      
154                 }
155
156                 public DateTimeOffset AddYears (int years)
157                 {
158                         return new DateTimeOffset (dt.AddYears (years).Ticks, utc_offset);
159                 }
160
161                 public static int Compare (DateTimeOffset first, DateTimeOffset second)
162                 {
163                         return first.CompareTo (second);        
164                 }
165
166                 public int CompareTo (DateTimeOffset other)
167                 {
168                         return UtcDateTime.CompareTo (other.UtcDateTime);
169                 }
170
171                 int IComparable.CompareTo (object obj)
172                 {
173                         return CompareTo ((DateTimeOffset) obj);
174                 }
175
176                 public static bool operator == (DateTimeOffset left, DateTimeOffset right)
177                 {
178                         return left.Equals (right);     
179                 }
180
181                 public bool Equals (DateTimeOffset other)
182                 {
183                         return UtcDateTime == other.UtcDateTime;
184                 }
185
186                 public override bool Equals (object obj)
187                 {
188                         if (obj is DateTimeOffset)
189                                 return UtcDateTime == ((DateTimeOffset) obj).UtcDateTime;
190                         return false;
191                 }
192
193                 public static bool Equals (DateTimeOffset first, DateTimeOffset second)
194                 {
195                         return first.Equals (second);   
196                 }
197
198                 public bool EqualsExact (DateTimeOffset other)
199                 {
200                         return dt == other.dt && utc_offset == other.utc_offset;        
201                 }
202
203                 public static DateTimeOffset FromFileTime (long fileTime)
204                 {
205                         if (fileTime < 0 || fileTime > MaxValue.Ticks)
206                                 throw new ArgumentOutOfRangeException ("fileTime is less than zero or greater than DateTimeOffset.MaxValue.Ticks.");
207                         
208                         return new DateTimeOffset (DateTime.FromFileTime (fileTime), TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.FromFileTime (fileTime)));
209
210                 }
211
212                 public override int GetHashCode ()
213                 {
214                         return dt.GetHashCode () ^ utc_offset.GetHashCode ();
215                 }
216
217                 [System.Security.Permissions.SecurityPermission (System.Security.Permissions.SecurityAction.LinkDemand, SerializationFormatter = true)]
218                 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
219                 {
220                         if (info == null)
221                                 throw new ArgumentNullException ("info");
222                         // An example SOAP serialization on MSFT is the following, so field 
223                         // names "DateTime" and "OffsetMinutes":
224                         //    <SOAP-ENV:Envelope ...>
225                         //    <SOAP-ENV:Body>
226                         //    <a1:DateTimeOffset id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/ns/System">
227                         //    <DateTime xsi:type="xsd:dateTime">2007-01-02T12:30:50.0000000+00:00</DateTime>
228                         //    <OffsetMinutes>0</OffsetMinutes>
229                         //    </a1:DateTimeOffset>
230                         //    </SOAP-ENV:Body>
231                         //    </SOAP-ENV:Envelope>
232                         DateTime dt0 = new DateTime (dt.Ticks).Subtract (utc_offset);
233                         info.AddValue ("DateTime", dt0);
234                         // MSFT BinaryFormatter output contains primitive code 6, i.e. Int16.
235                         info.AddValue ("OffsetMinutes", (Int16)utc_offset.TotalMinutes);
236                 }
237
238                 [System.Security.Permissions.SecurityPermission (System.Security.Permissions.SecurityAction.LinkDemand, SerializationFormatter = true)]
239                 private DateTimeOffset(SerializationInfo info, StreamingContext context)
240                 {
241                         DateTime dt0 = (DateTime)info.GetValue ("DateTime", typeof(DateTime));
242                         Int16 totalMinutes = info.GetInt16 ("OffsetMinutes");
243                         utc_offset = TimeSpan.FromMinutes(totalMinutes);
244                         dt = dt0.Add(utc_offset);
245                 }
246
247                 public static bool operator > (DateTimeOffset left, DateTimeOffset right)
248                 {
249                         return left.UtcDateTime > right.UtcDateTime;
250                 }                       
251
252                 public static bool operator >= (DateTimeOffset left, DateTimeOffset right)
253                 {
254                         return left.UtcDateTime >= right.UtcDateTime;
255                 }                       
256
257                 public static implicit operator DateTimeOffset (DateTime dateTime)
258                 {
259                         return new DateTimeOffset (dateTime);
260                 }
261
262                 public static bool operator != (DateTimeOffset left, DateTimeOffset right)
263                 {
264                         return left.UtcDateTime != right.UtcDateTime;
265                 }
266
267                 public static bool operator < (DateTimeOffset left, DateTimeOffset right)
268                 {
269                         return left.UtcDateTime < right.UtcDateTime;
270                 }
271                 
272                 public static bool operator <= (DateTimeOffset left, DateTimeOffset right)
273                 {
274                         return left.UtcDateTime <= right.UtcDateTime;
275                 }
276         
277                 [MonoTODO]
278                 void IDeserializationCallback.OnDeserialization (object sender)
279                 {
280                 }
281
282                 public static DateTimeOffset Parse (string input)
283                 {
284                         return Parse (input, null);
285                 }
286
287                 public static DateTimeOffset Parse (string input, IFormatProvider formatProvider)
288                 {
289                         return Parse (input, formatProvider, DateTimeStyles.AllowWhiteSpaces);
290                 }
291
292                 public static DateTimeOffset Parse (string input, IFormatProvider formatProvider, DateTimeStyles styles)
293                 {
294                         if (input == null)
295                                 throw new ArgumentNullException ("input");
296
297                         DateTime d;
298                         DateTimeOffset dto;
299                         Exception exception = null;
300                         try {
301                                 if (!DateTime.CoreParse (input, formatProvider, styles, out d, out dto, true, ref exception))
302                                         throw exception;
303                         } catch (ArgumentOutOfRangeException ex) {
304                                 throw new FormatException ("The UTC representation falls outside the 1-9999 year range", ex);
305                         }
306
307                         if (d.Ticks != 0 && dto.Ticks == 0)
308                                 throw new FormatException ("The UTC representation falls outside the 1-9999 year range");
309
310                         return dto;
311                 }
312
313                 public static DateTimeOffset ParseExact (string input, string format, IFormatProvider formatProvider)
314                 {
315                         return ParseExact (input, format, formatProvider, DateTimeStyles.AssumeLocal);
316                 }
317
318                 public static DateTimeOffset ParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles)
319                 {
320                         if (format == null)
321                                 throw new ArgumentNullException ("format");
322
323                         if (format == String.Empty)
324                                 throw new FormatException ("format is an empty string");
325
326                         return ParseExact (input, new string [] {format}, formatProvider, styles);
327                 }
328
329                 public static DateTimeOffset ParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles)
330                 {
331                         if (input == null)
332                                 throw new ArgumentNullException ("input");
333
334                         if (input == String.Empty)
335                                 throw new FormatException ("input is an empty string");
336
337                         if (formats == null)
338                                 throw new ArgumentNullException ("formats");
339
340                         if (formats.Length == 0)
341                                 throw new FormatException ("Invalid format specifier");
342
343                         if ((styles & DateTimeStyles.AssumeLocal) != 0 && (styles & DateTimeStyles.AssumeUniversal) != 0)
344                                 throw new ArgumentException ("styles parameter contains incompatible flags");
345
346                         DateTimeOffset result;
347                         if (!ParseExact (input, formats, DateTimeFormatInfo.GetInstance (formatProvider), styles, out result))
348                                 throw new FormatException ("Invalid format string");
349
350                         return result;
351                 }
352
353                 private static bool ParseExact (string input, string [] formats,
354                                 DateTimeFormatInfo dfi, DateTimeStyles styles, out DateTimeOffset ret)
355                 {
356                         foreach (string format in formats)
357                         {
358                                 if (format == null || format == String.Empty)
359                                         throw new FormatException ("Invalid format string");
360
361                                 DateTimeOffset result;
362                                 if (DoParse (input, format, false, out result, dfi, styles)) {
363                                         ret = result;
364                                         return true;
365                                 }
366                         }
367                         ret = DateTimeOffset.MinValue;
368                         return false;
369                 }
370
371                 private static bool DoParse (string input, 
372                                 string format,
373                                 bool exact,
374                                 out DateTimeOffset result,
375                                 DateTimeFormatInfo dfi,
376                                 DateTimeStyles styles)
377                 {
378                         if ((styles & DateTimeStyles.AllowLeadingWhite) != 0) {
379                                 format = format.TrimStart (null);
380                                 input = input.TrimStart (null);
381                         }
382
383                         if ((styles & DateTimeStyles.AllowTrailingWhite) != 0) {
384                                 format = format.TrimEnd (null);
385                                 input = input.TrimEnd (null);
386                         }
387
388                         bool allow_white_spaces = false;
389                         if ((styles & DateTimeStyles.AllowInnerWhite) != 0)
390                                 allow_white_spaces = true;
391
392                         result = DateTimeOffset.MinValue;
393                         
394                         bool useutc = false, use_invariants = false;
395                         if (format.Length == 1) {
396                                 format = DateTimeUtils.GetStandardPattern (format[0], dfi, out useutc, out use_invariants, true);
397                                 if (format == null)
398                                         return false;
399                         }
400
401                         int year = -1;
402                         int month = -1;
403                         int day = -1;
404                         int partial_hour = -1; // for 'hh tt' formats
405                         int hour = -1;
406                         int minute = -1;
407                         int second = -1;
408                         double fraction = -1;
409                         int temp_int = -1;
410                         TimeSpan offset = TimeSpan.MinValue;
411
412                         int fi = 0; //format iterator
413                         int ii = 0; //input iterator
414                         while (fi < format.Length) {
415                                 int tokLen;
416                                 char ch = format [fi];
417
418                                 switch (ch) {
419                                 case 'd':
420                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
421                                         if (day != -1 || tokLen > 4)
422                                                 return false;
423
424                                         if (tokLen <= 2)
425                                                 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out day);
426                                         else
427                                                 ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedDayNames : dfi.DayNames, allow_white_spaces, out temp_int); 
428                                         break;
429                                 case 'f':
430                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
431                                         ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out temp_int);
432                                         if (fraction >= 0 || tokLen > 7 || temp_int == -1)
433                                                 return false;
434                                         fraction = (double)temp_int / Math.Pow (10, tokLen);
435                                         break;
436                                 case 'F':
437                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
438                                         int digits;
439                                         int read = ParseNumber (input, ii, tokLen, true, allow_white_spaces, out temp_int, out digits);
440                                         if (temp_int == -1)
441                                                 ii += ParseNumber (input, ii, digits, true, allow_white_spaces, out temp_int);
442                                         else
443                                                 ii += read;
444                                         if (fraction >= 0 || tokLen > 7 || temp_int == -1)
445                                                 return false;   
446                                         fraction = (double)temp_int / Math.Pow (10, digits);    
447                                         break;
448                                 case 'h':
449                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
450                                         if (hour != -1 || tokLen > 2)
451                                                 return false;
452
453                                         ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out temp_int);
454                                         if (temp_int == -1)
455                                                 return false;
456
457                                         if (partial_hour == -1)
458                                                 partial_hour = temp_int;
459                                         else 
460                                                 hour = partial_hour + temp_int;
461                                         break;
462                                 case 'H':
463                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
464                                         if (hour != -1 || tokLen > 2)
465                                                 return false;
466
467                                         ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out hour);
468                                         break;
469                                 case 'm':
470                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
471                                         if (minute != -1 || tokLen > 2)
472                                                 return false;
473
474                                         ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out minute);
475                                         break;
476                                 case 'M':
477                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
478                                         if (month != -1 || tokLen > 4)
479                                                 return false;
480
481                                         if (tokLen <= 2)
482                                                 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out month);
483                                         else {
484                                                 ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedMonthNames : dfi.MonthNames, allow_white_spaces, out month);
485                                                 month += 1;
486                                         }
487
488                                         break;
489                                 case 's':
490                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
491                                         if (second != -1 || tokLen > 2)
492                                                 return false;
493                                         ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out second);
494                                         break;
495                                 case 't':
496                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
497                                         if (hour != -1 || tokLen > 2)
498                                                 return false;
499
500                                         ii += ParseEnum (input, ii,
501                                                          tokLen == 1 ? new string[] {new string (dfi.AMDesignator[0], 1), new string (dfi.PMDesignator[0], 0)} 
502                                                                      : new string[] {dfi.AMDesignator, dfi.PMDesignator},
503                                                          allow_white_spaces, out temp_int);
504                                         if (temp_int == -1)
505                                                 return false;
506
507                                         if (partial_hour == -1)
508                                                 partial_hour = temp_int * 12;
509                                         else
510                                                 hour = partial_hour + temp_int * 12;
511                                         break;
512                                 case 'y':
513                                         if (year != -1)
514                                                 return false;
515
516                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
517                                         if (tokLen <= 2) {
518                                                 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out year);
519                                                 if (year != -1)
520                                                         year = dfi.Calendar.ToFourDigitYear (year);
521                                         } else if (tokLen <= 4) { // yyy and yyyy accept up to 5 digits with leading 0
522                                                 int digit_parsed;
523                                                 ii += ParseNumber (input, ii, 5, false, allow_white_spaces, out year, out digit_parsed);
524                                                 if (digit_parsed < tokLen || (digit_parsed > tokLen && (year / Math.Pow (10, digit_parsed - 1) < 1)))
525                                                         return false;
526                                         } else
527                                                 ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out year);
528                                         break;
529
530                                         // The documentation is incorrect, they claim that K is the same as 'zz', but
531                                         // it actually allows the format to contain 4 digits for the offset
532                                 case 'K':
533                                         tokLen = 1;
534                                         int off_h, off_m = 0, sign;
535                                         temp_int = 0;
536                                         ii += ParseEnum (input, ii, new string [] {"-", "+"}, allow_white_spaces, out sign);
537                                         ii += ParseNumber (input, ii, 4, false, false, out off_h);
538                                         if (off_h == -1 || off_m == -1 || sign == -1)
539                                                 return false;
540
541                                         if (sign == 0)
542                                                 sign = -1;
543                                         offset = new TimeSpan (sign * off_h, sign * off_m, 0);
544                                         break;
545                                         
546                                 case 'z':
547                                         tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
548                                         if (offset != TimeSpan.MinValue || tokLen > 3)
549                                                 return false;
550
551                                         off_m = 0;
552                                         temp_int = 0;
553                                         ii += ParseEnum (input, ii, new string [] {"-", "+"}, allow_white_spaces, out sign);
554                                         ii += ParseNumber (input, ii, 2, tokLen != 1, false, out off_h);
555                                         if (tokLen == 3) {
556                                                 ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int);
557                                                 ii += ParseNumber (input, ii, 2, true, false, out off_m);
558                                         }
559                                         if (off_h == -1 || off_m == -1 || sign == -1)
560                                                 return false;
561
562                                         if (sign == 0)
563                                                 sign = -1;
564                                         offset = new TimeSpan (sign * off_h, sign * off_m, 0);
565                                         break;
566                                 case ':':
567                                         tokLen = 1;
568                                         ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int);
569                                         if (temp_int == -1)
570                                                 return false;
571                                         break;
572                                 case '/':
573                                         tokLen = 1;
574                                         ii += ParseEnum (input, ii, new string [] {dfi.DateSeparator}, false, out temp_int);
575                                         if (temp_int == -1)
576                                                 return false;
577                                         break;
578                                 case '%':
579                                         tokLen = 1;
580                                         if (fi != 0) 
581                                                 return false;
582                                         break;
583                                 case ' ':
584                                         tokLen = 1;
585                                         ii += ParseChar (input, ii, ' ', false, out temp_int);
586                                         if (temp_int == -1)
587                                                 return false;
588                                         break;
589                                 case '\\':
590                                         tokLen = 2;
591                                         ii += ParseChar (input, ii, format [fi + 1], allow_white_spaces, out temp_int);
592                                         if (temp_int == -1)
593                                                 return false;
594                                         break;
595                                 case '\'':
596                                 case '"':
597                                         tokLen = 1;
598                                         while (ii < input.Length) {
599                                                 var ftoken = format [fi + tokLen];
600                                                 ++tokLen;
601                                                 if (ftoken == format [fi]) {
602                                                         if (useutc && tokLen == 5 && input [ii - 3] == 'G' && input [ii - 2] == 'M' && input [ii - 1] == 'T') {
603                                                                 offset = TimeSpan.Zero;
604                                                         }
605
606                                                         break;
607                                                 }
608
609                                                 if (ftoken != input [ii++])
610                                                         return false;
611                                         }
612
613                                         break;
614                                 default:
615                                         //Console.WriteLine ("un-parsed character: {0}", ch);
616                                         tokLen = 1;
617                                         ii += ParseChar (input, ii, format [fi], allow_white_spaces, out temp_int);
618                                         if (temp_int == -1)
619                                                 return false;
620                                         break;
621                                 }
622                                 fi += tokLen;
623                         }
624
625                         //Console.WriteLine ("{0}-{1}-{2} {3}:{4} {5}", year, month, day, hour, minute, offset);
626                         if (offset == TimeSpan.MinValue) {
627                                 if ((styles & DateTimeStyles.AssumeUniversal) != 0) {
628                                         offset = TimeSpan.Zero;
629                                 } else if ((styles & DateTimeStyles.AssumeLocal) != 0) {
630                                         offset = use_invariants ?
631                                                 TimeSpan.Zero :
632                                                 TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.Now);
633                                 }
634                         }
635
636
637                         if (hour < 0)           hour = 0;
638                         if (minute < 0)         minute = 0;
639                         if (second < 0)         second = 0;
640                         if (fraction < 0)       fraction = 0;
641                         if (year > 0 && month > 0 && day > 0) {
642                                 result = new DateTimeOffset (year, month, day, hour, minute, second, 0, offset);
643                                 result = result.AddSeconds (fraction);
644                                 if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
645                                         result = result.ToUniversalTime ();
646                                 return true;
647                         }
648
649                         return false;
650                 }
651
652                 private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result)
653                 {
654                         int digit_parsed;
655                         return ParseNumber (input, pos, digits, leading_zero, allow_leading_white, out result, out digit_parsed);
656                 }
657
658                 private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result, out int digit_parsed)
659                 {
660                         int char_parsed = 0;
661                         digit_parsed = 0;
662                         result = 0;
663                         for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++)
664                                 char_parsed++;
665
666                         for (; pos < input.Length && Char.IsDigit (input[pos]) && digits > 0; pos ++, char_parsed++, digit_parsed++, digits --)
667                                 result = 10 * result + (byte) (input[pos] - '0');
668
669                         if (leading_zero && digits > 0)
670                                 result = -1;
671
672                         if (digit_parsed == 0)
673                                 result = -1;
674
675                         return char_parsed;
676                 }
677
678                 private static int ParseEnum (string input, int pos, string [] enums, bool allow_leading_white, out int result)
679                 {
680                         int char_parsed = 0;
681                         result = -1;
682                         for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++)
683                                 char_parsed ++;
684                         
685                         for (int i = 0; i < enums.Length; i++)
686                                 if (input.Substring(pos).StartsWith (enums [i])) {
687                                         result = i;
688                                         break;
689                                 }
690
691                         if (result >= 0)
692                                 char_parsed += enums[result].Length;
693
694                         return char_parsed;     
695                 }
696         
697                 private static int ParseChar (string input, int pos, char c, bool allow_leading_white, out int result)
698                 {
699                         int char_parsed = 0;
700                         result = -1;
701                         for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++, char_parsed++)
702                                 ;
703
704                         if (pos < input.Length && input[pos] == c){
705                                 result = (int) c;
706                                 char_parsed ++;
707                         }
708
709                         return char_parsed;
710                 }
711
712                 public TimeSpan Subtract (DateTimeOffset value)
713                 {
714                         return UtcDateTime - value.UtcDateTime;
715                 }
716
717                 public DateTimeOffset Subtract (TimeSpan value)
718                 {
719                         return Add (-value);
720                 }
721
722                 public static TimeSpan operator - (DateTimeOffset left, DateTimeOffset right)
723                 {
724                         return left.Subtract (right);
725                 }
726
727                 public static DateTimeOffset operator - (DateTimeOffset dateTimeOffset, TimeSpan timeSpan)
728                 {
729                         return dateTimeOffset.Subtract (timeSpan);      
730                 }
731
732                 public long ToFileTime ()
733                 {
734                         return UtcDateTime.ToFileTime ();
735                 }
736
737                 public DateTimeOffset ToLocalTime ()
738                 {
739                         return new DateTimeOffset (UtcDateTime.ToLocalTime (), TimeZone.CurrentTimeZone.GetUtcOffset (UtcDateTime.ToLocalTime ()));
740                 }
741
742                 public DateTimeOffset ToOffset (TimeSpan offset)
743                 {
744                         return new DateTimeOffset (dt - utc_offset + offset, offset);
745                 }
746
747                 public override string ToString ()
748                 {
749                         return ToString (null, null);
750                 }
751
752                 public string ToString (IFormatProvider formatProvider)
753                 {
754                         return ToString (null, formatProvider);
755                 }
756
757                 public string ToString (string format)
758                 {
759                         return ToString (format, null);
760                 }
761
762                 public string ToString (string format, IFormatProvider formatProvider)
763                 {
764                         DateTimeFormatInfo dfi = DateTimeFormatInfo.GetInstance(formatProvider);
765
766                         if (format == null || format == String.Empty)
767                                 format = dfi.ShortDatePattern + " " + dfi.LongTimePattern + " zzz";
768
769                         bool to_utc = false, use_invariant = false;
770                         if (format.Length == 1) {
771                                 char fchar = format [0];
772                                 try {
773                                         format = DateTimeUtils.GetStandardPattern (fchar, dfi, out to_utc, out use_invariant, true);
774                                 } catch {
775                                         format = null;
776                                 }
777                 
778                                 if (format == null)
779                                         throw new FormatException ("format is not one of the format specifier characters defined for DateTimeFormatInfo");
780                         }
781                         return to_utc ? DateTimeUtils.ToString (UtcDateTime, TimeSpan.Zero, format, dfi) : DateTimeUtils.ToString (DateTime, Offset, format, dfi);
782                 }
783
784                 public DateTimeOffset ToUniversalTime ()
785                 {
786                         return new DateTimeOffset (UtcDateTime, TimeSpan.Zero); 
787                 }
788
789                 public static bool TryParse (string input, out DateTimeOffset result)
790                 {
791                         try {
792                                 result = Parse (input);
793                                 return true;
794                         } catch {
795                                 result = MinValue;
796                                 return false;
797                         }
798                 }
799
800                 public static bool TryParse (string input, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
801                 {
802                         try {
803                                 result = Parse (input, formatProvider, styles);
804                                 return true;
805                         } catch {
806                                 result = MinValue;
807                                 return false;
808                         }       
809                 }
810
811                 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
812                 {
813                         try {
814                                 result = ParseExact (input, format, formatProvider, styles);
815                                 return true;
816                         } catch {
817                                 result = MinValue;
818                                 return false;
819                         }
820                 }
821
822                 public static bool TryParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
823                 {
824                         try {
825                                 result = ParseExact (input, formats, formatProvider, styles);
826                                 return true;
827                         } catch {
828                                 result = MinValue;
829                                 return false;
830                         }
831                 }
832
833                 public DateTime Date {
834                         get { return DateTime.SpecifyKind (dt.Date, DateTimeKind.Unspecified); }
835                 }
836
837                 public DateTime DateTime {
838                         get { return DateTime.SpecifyKind (dt, DateTimeKind.Unspecified); }
839                 }
840
841                 public int Day {
842                         get { return dt.Day; }
843                 }
844
845                 public DayOfWeek DayOfWeek {
846                         get { return dt.DayOfWeek; }
847                 }
848
849                 public int DayOfYear {
850                         get { return dt.DayOfYear; }
851                 }
852
853                 public int Hour {
854                         get { return dt.Hour; }
855                 }
856
857                 public DateTime LocalDateTime {
858                         get { return UtcDateTime.ToLocalTime (); }
859                 }
860
861                 public int Millisecond {
862                         get { return dt.Millisecond; }
863                 }
864
865                 public int Minute {
866                         get { return dt.Minute; }
867                 }
868
869                 public int Month {
870                         get { return dt.Month; }
871                 }
872
873                 public static DateTimeOffset Now {
874                         get { return new DateTimeOffset (DateTime.Now);}
875                 }
876
877                 public TimeSpan Offset {
878                         get { return utc_offset; }      
879                 }
880
881                 public int Second {
882                         get { return dt.Second; }
883                 }
884
885                 public long Ticks {
886                         get { return dt.Ticks; }        
887                 }
888
889                 public TimeSpan TimeOfDay {
890                         get { return dt.TimeOfDay; }
891                 }
892
893                 public DateTime UtcDateTime {
894                         get { return DateTime.SpecifyKind (dt - utc_offset, DateTimeKind.Utc); }        
895                 }
896                 
897                 public static DateTimeOffset UtcNow {
898                         get { return new DateTimeOffset (DateTime.UtcNow); }
899                 }
900
901                 public long UtcTicks {
902                         get { return UtcDateTime.Ticks; }
903                 }
904
905                 public int Year {
906                         get { return dt.Year; }
907                 }
908         }
909 }