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 TimeSpan offset = TimeSpan.MinValue;
363 result = DateTimeOffset.MinValue;
365 int fi = 0; //format iterator
366 int ii = 0; //input iterator
367 while (fi < format.Length) {
369 char ch = format [fi];
373 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
374 if (day != -1 || tokLen > 4)
378 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out day);
380 ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedDayNames : dfi.DayNames, allow_white_spaces, out temp_int);
383 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
384 if (hour != -1 || tokLen > 2)
387 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out temp_int);
391 if (partial_hour == -1)
392 partial_hour = temp_int;
394 hour = partial_hour + temp_int;
397 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
398 if (hour != -1 || tokLen > 2)
401 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out hour);
404 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
405 if (minute != -1 || tokLen > 2)
408 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out minute);
411 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
412 if (month != -1 || tokLen > 4)
416 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out month);
418 ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedMonthNames : dfi.MonthNames, allow_white_spaces, out month);
424 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
425 if (hour != -1 || tokLen > 2)
428 ii += ParseEnum (input, ii,
429 tokLen == 1 ? new string[] {new string (dfi.AMDesignator[0], 1), new string (dfi.PMDesignator[0], 0)}
430 : new string[] {dfi.AMDesignator, dfi.PMDesignator},
431 allow_white_spaces, out temp_int);
435 if (partial_hour == -1)
436 partial_hour = temp_int * 12;
438 hour = partial_hour + temp_int * 12;
444 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
446 ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out year);
448 year += DateTime.Now.Year - DateTime.Now.Year % 100;
449 } else if (tokLen <= 4) { // yyy and yyyy accept up to 5 digits with leading 0
451 ii += ParseNumber (input, ii, 5, false, allow_white_spaces, out year, out digit_parsed);
452 if (digit_parsed < tokLen || (digit_parsed > tokLen && (year / (10 ^ (digit_parsed - 1)) < 1)))
455 ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out year);
458 tokLen = DateTimeUtils.CountRepeat (format, fi, ch);
459 if (offset != TimeSpan.MinValue || tokLen > 3)
462 int off_h, off_m = 0, sign;
464 ii += ParseEnum (input, ii, new string [] {"-", "+"}, allow_white_spaces, out sign);
465 ii += ParseNumber (input, ii, 2, tokLen != 1, false, out off_h);
467 ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int);
468 ii += ParseNumber (input, ii, 2, true, false, out off_m);
470 if (off_h == -1 || off_m == -1 || sign == -1 || temp_int == -1)
475 offset = new TimeSpan (sign * off_h, sign * off_m, 0);
479 ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int);
485 ii += ParseEnum (input, ii, new string [] {dfi.DateSeparator}, false, out temp_int);
496 ii += ParseEnum (input, ii, new string [] {" "}, false, out temp_int);
502 ii += ParseEnum (input, ii, new string [] {format.Substring (fi + 1, 1)}, allow_white_spaces, out temp_int);
507 Console.WriteLine ("un-parsed character: {0}", ch);
513 //Console.WriteLine ("{0}-{1}-{2} {3}:{4} {5}", year, month, day, hour, minute, offset);
514 if (offset == TimeSpan.MinValue && (styles & DateTimeStyles.AssumeLocal) != 0)
515 offset = TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.Now);
517 if (offset == TimeSpan.MinValue && (styles & DateTimeStyles.AssumeUniversal) != 0)
518 offset = TimeSpan.Zero;
520 if (hour < 0) hour = 0;
521 if (minute < 0) minute = 0;
523 if (year > 0 && month > 0 && day > 0) {
524 result = new DateTimeOffset (year, month, day, hour, minute, 0, offset);
525 if ((styles & DateTimeStyles.AdjustToUniversal) != 0)
526 result = result.ToUniversalTime ();
533 private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result)
536 return ParseNumber (input, pos, digits, leading_zero, allow_leading_white, out result, out digit_parsed);
539 private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result, out int digit_parsed)
544 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++, char_parsed++)
547 for (; pos < input.Length && Char.IsDigit (input[pos]) && digits > 0; pos ++, char_parsed++, digit_parsed++, digits --)
548 result = 10 * result + (byte) (input[pos] - '0');
550 if (leading_zero && digits > 0)
553 if (digit_parsed == 0)
559 private static int ParseEnum (string input, int pos, string [] enums, bool allow_leading_white, out int result)
563 for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++, char_parsed++)
566 for (int i = 0; i < enums.Length; i++)
567 if (input.Substring(pos).StartsWith (enums [i])) {
573 char_parsed += enums[result].Length;
578 public TimeSpan Subtract (DateTimeOffset other)
580 return UtcDateTime - other.UtcDateTime;
583 public DateTimeOffset Subtract (TimeSpan timeSpan)
585 return Add (-timeSpan);
588 public static TimeSpan operator - (DateTimeOffset left, DateTimeOffset right)
590 return left.Subtract (right);
593 public static DateTimeOffset operator - (DateTimeOffset dateTimeTz, TimeSpan timeSpan)
595 return dateTimeTz.Subtract (timeSpan);
598 public long ToFileTime ()
600 return UtcDateTime.ToFileTime ();
603 public DateTimeOffset ToLocalTime ()
605 return new DateTimeOffset (UtcDateTime.ToLocalTime (), TimeZone.CurrentTimeZone.GetUtcOffset (UtcDateTime.ToLocalTime ()));
608 public DateTimeOffset ToOffset (TimeSpan offset)
610 return new DateTimeOffset (dt - utc_offset + offset, offset);
613 public override string ToString ()
615 return ToString (null, null);
618 public string ToString (IFormatProvider formatProvider)
620 return ToString (null, formatProvider);
623 public string ToString (string format)
625 return ToString (format, null);
628 public string ToString (string format, IFormatProvider formatProvider)
630 DateTimeFormatInfo dfi = DateTimeFormatInfo.GetInstance(formatProvider);
632 if (format == null || format == String.Empty)
633 format = dfi.ShortDatePattern + " " + dfi.LongTimePattern + " zzz";
635 bool to_utc = false, use_invariant = false;
636 if (format.Length == 1) {
637 char fchar = format [0];
639 format = DateTimeUtils.GetStandardPattern (fchar, dfi, out to_utc, out use_invariant, true);
645 throw new FormatException ("format is not one of the format specifier characters defined for DateTimeFormatInfo");
647 return to_utc ? DateTimeUtils.ToString (UtcDateTime, TimeSpan.Zero, format, dfi) : DateTimeUtils.ToString (DateTime, Offset, format, dfi);
650 public DateTimeOffset ToUniversalTime ()
652 return new DateTimeOffset (UtcDateTime, TimeSpan.Zero);
655 public static bool TryParse (string input, out DateTimeOffset result)
658 result = Parse (input);
666 public static bool TryParse (string input, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
669 result = Parse (input, formatProvider, styles);
677 public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
680 result = ParseExact (input, format, formatProvider, styles);
688 public static bool TryParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result)
691 result = ParseExact (input, formats, formatProvider, styles);
699 public DateTime Date {
700 get { return DateTime.SpecifyKind (dt.Date, DateTimeKind.Unspecified); }
703 public DateTime DateTime {
704 get { return DateTime.SpecifyKind (dt, DateTimeKind.Unspecified); }
708 get { return dt.Day; }
711 public DayOfWeek DayOfWeek {
712 get { return dt.DayOfWeek; }
715 public int DayOfYear {
716 get { return dt.DayOfYear; }
720 get { return dt.Hour; }
723 public DateTime LocalDateTime {
724 get { return UtcDateTime.ToLocalTime (); }
727 public int Millisecond {
728 get { return dt.Millisecond; }
732 get { return dt.Minute; }
736 get { return dt.Month; }
739 public static DateTimeOffset Now {
740 get { return new DateTimeOffset (DateTime.Now);}
743 public TimeSpan Offset {
744 get { return utc_offset; }
748 get { return dt.Second; }
752 get { return dt.Ticks; }
755 public TimeSpan TimeOfDay {
756 get { return dt.TimeOfDay; }
759 public DateTime UtcDateTime {
760 get { return DateTime.SpecifyKind (dt - utc_offset, DateTimeKind.Utc); }
763 public static DateTimeOffset UtcNow {
764 get { return new DateTimeOffset (DateTime.UtcNow); }
767 public long UtcTicks {
768 get { return UtcDateTime.Ticks; }
772 get { return dt.Year; }