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);
286 public static DateTimeOffset Parse (string input, IFormatProvider formatProvider, DateTimeStyles styles)
289 throw new ArgumentNullException ("input");
291 throw new NotImplementedException ();
294 public static DateTimeOffset ParseExact (string input, string format, IFormatProvider formatProvider)
296 return ParseExact (input, format, formatProvider, DateTimeStyles.AssumeLocal);
299 public static DateTimeOffset ParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles)
302 throw new ArgumentNullException ("format");
304 if (format == String.Empty)
305 throw new FormatException ("format is an empty string");
307 return ParseExact (input, new string [] {format}, formatProvider, styles);
310 public static DateTimeOffset ParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles)
313 throw new ArgumentNullException ("input");
315 if (input == String.Empty)
316 throw new FormatException ("input is an empty string");
319 throw new ArgumentNullException ("formats");
321 if (formats.Length == 0)
322 throw new FormatException ("Invalid format specifier");
324 if ((styles & DateTimeStyles.AssumeLocal) != 0 && (styles & DateTimeStyles.AssumeUniversal) != 0)
325 throw new ArgumentException ("styles parameter contains incompatible flags");
327 DateTimeOffset result;
328 if (!ParseExact (input, formats, DateTimeFormatInfo.GetInstance (formatProvider), styles, out result))
329 throw new FormatException ();
334 private static bool ParseExact (string input, string [] formats,
335 DateTimeFormatInfo dfi, DateTimeStyles styles, out DateTimeOffset ret)
337 foreach (string format in formats)
339 if (format == null || format == String.Empty)
340 throw new FormatException ("Invlid Format Sting");
342 DateTimeOffset result;
343 if (DoParse (input, format, false, out result, dfi, styles)) {
348 ret = DateTimeOffset.MinValue;
352 private static bool DoParse (string input,
355 out DateTimeOffset result,
356 DateTimeFormatInfo dfi,
357 DateTimeStyles styles)
359 if ((styles & DateTimeStyles.AllowLeadingWhite) != 0) {
360 format = format.TrimStart (null);
361 input = input.TrimStart (null);
364 if ((styles & DateTimeStyles.AllowTrailingWhite) != 0) {
365 format = format.TrimEnd (null);
366 input = input.TrimEnd (null);
369 bool allow_white_spaces = false;
370 if ((styles & DateTimeStyles.AllowInnerWhite) != 0)
371 allow_white_spaces = true;
373 bool useutc = false, use_invariants = false;
374 if (format.Length == 1)
375 format = DateTimeUtils.GetStandardPattern (format[0], dfi, out useutc, out use_invariants, true);
380 int partial_hour = -1; // for 'hh tt' formats
384 double fraction = -1;
386 TimeSpan offset = TimeSpan.MinValue;
388 result = DateTimeOffset.MinValue;
390 int fi = 0; //format iterator
391 int ii = 0; //input iterator
392 while (fi < format.Length) {
394 char ch = format [fi];
398 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
399 if (day != -1 || tokLen > 4)
403 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out day);
405 ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedDayNames : dfi.DayNames, allow_white_spaces, out temp_int);
408 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
409 ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out temp_int);
410 if (fraction >= 0 || tokLen > 7 || temp_int == -1)
412 fraction = (double)temp_int / Math.Pow (10, tokLen);
415 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
417 int read = ParseNumber (input, ii, tokLen, true, allow_white_spaces, out temp_int, out digits);
419 ii += ParseNumber (input, ii, digits, true, allow_white_spaces, out temp_int);
422 if (fraction >= 0 || tokLen > 7 || temp_int == -1)
424 fraction = (double)temp_int / Math.Pow (10, digits);
427 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
428 if (hour != -1 || tokLen > 2)
431 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out temp_int);
435 if (partial_hour == -1)
436 partial_hour = temp_int;
438 hour = partial_hour + temp_int;
441 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
442 if (hour != -1 || tokLen > 2)
445 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out hour);
448 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
449 if (minute != -1 || tokLen > 2)
452 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out minute);
455 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
456 if (month != -1 || tokLen > 4)
460 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out month);
462 ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedMonthNames : dfi.MonthNames, allow_white_spaces, out month);
468 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
469 if (second != -1 || tokLen > 2)
471 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out second);
474 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
475 if (hour != -1 || tokLen > 2)
478 ii += ParseEnum (input, ii,
479 tokLen == 1 ? new string[] {new string (dfi.AMDesignator[0], 1), new string (dfi.PMDesignator[0], 0)}
480 : new string[] {dfi.AMDesignator, dfi.PMDesignator},
481 allow_white_spaces, out temp_int);
485 if (partial_hour == -1)
486 partial_hour = temp_int * 12;
488 hour = partial_hour + temp_int * 12;
494 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
496 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out year);
498 year += DateTime.Now.Year - DateTime.Now.Year % 100;
499 } else if (tokLen <= 4) { // yyy and yyyy accept up to 5 digits with leading 0
501 ii += ParseNumber (input, ii, 5, false, allow_white_spaces, out year, out digit_parsed);
502 if (digit_parsed < tokLen || (digit_parsed > tokLen && (year / Math.Pow (10, digit_parsed - 1) < 1)))
505 ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out year);
508 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
509 if (offset != TimeSpan.MinValue || tokLen > 3)
512 int off_h, off_m = 0, sign;
514 ii += ParseEnum (input, ii, new string [] {"-", "+"}, allow_white_spaces, out sign);
515 ii += ParseNumber (input, ii, 2, tokLen != 1, false, out off_h);
517 ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int);
518 ii += ParseNumber (input, ii, 2, true, false, out off_m);
520 if (off_h == -1 || off_m == -1 || sign == -1 || temp_int == -1)
525 offset = new TimeSpan (sign * off_h, sign * off_m, 0);
529 ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int);
535 ii += ParseEnum (input, ii, new string [] {dfi.DateSeparator}, false, out temp_int);
546 ii += ParseChar (input, ii, ' ', false, out temp_int);
552 ii += ParseChar (input, ii, format [fi + 1], allow_white_spaces, out temp_int);
557 //Console.WriteLine ("un-parsed character: {0}", ch);
559 ii += ParseChar (input, ii, format [fi], allow_white_spaces, out temp_int);
567 //Console.WriteLine ("{0}-{1}-{2} {3}:{4} {5}", year, month, day, hour, minute, offset);
568 if (offset == TimeSpan.MinValue && (styles & DateTimeStyles.AssumeLocal) != 0)
569 offset = TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.Now);
571 if (offset == TimeSpan.MinValue && (styles & DateTimeStyles.AssumeUniversal) != 0)
572 offset = TimeSpan.Zero;
574 if (hour < 0) hour = 0;
575 if (minute < 0) minute = 0;
576 if (second < 0) second = 0;
577 if (fraction < 0) fraction = 0;
578 if (year > 0 && month > 0 && day > 0) {
579 result = new DateTimeOffset (year, month, day, hour, minute, second, (int) (1000 * fraction), offset);
580 if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
581 result = result.ToUniversalTime ();
588 private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result)
591 return ParseNumber (input, pos, digits, leading_zero, allow_leading_white, out result, out digit_parsed);
594 private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result, out int digit_parsed)
599 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++)
602 for (; pos < input.Length && Char.IsDigit (input[pos]) && digits > 0; pos ++, char_parsed++, digit_parsed++, digits --)
603 result = 10 * result + (byte) (input[pos] - '0');
605 if (leading_zero && digits > 0)
608 if (digit_parsed == 0)
614 private static int ParseEnum (string input, int pos, string [] enums, bool allow_leading_white, out int result)
618 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++)
621 for (int i = 0; i < enums.Length; i++)
622 if (input.Substring(pos).StartsWith (enums [i])) {
628 char_parsed += enums[result].Length;
633 private static int ParseChar (string input, int pos, char c, bool allow_leading_white, out int result)
637 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++, char_parsed++)
640 if (pos < input.Length && input[pos] == c){
648 public TimeSpan Subtract (DateTimeOffset value)
650 return UtcDateTime - value.UtcDateTime;
653 public DateTimeOffset Subtract (TimeSpan value)
658 public static TimeSpan operator - (DateTimeOffset left, DateTimeOffset right)
660 return left.Subtract (right);
663 public static DateTimeOffset operator - (DateTimeOffset dateTimeTz, TimeSpan timeSpan)
665 return dateTimeTz.Subtract (timeSpan);
668 public long ToFileTime ()
670 return UtcDateTime.ToFileTime ();
673 public DateTimeOffset ToLocalTime ()
675 return new DateTimeOffset (UtcDateTime.ToLocalTime (), TimeZone.CurrentTimeZone.GetUtcOffset (UtcDateTime.ToLocalTime ()));
678 public DateTimeOffset ToOffset (TimeSpan offset)
680 return new DateTimeOffset (dt - utc_offset + offset, offset);
683 public override string ToString ()
685 return ToString (null, null);
688 public string ToString (IFormatProvider formatProvider)
690 return ToString (null, formatProvider);
693 public string ToString (string format)
695 return ToString (format, null);
698 public string ToString (string format, IFormatProvider formatProvider)
700 DateTimeFormatInfo dfi = DateTimeFormatInfo.GetInstance(formatProvider);
702 if (format == null || format == String.Empty)
703 format = dfi.ShortDatePattern + " " + dfi.LongTimePattern + " zzz";
705 bool to_utc = false, use_invariant = false;
706 if (format.Length == 1) {
707 char fchar = format [0];
709 format = DateTimeUtils.GetStandardPattern (fchar, dfi, out to_utc, out use_invariant, true);
715 throw new FormatException ("format is not one of the format specifier characters defined for DateTimeFormatInfo");
717 return to_utc ? DateTimeUtils.ToString (UtcDateTime, TimeSpan.Zero, format, dfi) : DateTimeUtils.ToString (DateTime, Offset, format, dfi);
720 public DateTimeOffset ToUniversalTime ()
722 return new DateTimeOffset (UtcDateTime, TimeSpan.Zero);
725 public static bool TryParse (string input, out DateTimeOffset result)
728 result = Parse (input);
736 public static bool TryParse (string input, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
739 result = Parse (input, formatProvider, styles);
747 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
750 result = ParseExact (input, format, formatProvider, styles);
758 public static bool TryParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
761 result = ParseExact (input, formats, formatProvider, styles);
769 public DateTime Date {
770 get { return DateTime.SpecifyKind (dt.Date, DateTimeKind.Unspecified); }
773 public DateTime DateTime {
774 get { return DateTime.SpecifyKind (dt, DateTimeKind.Unspecified); }
778 get { return dt.Day; }
781 public DayOfWeek DayOfWeek {
782 get { return dt.DayOfWeek; }
785 public int DayOfYear {
786 get { return dt.DayOfYear; }
790 get { return dt.Hour; }
793 public DateTime LocalDateTime {
794 get { return UtcDateTime.ToLocalTime (); }
797 public int Millisecond {
798 get { return dt.Millisecond; }
802 get { return dt.Minute; }
806 get { return dt.Month; }
809 public static DateTimeOffset Now {
810 get { return new DateTimeOffset (DateTime.Now);}
813 public TimeSpan Offset {
814 get { return utc_offset; }
818 get { return dt.Second; }
822 get { return dt.Ticks; }
825 public TimeSpan TimeOfDay {
826 get { return dt.TimeOfDay; }
829 public DateTime UtcDateTime {
830 get { return DateTime.SpecifyKind (dt - utc_offset, DateTimeKind.Utc); }
833 public static DateTimeOffset UtcNow {
834 get { return new DateTimeOffset (DateTime.UtcNow); }
837 public long UtcTicks {
838 get { return UtcDateTime.Ticks; }
842 get { return dt.Year; }