/* * System.DateTimeOffset.cs * * Author(s) * Stephane Delcroix * Marek Safar (marek.safar@gmail.com) * * Copyright (C) 2007 Novell, Inc (http://www.novell.com) * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #if NET_2_0 // Introduced by .NET 3.5 for 2.0 mscorlib using System.Globalization; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Text; namespace System { [Serializable] [StructLayout (LayoutKind.Auto)] public struct DateTimeOffset : IComparable, IFormattable, ISerializable, IDeserializationCallback, IComparable, IEquatable { public static readonly DateTimeOffset MaxValue = new DateTimeOffset (DateTime.MaxValue, TimeSpan.Zero); public static readonly DateTimeOffset MinValue = new DateTimeOffset (DateTime.MinValue, TimeSpan.Zero); DateTime dt; TimeSpan utc_offset; public DateTimeOffset (DateTime dateTime) { dt = dateTime; if (dateTime.Kind == DateTimeKind.Utc) utc_offset = TimeSpan.Zero; else utc_offset = TimeZone.CurrentTimeZone.GetUtcOffset (dateTime); if (UtcDateTime < DateTime.MinValue || UtcDateTime > DateTime.MaxValue) throw new ArgumentOutOfRangeException ("The UTC date and time that results from applying the offset is earlier than MinValue or later than MaxValue."); } public DateTimeOffset (DateTime dateTime, TimeSpan offset) { if (dateTime.Kind == DateTimeKind.Utc && offset != TimeSpan.Zero) throw new ArgumentException ("dateTime.Kind equals Utc and offset does not equal zero."); if (dateTime.Kind == DateTimeKind.Local && offset != TimeZone.CurrentTimeZone.GetUtcOffset (dateTime)) throw new ArgumentException ("dateTime.Kind equals Local and offset does not equal the offset of the system's local time zone."); if (offset.Ticks % TimeSpan.TicksPerMinute != 0) throw new ArgumentException ("offset is not specified in whole minutes."); if (offset < new TimeSpan (-14, 0 ,0) || offset > new TimeSpan (14, 0, 0)) throw new ArgumentOutOfRangeException ("offset is less than -14 hours or greater than 14 hours."); dt = dateTime; utc_offset = offset; if (UtcDateTime < DateTime.MinValue || UtcDateTime > DateTime.MaxValue) throw new ArgumentOutOfRangeException ("The UtcDateTime property is earlier than MinValue or later than MaxValue."); } public DateTimeOffset (long ticks, TimeSpan offset) : this (new DateTime (ticks), offset) { } public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, TimeSpan offset) : this (new DateTime (year, month, day, hour, minute, second), offset) { } public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, int millisecond, TimeSpan offset) : this (new DateTime (year, month, day, hour, minute, second, millisecond), offset) { } public DateTimeOffset (int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, TimeSpan offset) : this (new DateTime (year, month, day, hour, minute, second, millisecond, calendar), offset) { } public DateTimeOffset Add (TimeSpan timeSpan) { return new DateTimeOffset (dt.Add (timeSpan), utc_offset); } public DateTimeOffset AddDays (double days) { return new DateTimeOffset (dt.AddDays (days), utc_offset); } public DateTimeOffset AddHours (double hours) { return new DateTimeOffset (dt.AddHours (hours), utc_offset); } public static DateTimeOffset operator + (DateTimeOffset dateTimeTz, TimeSpan timeSpan) { return dateTimeTz.Add (timeSpan); } public DateTimeOffset AddMilliseconds (double milliseconds) { return new DateTimeOffset (dt.AddMilliseconds (milliseconds), utc_offset); } public DateTimeOffset AddMinutes (double minutes) { return new DateTimeOffset (dt.AddMinutes (minutes), utc_offset); } public DateTimeOffset AddMonths (int months) { return new DateTimeOffset (dt.AddMonths (months), utc_offset); } public DateTimeOffset AddSeconds (double seconds) { return new DateTimeOffset (dt.AddSeconds (seconds), utc_offset); } public DateTimeOffset AddTicks (long ticks) { return new DateTimeOffset (dt.AddTicks (ticks), utc_offset); } public DateTimeOffset AddYears (int years) { return new DateTimeOffset (dt.AddYears (years), utc_offset); } public static int Compare (DateTimeOffset first, DateTimeOffset second) { return first.CompareTo (second); } public int CompareTo (DateTimeOffset other) { return UtcDateTime.CompareTo (other.UtcDateTime); } int IComparable.CompareTo (object obj) { return CompareTo ((DateTimeOffset) obj); } public static bool operator == (DateTimeOffset left, DateTimeOffset right) { return left.Equals (right); } public bool Equals (DateTimeOffset other) { return UtcDateTime == other.UtcDateTime; } public override bool Equals (object obj) { if (obj is DateTimeOffset) return UtcDateTime == ((DateTimeOffset) obj).UtcDateTime; return false; } public static bool Equals (DateTimeOffset first, DateTimeOffset second) { return first.Equals (second); } public bool EqualsExact (DateTimeOffset other) { return dt == other.dt && utc_offset == other.utc_offset; } public static DateTimeOffset FromFileTime (long fileTime) { if (fileTime < 0 || fileTime > MaxValue.Ticks) throw new ArgumentOutOfRangeException ("fileTime is less than zero or greater than DateTimeOffset.MaxValue.Ticks."); return new DateTimeOffset (DateTime.FromFileTime (fileTime), TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.FromFileTime (fileTime))); } public override int GetHashCode () { return dt.GetHashCode () ^ utc_offset.GetHashCode (); } void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context) { if (info == null) throw new ArgumentNullException ("info"); info.AddValue ("datetime", dt); info.AddValue ("offset", utc_offset); } public static bool operator > (DateTimeOffset left, DateTimeOffset right) { return left.UtcDateTime > right.UtcDateTime; } public static bool operator >= (DateTimeOffset left, DateTimeOffset right) { return left.UtcDateTime >= right.UtcDateTime; } public static implicit operator DateTimeOffset (DateTime dateTime) { return new DateTimeOffset (dateTime); } public static bool operator != (DateTimeOffset left, DateTimeOffset right) { return left.UtcDateTime != right.UtcDateTime; } public static bool operator < (DateTimeOffset left, DateTimeOffset right) { return left.UtcDateTime < right.UtcDateTime; } public static bool operator <= (DateTimeOffset left, DateTimeOffset right) { return left.UtcDateTime <= right.UtcDateTime; } [MonoTODO] void IDeserializationCallback.OnDeserialization (object sender) { } public static DateTimeOffset Parse (string input) { return Parse (input, null); } public static DateTimeOffset Parse (string input, IFormatProvider formatProvider) { return Parse (input, formatProvider, DateTimeStyles.AllowWhiteSpaces); } [MonoTODO] public static DateTimeOffset Parse (string input, IFormatProvider formatProvider, DateTimeStyles styles) { if (input == null) throw new ArgumentNullException ("input"); throw new NotImplementedException (); } public static DateTimeOffset ParseExact (string input, string format, IFormatProvider formatProvider) { return ParseExact (input, format, formatProvider, DateTimeStyles.AssumeLocal); } public static DateTimeOffset ParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles) { if (format == null) throw new ArgumentNullException ("format"); if (format == String.Empty) throw new FormatException ("format is an empty string"); return ParseExact (input, new string [] {format}, formatProvider, styles); } public static DateTimeOffset ParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles) { if (input == null) throw new ArgumentNullException ("input"); if (input == String.Empty) throw new FormatException ("input is an empty string"); if (formats == null) throw new ArgumentNullException ("formats"); if (formats.Length == 0) throw new FormatException ("Invalid format specifier"); if ((styles & DateTimeStyles.AssumeLocal) != 0 && (styles & DateTimeStyles.AssumeUniversal) != 0) throw new ArgumentException ("styles parameter contains incompatible flags"); DateTimeOffset result; if (!ParseExact (input, formats, DateTimeFormatInfo.GetInstance (formatProvider), styles, out result)) throw new FormatException (); return result; } private static bool ParseExact (string input, string [] formats, DateTimeFormatInfo dfi, DateTimeStyles styles, out DateTimeOffset ret) { foreach (string format in formats) { if (format == null || format == String.Empty) throw new FormatException ("Invlid Format Sting"); DateTimeOffset result; if (DoParse (input, format, false, out result, dfi, styles)) { ret = result; return true; } } ret = DateTimeOffset.MinValue; return false; } private static bool DoParse (string input, string format, bool exact, out DateTimeOffset result, DateTimeFormatInfo dfi, DateTimeStyles styles) { if ((styles & DateTimeStyles.AllowLeadingWhite) != 0) { format = format.TrimStart (null); input = input.TrimStart (null); } if ((styles & DateTimeStyles.AllowTrailingWhite) != 0) { format = format.TrimEnd (null); input = input.TrimEnd (null); } bool allow_white_spaces = false; if ((styles & DateTimeStyles.AllowInnerWhite) != 0) allow_white_spaces = true; bool useutc = false, use_invariants = false; if (format.Length == 1) format = DateTimeUtils.GetStandardPattern (format[0], dfi, out useutc, out use_invariants, true); int year = -1; int month = -1; int day = -1; int partial_hour = -1; // for 'hh tt' formats int hour = -1; int minute = -1; int second = -1; double fraction = -1; int temp_int = -1; TimeSpan offset = TimeSpan.MinValue; result = DateTimeOffset.MinValue; int fi = 0; //format iterator int ii = 0; //input iterator while (fi < format.Length) { int tokLen; char ch = format [fi]; switch (ch) { case 'd': tokLen = DateTimeUtils.CountRepeat (format, fi, ch); if (day != -1 || tokLen > 4) return false; if (tokLen <= 2) ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out day); else ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedDayNames : dfi.DayNames, allow_white_spaces, out temp_int); break; case 'f': tokLen = DateTimeUtils.CountRepeat (format, fi, ch); ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out temp_int); if (fraction >= 0 || tokLen > 7 || temp_int == -1) return false; fraction = (double)temp_int / Math.Pow (10, tokLen); break; case 'F': tokLen = DateTimeUtils.CountRepeat (format, fi, ch); int digits; int read = ParseNumber (input, ii, tokLen, true, allow_white_spaces, out temp_int, out digits); if (temp_int == -1) ii += ParseNumber (input, ii, digits, true, allow_white_spaces, out temp_int); else ii += read; if (fraction >= 0 || tokLen > 7 || temp_int == -1) return false; fraction = (double)temp_int / Math.Pow (10, digits); break; case 'h': tokLen = DateTimeUtils.CountRepeat (format, fi, ch); if (hour != -1 || tokLen > 2) return false; ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out temp_int); if (temp_int == -1) return false; if (partial_hour == -1) partial_hour = temp_int; else hour = partial_hour + temp_int; break; case 'H': tokLen = DateTimeUtils.CountRepeat (format, fi, ch); if (hour != -1 || tokLen > 2) return false; ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out hour); break; case 'm': tokLen = DateTimeUtils.CountRepeat (format, fi, ch); if (minute != -1 || tokLen > 2) return false; ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out minute); break; case 'M': tokLen = DateTimeUtils.CountRepeat (format, fi, ch); if (month != -1 || tokLen > 4) return false; if (tokLen <= 2) ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out month); else { ii += ParseEnum (input, ii, tokLen == 3 ? dfi.AbbreviatedMonthNames : dfi.MonthNames, allow_white_spaces, out month); month += 1; } break; case 's': tokLen = DateTimeUtils.CountRepeat (format, fi, ch); if (second != -1 || tokLen > 2) return false; ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out second); break; case 't': tokLen = DateTimeUtils.CountRepeat (format, fi, ch); if (hour != -1 || tokLen > 2) return false; ii += ParseEnum (input, ii, tokLen == 1 ? new string[] {new string (dfi.AMDesignator[0], 1), new string (dfi.PMDesignator[0], 0)} : new string[] {dfi.AMDesignator, dfi.PMDesignator}, allow_white_spaces, out temp_int); if (temp_int == -1) return false; if (partial_hour == -1) partial_hour = temp_int * 12; else hour = partial_hour + temp_int * 12; break; case 'y': if (year != -1) return false; tokLen = DateTimeUtils.CountRepeat (format, fi, ch); if (tokLen <= 2) { ii += ParseNumber (input, ii, 2, tokLen == 2, allow_white_spaces, out year); if (year != -1) year += DateTime.Now.Year - DateTime.Now.Year % 100; } else if (tokLen <= 4) { // yyy and yyyy accept up to 5 digits with leading 0 int digit_parsed; ii += ParseNumber (input, ii, 5, false, allow_white_spaces, out year, out digit_parsed); if (digit_parsed < tokLen || (digit_parsed > tokLen && (year / Math.Pow (10, digit_parsed - 1) < 1))) return false; } else ii += ParseNumber (input, ii, tokLen, true, allow_white_spaces, out year); break; case 'z': tokLen = DateTimeUtils.CountRepeat (format, fi, ch); if (offset != TimeSpan.MinValue || tokLen > 3) return false; int off_h, off_m = 0, sign; temp_int = 0; ii += ParseEnum (input, ii, new string [] {"-", "+"}, allow_white_spaces, out sign); ii += ParseNumber (input, ii, 2, tokLen != 1, false, out off_h); if (tokLen == 3) { ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int); ii += ParseNumber (input, ii, 2, true, false, out off_m); } if (off_h == -1 || off_m == -1 || sign == -1 || temp_int == -1) return false; if (sign == 0) sign = -1; offset = new TimeSpan (sign * off_h, sign * off_m, 0); break; case ':': tokLen = 1; ii += ParseEnum (input, ii, new string [] {dfi.TimeSeparator}, false, out temp_int); if (temp_int == -1) return false; break; case '/': tokLen = 1; ii += ParseEnum (input, ii, new string [] {dfi.DateSeparator}, false, out temp_int); if (temp_int == -1) return false; break; case '%': tokLen = 1; if (fi != 0) return false; break; case ' ': tokLen = 1; ii += ParseChar (input, ii, ' ', false, out temp_int); if (temp_int == -1) return false; break; case '\\': tokLen = 2; ii += ParseChar (input, ii, format [fi + 1], allow_white_spaces, out temp_int); if (temp_int == -1) return false; break; default: //Console.WriteLine ("un-parsed character: {0}", ch); tokLen = 1; ii += ParseChar (input, ii, format [fi], allow_white_spaces, out temp_int); if (temp_int == -1) return false; break; } fi += tokLen; } //Console.WriteLine ("{0}-{1}-{2} {3}:{4} {5}", year, month, day, hour, minute, offset); if (offset == TimeSpan.MinValue && (styles & DateTimeStyles.AssumeLocal) != 0) offset = TimeZone.CurrentTimeZone.GetUtcOffset (DateTime.Now); if (offset == TimeSpan.MinValue && (styles & DateTimeStyles.AssumeUniversal) != 0) offset = TimeSpan.Zero; if (hour < 0) hour = 0; if (minute < 0) minute = 0; if (second < 0) second = 0; if (fraction < 0) fraction = 0; if (year > 0 && month > 0 && day > 0) { result = new DateTimeOffset (year, month, day, hour, minute, second, (int) (1000 * fraction), offset); if ((styles & DateTimeStyles.AdjustToUniversal) != 0) result = result.ToUniversalTime (); return true; } return false; } private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result) { int digit_parsed; return ParseNumber (input, pos, digits, leading_zero, allow_leading_white, out result, out digit_parsed); } private static int ParseNumber (string input, int pos, int digits, bool leading_zero, bool allow_leading_white, out int result, out int digit_parsed) { int char_parsed = 0; digit_parsed = 0; result = 0; for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++) char_parsed++; for (; pos < input.Length && Char.IsDigit (input[pos]) && digits > 0; pos ++, char_parsed++, digit_parsed++, digits --) result = 10 * result + (byte) (input[pos] - '0'); if (leading_zero && digits > 0) result = -1; if (digit_parsed == 0) result = -1; return char_parsed; } private static int ParseEnum (string input, int pos, string [] enums, bool allow_leading_white, out int result) { int char_parsed = 0; result = -1; for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++) char_parsed ++; for (int i = 0; i < enums.Length; i++) if (input.Substring(pos).StartsWith (enums [i])) { result = i; break; } if (result >= 0) char_parsed += enums[result].Length; return char_parsed; } private static int ParseChar (string input, int pos, char c, bool allow_leading_white, out int result) { int char_parsed = 0; result = -1; for (; allow_leading_white && pos < input.Length && input[pos] == ' '; pos++, char_parsed++) ; if (pos < input.Length && input[pos] == c){ result = (int) c; char_parsed ++; } return char_parsed; } public TimeSpan Subtract (DateTimeOffset value) { return UtcDateTime - value.UtcDateTime; } public DateTimeOffset Subtract (TimeSpan value) { return Add (-value); } public static TimeSpan operator - (DateTimeOffset left, DateTimeOffset right) { return left.Subtract (right); } public static DateTimeOffset operator - (DateTimeOffset dateTimeTz, TimeSpan timeSpan) { return dateTimeTz.Subtract (timeSpan); } public long ToFileTime () { return UtcDateTime.ToFileTime (); } public DateTimeOffset ToLocalTime () { return new DateTimeOffset (UtcDateTime.ToLocalTime (), TimeZone.CurrentTimeZone.GetUtcOffset (UtcDateTime.ToLocalTime ())); } public DateTimeOffset ToOffset (TimeSpan offset) { return new DateTimeOffset (dt - utc_offset + offset, offset); } public override string ToString () { return ToString (null, null); } public string ToString (IFormatProvider formatProvider) { return ToString (null, formatProvider); } public string ToString (string format) { return ToString (format, null); } public string ToString (string format, IFormatProvider formatProvider) { DateTimeFormatInfo dfi = DateTimeFormatInfo.GetInstance(formatProvider); if (format == null || format == String.Empty) format = dfi.ShortDatePattern + " " + dfi.LongTimePattern + " zzz"; bool to_utc = false, use_invariant = false; if (format.Length == 1) { char fchar = format [0]; try { format = DateTimeUtils.GetStandardPattern (fchar, dfi, out to_utc, out use_invariant, true); } catch { format = null; } if (format == null) throw new FormatException ("format is not one of the format specifier characters defined for DateTimeFormatInfo"); } return to_utc ? DateTimeUtils.ToString (UtcDateTime, TimeSpan.Zero, format, dfi) : DateTimeUtils.ToString (DateTime, Offset, format, dfi); } public DateTimeOffset ToUniversalTime () { return new DateTimeOffset (UtcDateTime, TimeSpan.Zero); } public static bool TryParse (string input, out DateTimeOffset result) { try { result = Parse (input); return true; } catch { result = MinValue; return false; } } public static bool TryParse (string input, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result) { try { result = Parse (input, formatProvider, styles); return true; } catch { result = MinValue; return false; } } public static bool TryParseExact (string input, string format, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result) { try { result = ParseExact (input, format, formatProvider, styles); return true; } catch { result = MinValue; return false; } } public static bool TryParseExact (string input, string[] formats, IFormatProvider formatProvider, DateTimeStyles styles, out DateTimeOffset result) { try { result = ParseExact (input, formats, formatProvider, styles); return true; } catch { result = MinValue; return false; } } public DateTime Date { get { return DateTime.SpecifyKind (dt.Date, DateTimeKind.Unspecified); } } public DateTime DateTime { get { return DateTime.SpecifyKind (dt, DateTimeKind.Unspecified); } } public int Day { get { return dt.Day; } } public DayOfWeek DayOfWeek { get { return dt.DayOfWeek; } } public int DayOfYear { get { return dt.DayOfYear; } } public int Hour { get { return dt.Hour; } } public DateTime LocalDateTime { get { return UtcDateTime.ToLocalTime (); } } public int Millisecond { get { return dt.Millisecond; } } public int Minute { get { return dt.Minute; } } public int Month { get { return dt.Month; } } public static DateTimeOffset Now { get { return new DateTimeOffset (DateTime.Now);} } public TimeSpan Offset { get { return utc_offset; } } public int Second { get { return dt.Second; } } public long Ticks { get { return dt.Ticks; } } public TimeSpan TimeOfDay { get { return dt.TimeOfDay; } } public DateTime UtcDateTime { get { return DateTime.SpecifyKind (dt - utc_offset, DateTimeKind.Utc); } } public static DateTimeOffset UtcNow { get { return new DateTimeOffset (DateTime.UtcNow); } } public long UtcTicks { get { return UtcDateTime.Ticks; } } public int Year { get { return dt.Year; } } } } #endif