2 * System.DateTimeOffset
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 public int CompareTo (object other)
166 return CompareTo ((DateTimeOffset) other);
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 other)
181 return UtcDateTime == ((DateTimeOffset) other).UtcDateTime;
184 public static bool Equals (DateTimeOffset first, DateTimeOffset second)
186 return first.Equals (second);
189 public bool EqualsExact (DateTimeOffset other)
191 return dt == other.dt && utc_offset == other.utc_offset;
194 public static DateTimeOffset FromFileTime (long fileTime)
196 if (fileTime < 0 || fileTime > MaxValue.Ticks)
197 throw new ArgumentOutOfRangeException ("fileTime is less than zero or greater than DateTimeOffset.MaxValue.Ticks.");
199 return new DateTimeOffset (DateTime.FromFileTime (fileTime), TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.FromFileTime (fileTime)));
203 public override int GetHashCode ()
205 return dt.GetHashCode () ^ utc_offset.GetHashCode ();
208 void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
211 throw new ArgumentNullException ("info");
213 info.AddValue ("datetime", dt);
214 info.AddValue ("offset", utc_offset);
217 public static bool operator > (DateTimeOffset left, DateTimeOffset right)
219 return left.UtcDateTime > right.UtcDateTime;
222 public static bool operator >= (DateTimeOffset left, DateTimeOffset right)
224 return left.UtcDateTime >= right.UtcDateTime;
227 public static implicit operator DateTimeOffset (DateTime dateTime)
229 return new DateTimeOffset (dateTime);
232 public static bool operator != (DateTimeOffset left, DateTimeOffset right)
234 return left.UtcDateTime != right.UtcDateTime;
237 public static bool operator < (DateTimeOffset left, DateTimeOffset right)
239 return left.UtcDateTime < right.UtcDateTime;
242 public static bool operator <= (DateTimeOffset left, DateTimeOffset right)
244 return left.UtcDateTime <= right.UtcDateTime;
248 void IDeserializationCallback.OnDeserialization (object sender)
252 public static DateTimeOffset Parse (string input)
254 return Parse (input, null);
257 public static DateTimeOffset Parse (string input, IFormatProvider formatProvider)
259 return Parse (input, formatProvider, DateTimeStyles.AllowWhiteSpaces);
263 public static DateTimeOffset Parse (string input, IFormatProvider formatProvider, DateTimeStyles styles)
266 throw new ArgumentNullException ("input");
268 throw new NotImplementedException ();
271 public static DateTimeOffset ParseExact (string input, string format, IFormatProvider formatProvider)
273 return ParseExact (input, format, formatProvider, DateTimeStyles.AssumeLocal);
276 public static DateTimeOffset ParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles)
279 throw new ArgumentNullException ("format");
281 if (format == String.Empty)
282 throw new FormatException ("format is an empty string");
284 return ParseExact (input, new string [] {format}, formatProvider, styles);
287 public static DateTimeOffset ParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles)
290 throw new ArgumentNullException ("input");
292 if (input == String.Empty)
293 throw new FormatException ("input is an empty string");
296 throw new ArgumentNullException ("formats");
298 if (formats.Length == 0)
299 throw new FormatException ("Invalid format specifier");
301 if ((styles & DateTimeStyles.AssumeLocal) != 0 && (styles & DateTimeStyles.AssumeUniversal) != 0)
302 throw new ArgumentException ("styles parameter contains incompatible flags");
304 DateTimeOffset result;
305 if (!ParseExact (input, formats, DateTimeFormatInfo.GetInstance (formatProvider), styles, out result))
306 throw new FormatException ();
311 private static bool ParseExact (string input, string [] formats,
312 DateTimeFormatInfo dfi, DateTimeStyles styles, out DateTimeOffset ret)
314 foreach (string format in formats)
316 if (format == null || format == String.Empty)
317 throw new FormatException ("Invlid Format Sting");
319 DateTimeOffset result;
320 if (DoParse (input, format, false, out result, dfi, styles)) {
325 ret = DateTimeOffset.MinValue;
329 private static bool DoParse (string input,
332 out DateTimeOffset result,
333 DateTimeFormatInfo dfi,
334 DateTimeStyles styles)
336 if ((styles & DateTimeStyles.AllowLeadingWhite) != 0) {
337 format = format.TrimStart (null);
338 input = input.TrimStart (null);
341 if ((styles & DateTimeStyles.AllowTrailingWhite) != 0) {
342 format = format.TrimEnd (null);
343 input = input.TrimEnd (null);
346 bool allow_white_spaces = false;
347 if ((styles & DateTimeStyles.AllowInnerWhite) != 0)
348 allow_white_spaces = true;
350 bool useutc = false, use_invariants = false;
351 if (format.Length == 1)
352 format = DateTimeUtils.GetStandardPattern (format[0], dfi, out useutc, out use_invariants, true);
357 int partial_hour = -1; // for 'hh tt' formats
361 double fraction = -1;
363 TimeSpan offset = TimeSpan.MinValue;
365 result = DateTimeOffset.MinValue;
367 int fi = 0; //format iterator
368 int ii = 0; //input iterator
369 while (fi < format.Length) {
371 char ch = format [fi];
375 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
376 if (day != -1 || tokLen > 4)
380 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out day);
382 ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedDayNames : dfi.DayNames, allow_white_spaces, out temp_int);
385 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
386 ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out temp_int);
387 if (fraction >= 0 || tokLen > 7 || temp_int == -1)
389 fraction = (double)temp_int / Math.Pow (10, tokLen);
392 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
394 int read = ParseNumber (input, ii, tokLen, true, allow_white_spaces, out temp_int, out digits);
396 ii += ParseNumber (input, ii, digits, true, allow_white_spaces, out temp_int);
399 if (fraction >= 0 || tokLen > 7 || temp_int == -1)
401 fraction = (double)temp_int / Math.Pow (10, digits);
404 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
405 if (hour != -1 || tokLen > 2)
408 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out temp_int);
412 if (partial_hour == -1)
413 partial_hour = temp_int;
415 hour = partial_hour + temp_int;
418 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
419 if (hour != -1 || tokLen > 2)
422 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out hour);
425 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
426 if (minute != -1 || tokLen > 2)
429 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out minute);
432 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
433 if (month != -1 || tokLen > 4)
437 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out month);
439 ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedMonthNames : dfi.MonthNames, allow_white_spaces, out month);
445 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
446 if (second != -1 || tokLen > 2)
448 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out second);
451 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
452 if (hour != -1 || tokLen > 2)
455 ii += ParseEnum (input, ii,
456 tokLen == 1 ? new string[] {new string (dfi.AMDesignator[0], 1), new string (dfi.PMDesignator[0], 0)}
457 : new string[] {dfi.AMDesignator, dfi.PMDesignator},
458 allow_white_spaces, out temp_int);
462 if (partial_hour == -1)
463 partial_hour = temp_int * 12;
465 hour = partial_hour + temp_int * 12;
471 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
473 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out year);
475 year += DateTime.Now.Year - DateTime.Now.Year % 100;
476 } else if (tokLen <= 4) { // yyy and yyyy accept up to 5 digits with leading 0
478 ii += ParseNumber (input, ii, 5, false, allow_white_spaces, out year, out digit_parsed);
479 if (digit_parsed < tokLen || (digit_parsed > tokLen && (year / Math.Pow (10, digit_parsed - 1) < 1)))
482 ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out year);
485 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
486 if (offset != TimeSpan.MinValue || tokLen > 3)
489 int off_h, off_m = 0, sign;
491 ii += ParseEnum (input, ii, new string [] {"-", "+"}, allow_white_spaces, out sign);
492 ii += ParseNumber (input, ii, 2, tokLen != 1, false, out off_h);
494 ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int);
495 ii += ParseNumber (input, ii, 2, true, false, out off_m);
497 if (off_h == -1 || off_m == -1 || sign == -1 || temp_int == -1)
502 offset = new TimeSpan (sign * off_h, sign * off_m, 0);
506 ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int);
512 ii += ParseEnum (input, ii, new string [] {dfi.DateSeparator}, false, out temp_int);
523 ii += ParseChar (input, ii, ' ', false, out temp_int);
529 ii += ParseChar (input, ii, format [fi + 1], allow_white_spaces, out temp_int);
534 //Console.WriteLine ("un-parsed character: {0}", ch);
536 ii += ParseChar (input, ii, format [fi], allow_white_spaces, out temp_int);
544 //Console.WriteLine ("{0}-{1}-{2} {3}:{4} {5}", year, month, day, hour, minute, offset);
545 if (offset == TimeSpan.MinValue && (styles & DateTimeStyles.AssumeLocal) != 0)
546 offset = TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.Now);
548 if (offset == TimeSpan.MinValue && (styles & DateTimeStyles.AssumeUniversal) != 0)
549 offset = TimeSpan.Zero;
551 if (hour < 0) hour = 0;
552 if (minute < 0) minute = 0;
553 if (second < 0) second = 0;
554 if (fraction < 0) fraction = 0;
555 if (year > 0 && month > 0 && day > 0) {
556 result = new DateTimeOffset (year, month, day, hour, minute, second, (int) (1000 * fraction), offset);
557 if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
558 result = result.ToUniversalTime ();
565 private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result)
568 return ParseNumber (input, pos, digits, leading_zero, allow_leading_white, out result, out digit_parsed);
571 private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result, out int digit_parsed)
576 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++)
579 for (; pos < input.Length && Char.IsDigit (input[pos]) && digits > 0; pos ++, char_parsed++, digit_parsed++, digits --)
580 result = 10 * result + (byte) (input[pos] - '0');
582 if (leading_zero && digits > 0)
585 if (digit_parsed == 0)
591 private static int ParseEnum (string input, int pos, string [] enums, bool allow_leading_white, out int result)
595 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++)
598 for (int i = 0; i < enums.Length; i++)
599 if (input.Substring(pos).StartsWith (enums [i])) {
605 char_parsed += enums[result].Length;
610 private static int ParseChar (string input, int pos, char c, bool allow_leading_white, out int result)
614 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++, char_parsed++)
617 if (pos < input.Length && input[pos] == c){
625 public TimeSpan Subtract (DateTimeOffset other)
627 return UtcDateTime - other.UtcDateTime;
630 public DateTimeOffset Subtract (TimeSpan timeSpan)
632 return Add (-timeSpan);
635 public static TimeSpan operator - (DateTimeOffset left, DateTimeOffset right)
637 return left.Subtract (right);
640 public static DateTimeOffset operator - (DateTimeOffset dateTimeTz, TimeSpan timeSpan)
642 return dateTimeTz.Subtract (timeSpan);
645 public long ToFileTime ()
647 return UtcDateTime.ToFileTime ();
650 public DateTimeOffset ToLocalTime ()
652 return new DateTimeOffset (UtcDateTime.ToLocalTime (), TimeZone.CurrentTimeZone.GetUtcOffset (UtcDateTime.ToLocalTime ()));
655 public DateTimeOffset ToOffset (TimeSpan offset)
657 return new DateTimeOffset (dt - utc_offset + offset, offset);
660 public override string ToString ()
662 return ToString (null, null);
665 public string ToString (IFormatProvider formatProvider)
667 return ToString (null, formatProvider);
670 public string ToString (string format)
672 return ToString (format, null);
675 public string ToString (string format, IFormatProvider formatProvider)
677 DateTimeFormatInfo dfi = DateTimeFormatInfo.GetInstance(formatProvider);
679 if (format == null || format == String.Empty)
680 format = dfi.ShortDatePattern + " " + dfi.LongTimePattern + " zzz";
682 bool to_utc = false, use_invariant = false;
683 if (format.Length == 1) {
684 char fchar = format [0];
686 format = DateTimeUtils.GetStandardPattern (fchar, dfi, out to_utc, out use_invariant, true);
692 throw new FormatException ("format is not one of the format specifier characters defined for DateTimeFormatInfo");
694 return to_utc ? DateTimeUtils.ToString (UtcDateTime, TimeSpan.Zero, format, dfi) : DateTimeUtils.ToString (DateTime, Offset, format, dfi);
697 public DateTimeOffset ToUniversalTime ()
699 return new DateTimeOffset (UtcDateTime, TimeSpan.Zero);
702 public static bool TryParse (string input, out DateTimeOffset result)
705 result = Parse (input);
713 public static bool TryParse (string input, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
716 result = Parse (input, formatProvider, styles);
724 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
727 result = ParseExact (input, format, formatProvider, styles);
735 public static bool TryParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
738 result = ParseExact (input, formats, formatProvider, styles);
746 public DateTime Date {
747 get { return DateTime.SpecifyKind (dt.Date, DateTimeKind.Unspecified); }
750 public DateTime DateTime {
751 get { return DateTime.SpecifyKind (dt, DateTimeKind.Unspecified); }
755 get { return dt.Day; }
758 public DayOfWeek DayOfWeek {
759 get { return dt.DayOfWeek; }
762 public int DayOfYear {
763 get { return dt.DayOfYear; }
767 get { return dt.Hour; }
770 public DateTime LocalDateTime {
771 get { return UtcDateTime.ToLocalTime (); }
774 public int Millisecond {
775 get { return dt.Millisecond; }
779 get { return dt.Minute; }
783 get { return dt.Month; }
786 public static DateTimeOffset Now {
787 get { return new DateTimeOffset (DateTime.Now);}
790 public TimeSpan Offset {
791 get { return utc_offset; }
795 get { return dt.Second; }
799 get { return dt.Ticks; }
802 public TimeSpan TimeOfDay {
803 get { return dt.TimeOfDay; }
806 public DateTime UtcDateTime {
807 get { return DateTime.SpecifyKind (dt - utc_offset, DateTimeKind.Utc); }
810 public static DateTimeOffset UtcNow {
811 get { return new DateTimeOffset (DateTime.UtcNow); }
814 public long UtcTicks {
815 get { return UtcDateTime.Ticks; }
819 get { return dt.Year; }