2 * System.DateTimeOffset.cs
5 * Stephane Delcroix <stephane@delcroix.org>
6 * Marek Safar (marek.safar@gmail.com)
8 * Copyright (C) 2007 Novell, Inc (http://www.novell.com)
10 * Permission is hereby granted, free of charge, to any person obtaining
11 * a copy of this software and associated documentation files (the
12 * "Software"), to deal in the Software without restriction, including
13 * without limitation the rights to use, copy, modify, merge, publish,
14 * distribute, sublicense, and/or sell copies of the Software, and to
15 * permit persons to whom the Software is furnished to do so, subject to
16 * the following conditions:
18 * The above copyright notice and this permission notice shall be
19 * included in all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 #if NET_2_0 // Introduced by .NET 3.5 for 2.0 mscorlib
33 using System.Globalization;
34 using System.Runtime.InteropServices;
35 using System.Runtime.Serialization;
41 [StructLayout (LayoutKind.Auto)]
42 public struct DateTimeOffset : IComparable, IFormattable, ISerializable, IDeserializationCallback, IComparable<DateTimeOffset>, IEquatable<DateTimeOffset>
44 public static readonly DateTimeOffset MaxValue = new DateTimeOffset (DateTime.MaxValue, TimeSpan.Zero);
45 public static readonly DateTimeOffset MinValue = new DateTimeOffset (DateTime.MinValue, TimeSpan.Zero);
50 public DateTimeOffset (DateTime dateTime)
54 if (dateTime.Kind == DateTimeKind.Utc)
55 utc_offset = TimeSpan.Zero;
57 utc_offset = TimeZone.CurrentTimeZone.GetUtcOffset (dateTime);
59 if (UtcDateTime < DateTime.MinValue || UtcDateTime > DateTime.MaxValue)
60 throw new ArgumentOutOfRangeException ("The UTC date and time that results from applying the offset is earlier than MinValue or later than MaxValue.");
64 public DateTimeOffset (DateTime dateTime, TimeSpan offset)
66 if (dateTime.Kind == DateTimeKind.Utc && offset != TimeSpan.Zero)
67 throw new ArgumentException ("dateTime.Kind equals Utc and offset does not equal zero.");
69 if (dateTime.Kind == DateTimeKind.Local && offset != TimeZone.CurrentTimeZone.GetUtcOffset (dateTime))
70 throw new ArgumentException ("dateTime.Kind equals Local and offset does not equal the offset of the system's local time zone.");
72 if (offset.Ticks % TimeSpan.TicksPerMinute != 0)
73 throw new ArgumentException ("offset is not specified in whole minutes.");
75 if (offset < new TimeSpan (-14, 0 ,0) || offset > new TimeSpan (14, 0, 0))
76 throw new ArgumentOutOfRangeException ("offset is less than -14 hours or greater than 14 hours.");
81 if (UtcDateTime < DateTime.MinValue || UtcDateTime > DateTime.MaxValue)
82 throw new ArgumentOutOfRangeException ("The UtcDateTime property is earlier than MinValue or later than MaxValue.");
85 public DateTimeOffset (long ticks, TimeSpan offset) : this (new DateTime (ticks), offset)
89 public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, TimeSpan offset) :
90 this (new DateTime (year, month, day, hour, minute, second), offset)
94 public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, int millisecond, TimeSpan offset) :
95 this (new DateTime (year, month, day, hour, minute, second, millisecond), offset)
99 public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, TimeSpan offset) :
100 this (new DateTime (year, month, day, hour, minute, second, millisecond, calendar), offset)
104 public DateTimeOffset Add (TimeSpan timeSpan)
106 return new DateTimeOffset (dt.Add (timeSpan), utc_offset);
109 public DateTimeOffset AddDays (double days)
111 return new DateTimeOffset (dt.AddDays (days), utc_offset);
114 public DateTimeOffset AddHours (double hours)
116 return new DateTimeOffset (dt.AddHours (hours), utc_offset);
119 public static DateTimeOffset operator + (DateTimeOffset dateTimeTz, TimeSpan timeSpan)
121 return dateTimeTz.Add (timeSpan);
124 public DateTimeOffset AddMilliseconds (double milliseconds)
126 return new DateTimeOffset (dt.AddMilliseconds (milliseconds), utc_offset);
129 public DateTimeOffset AddMinutes (double minutes)
131 return new DateTimeOffset (dt.AddMinutes (minutes), utc_offset);
134 public DateTimeOffset AddMonths (int months)
136 return new DateTimeOffset (dt.AddMonths (months), utc_offset);
139 public DateTimeOffset AddSeconds (double seconds)
141 return new DateTimeOffset (dt.AddSeconds (seconds), utc_offset);
144 public DateTimeOffset AddTicks (long ticks)
146 return new DateTimeOffset (dt.AddTicks (ticks), utc_offset);
149 public DateTimeOffset AddYears (int years)
151 return new DateTimeOffset (dt.AddYears (years), utc_offset);
154 public static int Compare (DateTimeOffset first, DateTimeOffset second)
156 return first.CompareTo (second);
159 public int CompareTo (DateTimeOffset other)
161 return UtcDateTime.CompareTo (other.UtcDateTime);
164 int IComparable.CompareTo (object obj)
166 return CompareTo ((DateTimeOffset) obj);
169 public static bool operator == (DateTimeOffset left, DateTimeOffset right)
171 return left.Equals (right);
174 public bool Equals (DateTimeOffset other)
176 return UtcDateTime == other.UtcDateTime;
179 public override bool Equals (object obj)
181 if (obj is DateTimeOffset)
182 return UtcDateTime == ((DateTimeOffset) obj).UtcDateTime;
186 public static bool Equals (DateTimeOffset first, DateTimeOffset second)
188 return first.Equals (second);
191 public bool EqualsExact (DateTimeOffset other)
193 return dt == other.dt && utc_offset == other.utc_offset;
196 public static DateTimeOffset FromFileTime (long fileTime)
198 if (fileTime < 0 || fileTime > MaxValue.Ticks)
199 throw new ArgumentOutOfRangeException ("fileTime is less than zero or greater than DateTimeOffset.MaxValue.Ticks.");
201 return new DateTimeOffset (DateTime.FromFileTime (fileTime), TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.FromFileTime (fileTime)));
205 public override int GetHashCode ()
207 return dt.GetHashCode () ^ utc_offset.GetHashCode ();
210 [System.Security.Permissions.SecurityPermission (System.Security.Permissions.SecurityAction.LinkDemand, SerializationFormatter = true)]
211 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
214 throw new ArgumentNullException ("info");
215 // An example SOAP serialization on MSFT is the following, so field
216 // names "DateTime" and "OffsetMinutes":
217 // <SOAP-ENV:Envelope ...>
219 // <a1:DateTimeOffset id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/ns/System">
220 // <DateTime xsi:type="xsd:dateTime">2007-01-02T12:30:50.0000000+00:00</DateTime>
221 // <OffsetMinutes>0</OffsetMinutes>
222 // </a1:DateTimeOffset>
224 // </SOAP-ENV:Envelope>
225 DateTime dt0 = new DateTime (dt.Ticks).Subtract (utc_offset);
226 info.AddValue ("DateTime", dt0);
227 // MSFT BinaryFormatter output contains primitive code 6, i.e. Int16.
228 info.AddValue ("OffsetMinutes", (Int16)utc_offset.TotalMinutes);
231 [System.Security.Permissions.SecurityPermission (System.Security.Permissions.SecurityAction.LinkDemand, SerializationFormatter = true)]
232 private DateTimeOffset(SerializationInfo info, StreamingContext context)
234 DateTime dt0 = (DateTime)info.GetValue ("DateTime", typeof(DateTime));
235 Int16 totalMinutes = info.GetInt16 ("OffsetMinutes");
236 utc_offset = TimeSpan.FromMinutes(totalMinutes);
237 dt = dt0.Add(utc_offset);
240 public static bool operator > (DateTimeOffset left, DateTimeOffset right)
242 return left.UtcDateTime > right.UtcDateTime;
245 public static bool operator >= (DateTimeOffset left, DateTimeOffset right)
247 return left.UtcDateTime >= right.UtcDateTime;
250 public static implicit operator DateTimeOffset (DateTime dateTime)
252 return new DateTimeOffset (dateTime);
255 public static bool operator != (DateTimeOffset left, DateTimeOffset right)
257 return left.UtcDateTime != right.UtcDateTime;
260 public static bool operator < (DateTimeOffset left, DateTimeOffset right)
262 return left.UtcDateTime < right.UtcDateTime;
265 public static bool operator <= (DateTimeOffset left, DateTimeOffset right)
267 return left.UtcDateTime <= right.UtcDateTime;
271 void IDeserializationCallback.OnDeserialization (object sender)
275 public static DateTimeOffset Parse (string input)
277 return Parse (input, null);
280 public static DateTimeOffset Parse (string input, IFormatProvider formatProvider)
282 return Parse (input, formatProvider, DateTimeStyles.AllowWhiteSpaces);
285 public static DateTimeOffset Parse (string input, IFormatProvider formatProvider, DateTimeStyles styles)
288 throw new ArgumentNullException ("input");
292 Exception exception = null;
293 if (!DateTime.CoreParse (input, formatProvider, styles, out d, out dto, true, ref exception))
296 if (d.Ticks != 0 && dto.Ticks == 0)
297 throw new ArgumentOutOfRangeException ("The UTC representation falls outside the 1-9999 year range");
302 public static DateTimeOffset ParseExact (string input, string format, IFormatProvider formatProvider)
304 return ParseExact (input, format, formatProvider, DateTimeStyles.AssumeLocal);
307 public static DateTimeOffset ParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles)
310 throw new ArgumentNullException ("format");
312 if (format == String.Empty)
313 throw new FormatException ("format is an empty string");
315 return ParseExact (input, new string [] {format}, formatProvider, styles);
318 public static DateTimeOffset ParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles)
321 throw new ArgumentNullException ("input");
323 if (input == String.Empty)
324 throw new FormatException ("input is an empty string");
327 throw new ArgumentNullException ("formats");
329 if (formats.Length == 0)
330 throw new FormatException ("Invalid format specifier");
332 if ((styles & DateTimeStyles.AssumeLocal) != 0 && (styles & DateTimeStyles.AssumeUniversal) != 0)
333 throw new ArgumentException ("styles parameter contains incompatible flags");
335 DateTimeOffset result;
336 if (!ParseExact (input, formats, DateTimeFormatInfo.GetInstance (formatProvider), styles, out result))
337 throw new FormatException ("Invalid format string");
342 private static bool ParseExact (string input, string [] formats,
343 DateTimeFormatInfo dfi, DateTimeStyles styles, out DateTimeOffset ret)
345 foreach (string format in formats)
347 if (format == null || format == String.Empty)
348 throw new FormatException ("Invalid format string");
350 DateTimeOffset result;
351 if (DoParse (input, format, false, out result, dfi, styles)) {
356 ret = DateTimeOffset.MinValue;
360 private static bool DoParse (string input,
363 out DateTimeOffset result,
364 DateTimeFormatInfo dfi,
365 DateTimeStyles styles)
367 if ((styles & DateTimeStyles.AllowLeadingWhite) != 0) {
368 format = format.TrimStart (null);
369 input = input.TrimStart (null);
372 if ((styles & DateTimeStyles.AllowTrailingWhite) != 0) {
373 format = format.TrimEnd (null);
374 input = input.TrimEnd (null);
377 bool allow_white_spaces = false;
378 if ((styles & DateTimeStyles.AllowInnerWhite) != 0)
379 allow_white_spaces = true;
381 bool useutc = false, use_invariants = false;
382 if (format.Length == 1)
383 format = DateTimeUtils.GetStandardPattern (format[0], dfi, out useutc, out use_invariants, true);
388 int partial_hour = -1; // for 'hh tt' formats
392 double fraction = -1;
394 TimeSpan offset = TimeSpan.MinValue;
396 result = DateTimeOffset.MinValue;
398 int fi = 0; //format iterator
399 int ii = 0; //input iterator
400 while (fi < format.Length) {
402 char ch = format [fi];
406 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
407 if (day != -1 || tokLen > 4)
411 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out day);
413 ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedDayNames : dfi.DayNames, allow_white_spaces, out temp_int);
416 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
417 ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out temp_int);
418 if (fraction >= 0 || tokLen > 7 || temp_int == -1)
420 fraction = (double)temp_int / Math.Pow (10, tokLen);
423 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
425 int read = ParseNumber (input, ii, tokLen, true, allow_white_spaces, out temp_int, out digits);
427 ii += ParseNumber (input, ii, digits, true, allow_white_spaces, out temp_int);
430 if (fraction >= 0 || tokLen > 7 || temp_int == -1)
432 fraction = (double)temp_int / Math.Pow (10, digits);
435 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
436 if (hour != -1 || tokLen > 2)
439 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out temp_int);
443 if (partial_hour == -1)
444 partial_hour = temp_int;
446 hour = partial_hour + temp_int;
449 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
450 if (hour != -1 || tokLen > 2)
453 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out hour);
456 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
457 if (minute != -1 || tokLen > 2)
460 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out minute);
463 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
464 if (month != -1 || tokLen > 4)
468 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out month);
470 ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedMonthNames : dfi.MonthNames, allow_white_spaces, out month);
476 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
477 if (second != -1 || tokLen > 2)
479 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out second);
482 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
483 if (hour != -1 || tokLen > 2)
486 ii += ParseEnum (input, ii,
487 tokLen == 1 ? new string[] {new string (dfi.AMDesignator[0], 1), new string (dfi.PMDesignator[0], 0)}
488 : new string[] {dfi.AMDesignator, dfi.PMDesignator},
489 allow_white_spaces, out temp_int);
493 if (partial_hour == -1)
494 partial_hour = temp_int * 12;
496 hour = partial_hour + temp_int * 12;
502 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
504 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out year);
506 year += DateTime.Now.Year - DateTime.Now.Year % 100;
507 } else if (tokLen <= 4) { // yyy and yyyy accept up to 5 digits with leading 0
509 ii += ParseNumber (input, ii, 5, false, allow_white_spaces, out year, out digit_parsed);
510 if (digit_parsed < tokLen || (digit_parsed > tokLen && (year / Math.Pow (10, digit_parsed - 1) < 1)))
513 ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out year);
516 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
517 if (offset != TimeSpan.MinValue || tokLen > 3)
520 int off_h, off_m = 0, sign;
522 ii += ParseEnum (input, ii, new string [] {"-", "+"}, allow_white_spaces, out sign);
523 ii += ParseNumber (input, ii, 2, tokLen != 1, false, out off_h);
525 ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int);
526 ii += ParseNumber (input, ii, 2, true, false, out off_m);
528 if (off_h == -1 || off_m == -1 || sign == -1 || temp_int == -1)
533 offset = new TimeSpan (sign * off_h, sign * off_m, 0);
537 ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int);
543 ii += ParseEnum (input, ii, new string [] {dfi.DateSeparator}, false, out temp_int);
554 ii += ParseChar (input, ii, ' ', false, out temp_int);
560 ii += ParseChar (input, ii, format [fi + 1], allow_white_spaces, out temp_int);
565 //Console.WriteLine ("un-parsed character: {0}", ch);
567 ii += ParseChar (input, ii, format [fi], allow_white_spaces, out temp_int);
575 //Console.WriteLine ("{0}-{1}-{2} {3}:{4} {5}", year, month, day, hour, minute, offset);
576 if (offset == TimeSpan.MinValue && (styles & DateTimeStyles.AssumeLocal) != 0)
577 offset = TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.Now);
579 if (offset == TimeSpan.MinValue && (styles & DateTimeStyles.AssumeUniversal) != 0)
580 offset = TimeSpan.Zero;
582 if (hour < 0) hour = 0;
583 if (minute < 0) minute = 0;
584 if (second < 0) second = 0;
585 if (fraction < 0) fraction = 0;
586 if (year > 0 && month > 0 && day > 0) {
587 result = new DateTimeOffset (year, month, day, hour, minute, second, (int) (1000 * fraction), offset);
588 if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
589 result = result.ToUniversalTime ();
596 private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result)
599 return ParseNumber (input, pos, digits, leading_zero, allow_leading_white, out result, out digit_parsed);
602 private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result, out int digit_parsed)
607 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++)
610 for (; pos < input.Length && Char.IsDigit (input[pos]) && digits > 0; pos ++, char_parsed++, digit_parsed++, digits --)
611 result = 10 * result + (byte) (input[pos] - '0');
613 if (leading_zero && digits > 0)
616 if (digit_parsed == 0)
622 private static int ParseEnum (string input, int pos, string [] enums, bool allow_leading_white, out int result)
626 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++)
629 for (int i = 0; i < enums.Length; i++)
630 if (input.Substring(pos).StartsWith (enums [i])) {
636 char_parsed += enums[result].Length;
641 private static int ParseChar (string input, int pos, char c, bool allow_leading_white, out int result)
645 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++, char_parsed++)
648 if (pos < input.Length && input[pos] == c){
656 public TimeSpan Subtract (DateTimeOffset value)
658 return UtcDateTime - value.UtcDateTime;
661 public DateTimeOffset Subtract (TimeSpan value)
666 public static TimeSpan operator - (DateTimeOffset left, DateTimeOffset right)
668 return left.Subtract (right);
671 public static DateTimeOffset operator - (DateTimeOffset dateTimeTz, TimeSpan timeSpan)
673 return dateTimeTz.Subtract (timeSpan);
676 public long ToFileTime ()
678 return UtcDateTime.ToFileTime ();
681 public DateTimeOffset ToLocalTime ()
683 return new DateTimeOffset (UtcDateTime.ToLocalTime (), TimeZone.CurrentTimeZone.GetUtcOffset (UtcDateTime.ToLocalTime ()));
686 public DateTimeOffset ToOffset (TimeSpan offset)
688 return new DateTimeOffset (dt - utc_offset + offset, offset);
691 public override string ToString ()
693 return ToString (null, null);
696 public string ToString (IFormatProvider formatProvider)
698 return ToString (null, formatProvider);
701 public string ToString (string format)
703 return ToString (format, null);
706 public string ToString (string format, IFormatProvider formatProvider)
708 DateTimeFormatInfo dfi = DateTimeFormatInfo.GetInstance(formatProvider);
710 if (format == null || format == String.Empty)
711 format = dfi.ShortDatePattern + " " + dfi.LongTimePattern + " zzz";
713 bool to_utc = false, use_invariant = false;
714 if (format.Length == 1) {
715 char fchar = format [0];
717 format = DateTimeUtils.GetStandardPattern (fchar, dfi, out to_utc, out use_invariant, true);
723 throw new FormatException ("format is not one of the format specifier characters defined for DateTimeFormatInfo");
725 return to_utc ? DateTimeUtils.ToString (UtcDateTime, TimeSpan.Zero, format, dfi) : DateTimeUtils.ToString (DateTime, Offset, format, dfi);
728 public DateTimeOffset ToUniversalTime ()
730 return new DateTimeOffset (UtcDateTime, TimeSpan.Zero);
733 public static bool TryParse (string input, out DateTimeOffset result)
736 result = Parse (input);
744 public static bool TryParse (string input, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
747 result = Parse (input, formatProvider, styles);
755 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
758 result = ParseExact (input, format, formatProvider, styles);
766 public static bool TryParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
769 result = ParseExact (input, formats, formatProvider, styles);
777 public DateTime Date {
778 get { return DateTime.SpecifyKind (dt.Date, DateTimeKind.Unspecified); }
781 public DateTime DateTime {
782 get { return DateTime.SpecifyKind (dt, DateTimeKind.Unspecified); }
786 get { return dt.Day; }
789 public DayOfWeek DayOfWeek {
790 get { return dt.DayOfWeek; }
793 public int DayOfYear {
794 get { return dt.DayOfYear; }
798 get { return dt.Hour; }
801 public DateTime LocalDateTime {
802 get { return UtcDateTime.ToLocalTime (); }
805 public int Millisecond {
806 get { return dt.Millisecond; }
810 get { return dt.Minute; }
814 get { return dt.Month; }
817 public static DateTimeOffset Now {
818 get { return new DateTimeOffset (DateTime.Now);}
821 public TimeSpan Offset {
822 get { return utc_offset; }
826 get { return dt.Second; }
830 get { return dt.Ticks; }
833 public TimeSpan TimeOfDay {
834 get { return dt.TimeOfDay; }
837 public DateTime UtcDateTime {
838 get { return DateTime.SpecifyKind (dt - utc_offset, DateTimeKind.Utc); }
841 public static DateTimeOffset UtcNow {
842 get { return new DateTimeOffset (DateTime.UtcNow); }
845 public long UtcTicks {
846 get { return UtcDateTime.Ticks; }
850 get { return dt.Year; }