// // System.DateTime.cs // // author: // Marcel Narings (marcel@narings.nl) // Martin Baulig (martin@gnome.org) // Atsushi Enomoto (atsushi@ximian.com) // // (C) 2001 Marcel Narings // Copyright (C) 2004-2005 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. // using System.Collections; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; namespace System { /// /// The DateTime structure represents dates and time ranging from /// 1-1-0001 12:00:00 AM to 31-12-9999 23:59:00 Common Era. /// /// [Serializable] [StructLayout (LayoutKind.Auto)] public struct DateTime : IFormattable, IConvertible, IComparable #if NET_2_0 , IComparable, IEquatable #endif { private TimeSpan ticks; #if NET_2_0 DateTimeKind kind; #endif private const int dp400 = 146097; private const int dp100 = 36524; private const int dp4 = 1461; // w32 file time starts counting from 1/1/1601 00:00 GMT // which is the constant ticks from the .NET epoch private const long w32file_epoch = 504911232000000000L; //private const long MAX_VALUE_TICKS = 3155378975400000000L; // -- Microsoft .NET has this value. private const long MAX_VALUE_TICKS = 3155378975999999999L; // // The UnixEpoch, it begins on Jan 1, 1970 at 0:0:0, expressed // in Ticks // internal const long UnixEpoch = 621355968000000000L; // for OLE Automation dates private const long ticks18991230 = 599264352000000000L; private const double OAMinValue = -657435.0d; private const double OAMaxValue = 2958466.0d; public static readonly DateTime MaxValue = new DateTime (false, new TimeSpan (MAX_VALUE_TICKS)); public static readonly DateTime MinValue = new DateTime (false, new TimeSpan (0)); private static readonly string[] commonFormats = { // For compatibility with MS's CLR, this format (which // doesn't have a one-letter equivalent) is parsed // too. It's important because it's used in XML // serialization. // Note that those format should be tried only for // invalid patterns; // FIXME: SOME OF those patterns looks tried against // the current culture, since some patterns fail in // some culture. "yyyy-MM-dd", "yyyy-MM-ddTHH:mm:sszzz", "yyyy-MM-ddTHH:mm:ss.fffffff", "yyyy-MM-ddTHH:mm:ss.fffffffzzz", // bug #78618 "yyyy-M-d H:m:s.fffffff", // UTC / allow any separator "yyyy/MM/ddTHH:mm:ssZ", "yyyy/M/dZ", // bug #58938 "yyyy/M/d HH:mm:ss", // bug #47720 "yyyy/MM/dd HH:mm:ss 'GMT'", // bug #53023 "MM/dd/yyyy", // Close to RFC1123, but without 'GMT' "ddd, d MMM yyyy HH:mm:ss", // use UTC ('Z'; not literal "'Z'") // FIXME: 1078(af-ZA) and 1079(ka-GE) reject it "yyyy/MM/dd HH':'mm':'ssZ", // bug #60912 "M/d/yyyy HH':'mm':'ss tt", "H':'mm':'ss tt", // another funky COM dependent one "dd-MMM-yy", // DayOfTheWeek, dd full_month_name yyyy // FIXME: 1054(th-TH) rejects them "dddd, dd MMMM yyyy", "dddd, dd MMMM yyyy HH:mm", "dddd, dd MMMM yyyy HH:mm:ss", "yyyy MMMM", // DayOfTheWeek, dd yyyy. This works for every locales. "MMMM dd, yyyy", #if NET_1_1 // X509Certificate pattern is accepted by Parse() *in every culture* "yyyyMMddHHmmssZ", #endif // In Parse() the 'r' equivalent pattern is first parsed as universal time "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'", // Additionally there seems language-specific format // patterns that however works in all language // environment. // For example, the pattern below is for Japanese. "yyyy'\u5E74'MM'\u6708'dd'\u65E5' HH'\u6642'mm'\u5206'ss'\u79D2'", /* // Full date and time "F", "G", "r", "s", "u", "U", // Full date and time, but no seconds "f", "g", // Only date "d", "D", // Only time "T", "t", // Only date, but no year "m", // Only date, but no day "y" */ }; private enum Which { Day, DayYear, Month, Year }; private static readonly int[] daysmonth = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; private static readonly int[] daysmonthleap = { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; private static int AbsoluteDays (int year, int month, int day) { int[] days; int temp = 0, m=1 ; days = (IsLeapYear(year) ? daysmonthleap : daysmonth); while (m < month) temp += days[m++]; return ((day-1) + temp + (365* (year-1)) + ((year-1)/4) - ((year-1)/100) + ((year-1)/400)); } private int FromTicks(Which what) { int num400, num100, num4, numyears; int M =1; int[] days = daysmonth; int totaldays = this.ticks.Days; num400 = (totaldays / dp400); totaldays -= num400 * dp400; num100 = (totaldays / dp100); if (num100 == 4) // leap num100 = 3; totaldays -= (num100 * dp100); num4 = totaldays / dp4; totaldays -= (num4 * dp4); numyears = totaldays / 365 ; if (numyears == 4) //leap numyears =3 ; if (what == Which.Year ) return num400*400 + num100*100 + num4*4 + numyears + 1; totaldays -= (numyears * 365) ; if (what == Which.DayYear ) return totaldays + 1; if ((numyears==3) && ((num100 == 3) || !(num4 == 24)) ) //31 dec leapyear days = daysmonthleap; while (totaldays >= days[M]) totaldays -= days[M++]; if (what == Which.Month ) return M; return totaldays +1; } // Constructors /// /// Constructs a DateTime for specified ticks /// /// public DateTime (long ticks) { this.ticks = new TimeSpan (ticks); if (ticks < MinValue.Ticks || ticks > MaxValue.Ticks) { string msg = Locale.GetText ("Value {0} is outside the valid range [{1},{2}].", ticks, MinValue.Ticks, MaxValue.Ticks); throw new ArgumentOutOfRangeException ("ticks", msg); } #if NET_2_0 kind = DateTimeKind.Unspecified; #endif } public DateTime (int year, int month, int day) : this (year, month, day,0,0,0,0) {} public DateTime (int year, int month, int day, int hour, int minute, int second) : this (year, month, day, hour, minute, second, 0) {} public DateTime (int year, int month, int day, int hour, int minute, int second, int millisecond) { if ( year < 1 || year > 9999 || month < 1 || month >12 || day < 1 || day > DaysInMonth(year, month) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59 || millisecond < 0 || millisecond > 999) throw new ArgumentOutOfRangeException ("Parameters describe an " + "unrepresentable DateTime."); ticks = new TimeSpan (AbsoluteDays(year,month,day), hour, minute, second, millisecond); #if NET_2_0 kind = DateTimeKind.Unspecified; #endif } [MonoTODO ("The Calendar is not taken into consideration")] public DateTime (int year, int month, int day, Calendar calendar) : this (year, month, day, 0, 0, 0, 0, calendar) { } [MonoTODO ("The Calendar is not taken into consideration")] public DateTime (int year, int month, int day, int hour, int minute, int second, Calendar calendar) : this (year, month, day, hour, minute, second, 0, calendar) { } [MonoTODO ("The Calendar is not taken into consideration")] public DateTime (int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar) : this (year, month, day, hour, minute, second, millisecond) { if (calendar == null) throw new ArgumentNullException ("calendar"); } internal DateTime (bool check, TimeSpan value) { if (check && (value.Ticks < MinValue.Ticks || value.Ticks > MaxValue.Ticks)) throw new ArgumentOutOfRangeException (); ticks = value; #if NET_2_0 kind = DateTimeKind.Unspecified; #endif } #if NET_2_0 public DateTime (long ticks, DateTimeKind kind) : this (ticks) { CheckDateTimeKind (kind); this.kind = kind; } public DateTime (int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) : this (year, month, day, hour, minute, second) { CheckDateTimeKind (kind); this.kind = kind; } public DateTime (int year, int month, int day, int hour, int minute, int second, int millisecond, DateTimeKind kind) : this (year, month, day, hour, minute, second, millisecond) { CheckDateTimeKind (kind); this.kind = kind; } public DateTime (int year, int month, int day, int hour, int minute, int second, int millisecond, Calendar calendar, DateTimeKind kind) : this (year, month, day, hour, minute, second, millisecond, calendar) { CheckDateTimeKind (kind); this.kind = kind; } #endif /* Properties */ public DateTime Date { get { return new DateTime (Year, Month, Day); } } public int Month { get { return FromTicks(Which.Month); } } public int Day { get { return FromTicks(Which.Day); } } public DayOfWeek DayOfWeek { get { return ( (DayOfWeek) ((ticks.Days+1) % 7) ); } } public int DayOfYear { get { return FromTicks(Which.DayYear); } } public TimeSpan TimeOfDay { get { return new TimeSpan(ticks.Ticks % TimeSpan.TicksPerDay ); } } public int Hour { get { return ticks.Hours; } } public int Minute { get { return ticks.Minutes; } } public int Second { get { return ticks.Seconds; } } public int Millisecond { get { return ticks.Milliseconds; } } [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern long GetNow (); // // To reduce the time consumed by DateTime.Now, we keep // the difference to map the system time into a local // time into `to_local_time_span', we record the timestamp // for this in `last_now' // static object to_local_time_span_object; static long last_now; public static DateTime Now { get { long now = GetNow (); DateTime dt = new DateTime (now); if ((now - last_now) > TimeSpan.TicksPerMinute){ to_local_time_span_object = TimeZone.CurrentTimeZone.GetLocalTimeDiff (dt); last_now = now; } // This is boxed, so we avoid locking. return dt + (TimeSpan) to_local_time_span_object; } } public long Ticks { get { return ticks.Ticks; } } public static DateTime Today { get { DateTime now = Now; DateTime today = new DateTime (now.Year, now.Month, now.Day); #if NET_2_0 today.kind = now.kind; #endif return today; } } public static DateTime UtcNow { get { #if NET_2_0 return new DateTime (GetNow (), DateTimeKind.Utc); #else return new DateTime (GetNow ()); #endif } } public int Year { get { return FromTicks(Which.Year); } } #if NET_2_0 public DateTimeKind Kind { get { return kind; } } #endif /* methods */ public DateTime Add (TimeSpan ts) { return AddTicks (ts.Ticks); } public DateTime AddDays (double days) { return AddMilliseconds (Math.Round (days * 86400000)); } public DateTime AddTicks (long t) { if ((t + ticks.Ticks) > MAX_VALUE_TICKS || (t + ticks.Ticks) < 0) { throw new ArgumentOutOfRangeException(); } return new DateTime (t + ticks.Ticks); } public DateTime AddHours (double hours) { return AddMilliseconds (hours * 3600000); } public DateTime AddMilliseconds (double ms) { if ((ms * TimeSpan.TicksPerMillisecond) > long.MaxValue || (ms * TimeSpan.TicksPerMillisecond) < long.MinValue) { throw new ArgumentOutOfRangeException(); } long msticks = (long) (ms * TimeSpan.TicksPerMillisecond); return AddTicks (msticks); } // required to match MS implementation for OADate (OLE Automation) private DateTime AddRoundedMilliseconds (double ms) { if ((ms * TimeSpan.TicksPerMillisecond) > long.MaxValue || (ms * TimeSpan.TicksPerMillisecond) < long.MinValue) { throw new ArgumentOutOfRangeException (); } long msticks = (long) (ms += ms > 0 ? 0.5 : -0.5) * TimeSpan.TicksPerMillisecond; return AddTicks (msticks); } public DateTime AddMinutes (double minutes) { return AddMilliseconds (minutes * 60000); } public DateTime AddMonths (int months) { int day, month, year, maxday ; DateTime temp ; day = this.Day; month = this.Month + (months % 12); year = this.Year + months/12 ; if (month < 1) { month = 12 + month ; year -- ; } else if (month>12) { month = month -12; year ++; } maxday = DaysInMonth(year, month); if (day > maxday) day = maxday; temp = new DateTime (year, month, day); return temp.Add (this.TimeOfDay); } public DateTime AddSeconds (double seconds) { return AddMilliseconds (seconds*1000); } public DateTime AddYears (int years ) { return AddMonths(years * 12); } public static int Compare (DateTime t1, DateTime t2) { if (t1.ticks < t2.ticks) return -1; else if (t1.ticks > t2.ticks) return 1; else return 0; } public int CompareTo (object v) { if ( v == null) return 1; if (!(v is System.DateTime)) throw new ArgumentException (Locale.GetText ( "Value is not a System.DateTime")); return Compare (this, (DateTime) v); } #if NET_2_0 public bool IsDaylightSavingTime () { if (kind == DateTimeKind.Utc) return false; return TimeZone.CurrentTimeZone.IsDaylightSavingTime (this); } public int CompareTo (DateTime value) { return Compare (this, value); } public bool Equals (DateTime value) { return value.ticks == ticks; } public static DateTime SpecifyKind (DateTime value, DateTimeKind kind) { return new DateTime (value.Ticks, kind); } #endif public static int DaysInMonth (int year, int month) { int[] days ; if (month < 1 || month >12) throw new ArgumentOutOfRangeException (); days = (IsLeapYear(year) ? daysmonthleap : daysmonth); return days[month]; } public override bool Equals (object o) { if (!(o is System.DateTime)) return false; return ((DateTime) o).ticks == ticks; } public static bool Equals (DateTime t1, DateTime t2 ) { return (t1.ticks == t2.ticks ); } public static DateTime FromFileTime (long fileTime) { if (fileTime < 0) throw new ArgumentOutOfRangeException ("fileTime", "< 0"); return new DateTime (w32file_epoch + fileTime).ToLocalTime (); } #if NET_1_1 public static DateTime FromFileTimeUtc (long fileTime) { if (fileTime < 0) throw new ArgumentOutOfRangeException ("fileTime", "< 0"); return new DateTime (w32file_epoch + fileTime); } #endif public static DateTime FromOADate (double d) { // An OLE Automation date is implemented as a floating-point number // whose value is the number of days from midnight, 30 December 1899. // d must be negative 657435.0 through positive 2958466.0. if ((d <= OAMinValue) || (d >= OAMaxValue)) throw new ArgumentException ("d", "[-657435,2958466]"); DateTime dt = new DateTime (ticks18991230); if (d < 0.0d) { Double days = Math.Ceiling (d); // integer part is the number of days (negative) dt = dt.AddRoundedMilliseconds (days * 86400000); // but decimals are the number of hours (in days fractions) and positive Double hours = (days - d); dt = dt.AddRoundedMilliseconds (hours * 86400000); } else { dt = dt.AddRoundedMilliseconds (d * 86400000); } return dt; } public string[] GetDateTimeFormats() { return GetDateTimeFormats (CultureInfo.CurrentCulture); } public string[] GetDateTimeFormats(char format) { if ("dDgGfFmMrRstTuUyY".IndexOf (format) < 0) throw new FormatException ("Invalid format character."); string[] result = new string[1]; result[0] = this.ToString(format.ToString()); return result; } public string[] GetDateTimeFormats(IFormatProvider provider) { DateTimeFormatInfo info = (DateTimeFormatInfo) provider.GetFormat (typeof(DateTimeFormatInfo)); // return GetDateTimeFormats (info.GetAllDateTimePatterns ()); ArrayList al = new ArrayList (); foreach (char c in "dDgGfFmMrRstTuUyY") al.AddRange (GetDateTimeFormats (c, info)); return al.ToArray (typeof (string)) as string []; } public string[] GetDateTimeFormats(char format,IFormatProvider provider ) { if ("dDgGfFmMrRstTuUyY".IndexOf (format) < 0) throw new FormatException ("Invalid format character."); // LAMESPEC: There is NO assurance that 'U' ALWAYS // euqals to 'F', but since we have to iterate all // the pattern strings, we cannot just use // ToString("U", provider) here. I believe that the // method's behavior cannot be formalized. bool adjustutc = false; switch (format) { case 'U': // case 'r': // case 'R': // case 'u': adjustutc = true; break; } DateTimeFormatInfo info = (DateTimeFormatInfo) provider.GetFormat (typeof(DateTimeFormatInfo)); return GetDateTimeFormats (adjustutc, info.GetAllRawDateTimePatterns (format), info); } private string [] GetDateTimeFormats (bool adjustutc, string [] patterns, DateTimeFormatInfo dfi) { string [] results = new string [patterns.Length]; DateTime val = adjustutc ? ToUniversalTime () : this; for (int i = 0; i < results.Length; i++) results [i] = val._ToString (patterns [i], dfi); return results; } #if NET_2_0 private void CheckDateTimeKind (DateTimeKind kind) { if ((kind != DateTimeKind.Unspecified) && (kind != DateTimeKind.Utc) && (kind != DateTimeKind.Local)) throw new ArgumentException ("Invalid DateTimeKind value.", "kind"); } #endif public override int GetHashCode () { return (int) ticks.Ticks; } public TypeCode GetTypeCode () { return TypeCode.DateTime; } public static bool IsLeapYear (int year) { return ( (year % 4 == 0 && year % 100 != 0) || year % 400 == 0) ; } public static DateTime Parse (string s) { return Parse (s, null); } public static DateTime Parse (string s, IFormatProvider fp) { return Parse (s, fp, DateTimeStyles.AllowWhiteSpaces); } public static DateTime Parse (string s, IFormatProvider fp, DateTimeStyles styles) { // This method should try only expected patterns. // Should not try extra patterns. // Right now we also try InvariantCulture, but I // confirmed in some cases this method rejects what // InvariantCulture supports (can be checked against // "th-TH" with Gregorian Calendar). So basically it // should not be done. // I think it should be CurrentCulture to be tested, // but right now we don't support all the supported // patterns for each culture, so try InvariantCulture // as a quick remedy. if (s == null) throw new ArgumentNullException (Locale.GetText ("s is null")); DateTime result; if (fp == null) fp = CultureInfo.CurrentCulture; DateTimeFormatInfo dfi = DateTimeFormatInfo.GetInstance (fp); bool longYear = false; // Try all the patterns if (ParseExact (s, dfi.GetAllDateTimePatternsInternal (), dfi, styles, out result, false, ref longYear)) return result; // Try common formats. // if (ParseExact (s, commonFormats, dfi, styles, out result, false, ref longYear)) // return result; // Try common formats with invariant culture if (ParseExact (s, commonFormats, DateTimeFormatInfo.InvariantInfo, styles, out result, false, ref longYear)) return result; if (longYear) { throw new ArgumentOutOfRangeException ("year", "Valid values are between 1 and 9999 inclusive"); } throw new FormatException ("String was not recognized as a valid DateTime."); } public static DateTime ParseExact (string s, string format, IFormatProvider fp) { return ParseExact (s, format, fp, DateTimeStyles.None); } internal static int _ParseNumber (string s, int valuePos, int min_digits, int digits, bool leadingzero, bool sloppy_parsing, out int num_parsed) { int number = 0, i; if (sloppy_parsing) leadingzero = false; if (!leadingzero) { int real_digits = 0; for (i = valuePos; i < s.Length && i < digits + valuePos; i++) { if (!Char.IsDigit (s[i])) break; real_digits++; } digits = real_digits; } if (digits < min_digits) { num_parsed = -1; return 0; } if (s.Length - valuePos < digits) { num_parsed = -1; return 0; } for (i = valuePos; i < digits + valuePos; i++) { char c = s[i]; if (!Char.IsDigit (c)) { num_parsed = -1; return 0; } number = number * 10 + (byte) (c - '0'); } num_parsed = digits; return number; } internal static int _ParseEnum (string s, int sPos, string[] values, out int num_parsed) { int i; // FIXME: I know this is somehow lame code. Probably // it should iterate all the enum value and return // the longest match. However right now I don't see // anything but "1" and "10" - "12" that might match // two or more values. (They are only abbrev month // names, so do reverse order search). See bug #80094. for (i = values.Length - 1; i >= 0; i--) { if (s.Length - sPos < values[i].Length) continue; else if (values [i].Length == 0) continue; String tmp = s.Substring (sPos, values[i].Length); if (String.Compare (tmp, values[i], true) == 0) { num_parsed = values[i].Length; return i; } } num_parsed = -1; return -1; } internal static bool _ParseString (string s, int sPos, int maxlength, string value, out int num_parsed) { if (maxlength <= 0) maxlength = value.Length; if (String.Compare (s, sPos, value, 0, maxlength, true, CultureInfo.InvariantCulture) == 0) { num_parsed = maxlength; return true; } num_parsed = -1; return false; } private static bool _DoParse (string s, string format, bool exact, out DateTime result, DateTimeFormatInfo dfi, DateTimeStyles style, ref bool longYear) { bool useutc = false, use_localtime = true; bool use_invariant = false; bool sloppy_parsing = false; int valuePos = 0; if (format.Length == 1) format = _GetStandardPattern (format [0], dfi, out useutc, out use_invariant); else if (!exact && CultureInfo.InvariantCulture.CompareInfo.IndexOf (format, "GMT", CompareOptions.Ordinal) >= 0) useutc = true; if ((style & DateTimeStyles.AllowLeadingWhite) != 0) { format = format.TrimStart (null); s = s.TrimStart (null); // it could be optimized, but will make little good. } if ((style & DateTimeStyles.AllowTrailingWhite) != 0) { format = format.TrimEnd (null); s = s.TrimEnd (null); // it could be optimized, but will make little good. } if (use_invariant) dfi = DateTimeFormatInfo.InvariantInfo; if ((style & DateTimeStyles.AllowInnerWhite) != 0) sloppy_parsing = true; string chars = format; int len = format.Length, pos = 0, num = 0; int day = -1, dayofweek = -1, month = -1, year = -1; int hour = -1, minute = -1, second = -1; double fractionalSeconds = -1; int ampm = -1; int tzsign = -1, tzoffset = -1, tzoffmin = -1; result = new DateTime (0); while (pos+num < len) { if (s.Length == valuePos) break; if (chars[pos] == '\'') { num = 1; while (pos+num < len) { if (chars[pos+num] == '\'') break; if (valuePos == s.Length) return false; if (s [valuePos] != chars [pos + num]) return false; valuePos++; num++; } if (pos+num > len) return false; pos += num + 1; num = 0; continue; } else if (chars[pos] == '"') { num = 1; while (pos+num < len) { if (chars[pos+num] == '"') break; if (valuePos == s.Length) return false; if (s [valuePos] != chars[pos+num]) return false; valuePos++; num++; } if (pos+num > len) return false; pos += num + 1; num = 0; continue; } else if (chars[pos] == '\\') { pos += num + 1; num = 0; if (pos >= len) return false; if (s [valuePos] != chars [pos]) return false; valuePos++; pos++; continue; } else if (chars[pos] == '%') { pos++; continue; } else if (Char.IsWhiteSpace (s [valuePos]) || s [valuePos] == ',' && Char.IsWhiteSpace (chars [pos])) { valuePos++; num = 0; if (exact && (style & DateTimeStyles.AllowInnerWhite) == 0) { if (!Char.IsWhiteSpace (chars[pos])) return false; pos++; continue; } int ws = valuePos; while (ws < s.Length) { if (Char.IsWhiteSpace (s [ws]) || s [ws] == ',') ws++; else break; } valuePos = ws; ws = pos; while (ws < chars.Length) { if (Char.IsWhiteSpace (chars [ws]) || chars [ws] == ',') ws++; else break; } pos = ws; continue; } if ((pos+num+1 < len) && (chars[pos+num+1] == chars[pos+num])) { num++; continue; } int num_parsed = 0; switch (chars[pos]) { case 'd': if (day != -1) return false; if (num == 0) day = _ParseNumber (s, valuePos,0, 2, false, sloppy_parsing, out num_parsed); else if (num == 1) day = _ParseNumber (s, valuePos,0, 2, true, sloppy_parsing, out num_parsed); else if (num == 2) dayofweek = _ParseEnum (s, valuePos, dfi.RawAbbreviatedDayNames, out num_parsed); else { dayofweek = _ParseEnum (s, valuePos, dfi.RawDayNames, out num_parsed); num = 3; } break; case 'M': if (month != -1) return false; if (num == 0) month = _ParseNumber (s, valuePos, 0, 2, false, sloppy_parsing, out num_parsed); else if (num == 1) month = _ParseNumber (s, valuePos, 0, 2, true, sloppy_parsing, out num_parsed); else if (num == 2) month = _ParseEnum (s, valuePos, dfi.RawAbbreviatedMonthNames , out num_parsed) + 1; else { month = _ParseEnum (s, valuePos, dfi.RawMonthNames, out num_parsed) + 1; num = 3; } break; case 'y': if (year != -1) return false; if (num == 0) { year = _ParseNumber (s, valuePos,0, 2, false, sloppy_parsing, out num_parsed); } else if (num < 3) { year = _ParseNumber (s, valuePos,0, 2, true, sloppy_parsing, out num_parsed); } else { year = _ParseNumber (s, valuePos,4, 4, false, sloppy_parsing, out num_parsed); if ((year >= 1000) && (num_parsed == 4) && (!longYear) && (s.Length > 4 + valuePos)) { int np = 0; int ly = _ParseNumber (s, valuePos, 5, 5, false, sloppy_parsing, out np); longYear = (ly > 9999); } num = 3; } //FIXME: We should do use dfi.Calendat.TwoDigitYearMax if (num_parsed <= 2) year += (year < 30) ? 2000 : 1900; break; case 'h': if (hour != -1) return false; if (num == 0) hour = _ParseNumber (s, valuePos,0, 2, false, sloppy_parsing, out num_parsed); else { hour = _ParseNumber (s, valuePos,0, 2, true, sloppy_parsing, out num_parsed); num = 1; } if (hour > 12) return false; if (hour == 12) hour = 0; break; case 'H': if ((hour != -1) || (ampm >= 0)) return false; if (num == 0) hour = _ParseNumber (s, valuePos,0, 2, false, sloppy_parsing, out num_parsed); else { hour = _ParseNumber (s, valuePos,0, 2, true, sloppy_parsing, out num_parsed); num = 1; } if (hour >= 24) return false; // ampm = -2; break; case 'm': if (minute != -1) return false; if (num == 0) minute = _ParseNumber (s, valuePos, 0, 2, false, sloppy_parsing, out num_parsed); else { minute = _ParseNumber (s, valuePos, 0, 2, true, sloppy_parsing, out num_parsed); num = 1; } if (minute >= 60) return false; break; case 's': if (second != -1) return false; if (num == 0) second = _ParseNumber (s, valuePos, 0, 2, false, sloppy_parsing, out num_parsed); else { second = _ParseNumber (s, valuePos, 0, 2, true, sloppy_parsing, out num_parsed); num = 1; } if (second >= 60) return false; break; case 'f': if (fractionalSeconds != -1) return false; num = Math.Min (num, 6); double decimalNumber = (double) _ParseNumber (s, valuePos, 0, num+1, true, sloppy_parsing, out num_parsed); if (num_parsed == -1) return false; else fractionalSeconds = decimalNumber / Math.Pow(10.0, num_parsed); break; case 't': if (ampm != -1) return false; if (num == 0) { if (_ParseString (s, valuePos, 1, dfi.AMDesignator, out num_parsed)) ampm = 0; else if (_ParseString (s, valuePos, 1, dfi.PMDesignator, out num_parsed)) ampm = 1; else return false; } else { if (_ParseString (s, valuePos, 0, dfi.AMDesignator, out num_parsed)) ampm = 0; else if (_ParseString (s, valuePos, 0, dfi.PMDesignator, out num_parsed)) ampm = 1; else return false; num = 1; } break; case 'z': if (tzsign != -1) return false; if (s [valuePos] == '+') tzsign = 0; else if (s [valuePos] == '-') tzsign = 1; else return false; valuePos++; if (num == 0) tzoffset = _ParseNumber (s, valuePos, 0, 2, false, sloppy_parsing, out num_parsed); else if (num == 1) tzoffset = _ParseNumber (s, valuePos, 0, 2, true, sloppy_parsing, out num_parsed); else { tzoffset = _ParseNumber (s, valuePos, 0, 2, true, sloppy_parsing, out num_parsed); if (num_parsed < 0) return false; valuePos += num_parsed; if (Char.IsDigit (s [valuePos])) num_parsed = 0; else if (!_ParseString (s, valuePos, 0, dfi.TimeSeparator, out num_parsed)) return false; valuePos += num_parsed; tzoffmin = _ParseNumber (s, valuePos, 0, 2, true, sloppy_parsing, out num_parsed); if (num_parsed < 0) return false; num = 2; } break; // LAMESPEC: This should be part of UTCpattern // string and thus should not be considered here. // // Note that 'Z' is not defined as a pattern // character. Keep it for X509 certificate // verification. Also, "Z" != "'Z'" under MS.NET // ("'Z'" is just literal; handled above) case 'Z': if (s [valuePos] != 'Z') return false; num = 0; num_parsed = 1; useutc = true; break; case ':': if (!_ParseString (s, valuePos, 0, dfi.TimeSeparator, out num_parsed)) return false; break; case '/': /* Accept any character for * DateSeparator, except * TimeSeparator, a digit or a * letter. Not documented, * but seems to be MS * behaviour here. See bug * 54047. */ if (exact && s [valuePos] != '/') return false; if (_ParseString (s, valuePos, 0, dfi.TimeSeparator, out num_parsed) || Char.IsDigit (s [valuePos]) || Char.IsLetter (s [valuePos])) { return(false); } num = 0; if (num_parsed <= 0) { num_parsed = 1; } break; default: if (s [valuePos] != chars[pos]) { // FIXME: It is not sure, but // IsLetter() is introduced // because we have to reject // "2002a02b25" but have to // allow "2002$02$25". The same // thing applies to '/' case. if (exact || Char.IsDigit (s [valuePos]) || Char.IsLetter (s [valuePos])) return false; } num = 0; num_parsed = 1; break; } if (num_parsed < 0) return false; valuePos += num_parsed; if (!exact) { switch (chars [pos]) { case 'm': case 's': case 'f': case 'z': if (s.Length > valuePos && s [valuePos] == 'Z' && (pos + 1 == chars.Length || chars [pos + 1] != 'Z')) { useutc = true; valuePos++; } break; } } pos = pos + num + 1; num = 0; } // possible empty value. Regarded as no match. if (pos == 0) return false; if (pos < len) return false; if (s.Length != valuePos) // extraneous tail. return false; if (hour == -1) hour = 0; if (minute == -1) minute = 0; if (second == -1) second = 0; if (fractionalSeconds == -1) fractionalSeconds = 0; // If no date was given if ((day == -1) && (month == -1) && (year == -1)) { if ((style & DateTimeStyles.NoCurrentDateDefault) != 0) { day = 1; month = 1; year = 1; } else { day = Today.Day; month = Today.Month; year = Today.Year; } } if (day == -1) day = 1; if (month == -1) month = 1; if (year == -1) { if ((style & DateTimeStyles.NoCurrentDateDefault) != 0) year = 1; else year = Today.Year; } if (ampm == 1) hour = hour + 12; // For anything out of range // return false if ( year < 1 || year > 9999 || month < 1 || month >12 || day < 1 || day > DaysInMonth(year, month) || hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59 ) return false; result = new DateTime (year, month, day, hour, minute, second, 0); result = result.AddSeconds(fractionalSeconds); if ((dayofweek != -1) && (dayofweek != (int) result.DayOfWeek)) throw new FormatException (Locale.GetText ("String was not recognized as valid DateTime because the day of week was incorrect.")); // If no timezone was specified, default to the local timezone. TimeSpan utcoffset; if (useutc) { if ((style & DateTimeStyles.AdjustToUniversal) != 0) use_localtime = false; utcoffset = new TimeSpan (0, 0, 0); } else if (tzsign == -1) { TimeZone tz = TimeZone.CurrentTimeZone; utcoffset = tz.GetUtcOffset (result); } else { if ((style & DateTimeStyles.AdjustToUniversal) != 0) use_localtime = false; if (tzoffmin == -1) tzoffmin = 0; if (tzoffset == -1) tzoffset = 0; if (tzsign == 1) tzoffset = -tzoffset ; utcoffset = new TimeSpan (tzoffset, tzoffmin, 0); } long newticks = (result.ticks - utcoffset).Ticks; result = new DateTime (false, new TimeSpan (newticks)); if (use_localtime) result = result.ToLocalTime (); return true; } public static DateTime ParseExact (string s, string format, IFormatProvider fp, DateTimeStyles style) { string[] formats; formats = new string [1]; formats[0] = format; return ParseExact (s, formats, fp, style); } public static DateTime ParseExact (string s, string[] formats, IFormatProvider fp, DateTimeStyles style) { DateTimeFormatInfo dfi = DateTimeFormatInfo.GetInstance (fp); if (s == null) throw new ArgumentNullException (Locale.GetText ("s is null")); if (formats == null || formats.Length == 0) throw new ArgumentNullException (Locale.GetText ("format is null")); DateTime result; bool longYear = false; if (!ParseExact (s, formats, dfi, style, out result, true, ref longYear)) throw new FormatException (); return result; } #if NET_2_0 public static bool TryParse (string s, out DateTime result) { try { result = Parse (s); } catch { result = MinValue; return false; } return true; } public static bool TryParse (string s, IFormatProvider provider, DateTimeStyles styles, out DateTime result) { try { result = Parse (s, provider, styles); } catch { result = MinValue; return false; } return true; } public static bool TryParseExact (string s, string format, IFormatProvider fp, DateTimeStyles style, out DateTime result) { string[] formats; formats = new string [1]; formats[0] = format; return TryParseExact (s, formats, fp, style, out result); } public static bool TryParseExact (string s, string[] formats, IFormatProvider fp, DateTimeStyles style, out DateTime result) { DateTimeFormatInfo dfi = DateTimeFormatInfo.GetInstance (fp); bool longYear = false; return ParseExact (s, formats, dfi, style, out result, true, ref longYear); } #endif private static bool ParseExact (string s, string [] formats, DateTimeFormatInfo dfi, DateTimeStyles style, out DateTime ret, bool exact, ref bool longYear) { int i; for (i = 0; i < formats.Length; i++) { DateTime result; if (_DoParse (s, formats[i], exact, out result, dfi, style, ref longYear)) { ret = result; return true; } } ret = DateTime.MinValue; return false; } public TimeSpan Subtract(DateTime dt) { return new TimeSpan(ticks.Ticks) - dt.ticks; } public DateTime Subtract(TimeSpan ts) { TimeSpan newticks; newticks = (new TimeSpan (ticks.Ticks)) - ts; return new DateTime(true,newticks); } public long ToFileTime() { DateTime universalTime = ToUniversalTime(); if (universalTime.Ticks < w32file_epoch) { throw new ArgumentOutOfRangeException("file time is not valid"); } return(universalTime.Ticks - w32file_epoch); } #if NET_1_1 public long ToFileTimeUtc() { if (Ticks < w32file_epoch) { throw new ArgumentOutOfRangeException("file time is not valid"); } return (Ticks - w32file_epoch); } #endif public string ToLongDateString() { return ToString ("D"); } public string ToLongTimeString() { return ToString ("T"); } public double ToOADate () { long t = this.Ticks; // uninitialized DateTime case if (t == 0) return 0; // we can't reach minimum value if (t < 31242239136000000) return OAMinValue + 0.001; TimeSpan ts = new TimeSpan (this.Ticks - ticks18991230); double result = ts.TotalDays; // t < 0 (where 599264352000000000 == 0.0d for OA) if (t < 599264352000000000) { // negative days (int) but decimals are positive double d = Math.Ceiling (result); result = d - 2 - (result - d); } else { // we can't reach maximum value if (result >= OAMaxValue) result = OAMaxValue - 0.00000001d; } return result; } public string ToShortDateString() { return ToString ("d"); } public string ToShortTimeString() { return ToString ("t"); } public override string ToString () { return ToString ("G", null); } public string ToString (IFormatProvider fp) { return ToString (null, fp); } public string ToString (string format) { return ToString (format, null); } internal static string _GetStandardPattern (char format, DateTimeFormatInfo dfi, out bool useutc, out bool use_invariant) { String pattern; useutc = false; use_invariant = false; switch (format) { case 'd': pattern = dfi.ShortDatePattern; break; case 'D': pattern = dfi.LongDatePattern; break; case 'f': pattern = dfi.LongDatePattern + " " + dfi.ShortTimePattern; break; case 'F': pattern = dfi.FullDateTimePattern; break; case 'g': pattern = dfi.ShortDatePattern + " " + dfi.ShortTimePattern; break; case 'G': pattern = dfi.ShortDatePattern + " " + dfi.LongTimePattern; break; case 'm': case 'M': pattern = dfi.MonthDayPattern; break; case 'r': case 'R': pattern = dfi.RFC1123Pattern; // commented by LP 09/jun/2002, rfc 1123 pattern is always in GMT // uncommented by AE 27/may/2004 // useutc = true; use_invariant = true; break; case 's': pattern = dfi.SortableDateTimePattern; break; case 't': pattern = dfi.ShortTimePattern; break; case 'T': pattern = dfi.LongTimePattern; break; case 'u': pattern = dfi.UniversalSortableDateTimePattern; useutc = true; break; case 'U': // pattern = dfi.LongDatePattern + " " + dfi.LongTimePattern; pattern = dfi.FullDateTimePattern; useutc = true; break; case 'y': case 'Y': pattern = dfi.YearMonthPattern; break; default: pattern = null; break; } return pattern; } internal string _ToString (string format, DateTimeFormatInfo dfi) { // the length of the format is usually a good guess of the number // of chars in the result. Might save us a few bytes sometimes // Add + 10 for cases like mmmm dddd StringBuilder result = new StringBuilder (format.Length + 10); // For some cases, the output should not use culture dependent calendar DateTimeFormatInfo inv = DateTimeFormatInfo.InvariantInfo; if (format == inv.RFC1123Pattern) dfi = inv; else if (format == inv.UniversalSortableDateTimePattern) dfi = inv; int i = 0; while (i < format.Length) { int tokLen; char ch = format [i]; switch (ch) { // // Time Formats // case 'h': // hour, [1, 12] tokLen = CountRepeat (format, i, ch); int hr = this.Hour % 12; if (hr == 0) hr = 12; ZeroPad (result, hr, tokLen == 1 ? 1 : 2); break; case 'H': // hour, [0, 23] tokLen = CountRepeat (format, i, ch); ZeroPad (result, this.Hour, tokLen == 1 ? 1 : 2); break; case 'm': // minute, [0, 59] tokLen = CountRepeat (format, i, ch); ZeroPad (result, this.Minute, tokLen == 1 ? 1 : 2); break; case 's': // second [0, 29] tokLen = CountRepeat (format, i, ch); ZeroPad (result, this.Second, tokLen == 1 ? 1 : 2); break; case 'f': // fraction of second, to same number of // digits as there are f's tokLen = CountRepeat (format, i, ch); if (tokLen > 7) throw new FormatException ("Invalid Format String"); int dec = (int)((long)(this.Ticks % TimeSpan.TicksPerSecond) / (long) Math.Pow (10, 7 - tokLen)); ZeroPad (result, dec, tokLen); break; case 't': // AM/PM. t == first char, tt+ == full tokLen = CountRepeat (format, i, ch); string desig = this.Hour < 12 ? dfi.AMDesignator : dfi.PMDesignator; if (tokLen == 1) { if (desig.Length >= 1) result.Append (desig [0]); } else result.Append (desig); break; case 'z': // timezone. t = +/-h; tt = +/-hh; ttt+=+/-hh:mm tokLen = CountRepeat (format, i, ch); TimeSpan offset = TimeZone.CurrentTimeZone.GetUtcOffset (this); if (offset.Ticks >= 0) result.Append ('+'); else result.Append ('-'); switch (tokLen) { case 1: result.Append (Math.Abs (offset.Hours)); break; case 2: result.Append (Math.Abs (offset.Hours).ToString ("00")); break; default: result.Append (Math.Abs (offset.Hours).ToString ("00")); result.Append (':'); result.Append (Math.Abs (offset.Minutes).ToString ("00")); break; } break; // // Date tokens // case 'd': // day. d(d?) = day of month (leading 0 if two d's) // ddd = three leter day of week // dddd+ full day-of-week tokLen = CountRepeat (format, i, ch); if (tokLen <= 2) ZeroPad (result, dfi.Calendar.GetDayOfMonth (this), tokLen == 1 ? 1 : 2); else if (tokLen == 3) result.Append (dfi.GetAbbreviatedDayName (dfi.Calendar.GetDayOfWeek (this))); else result.Append (dfi.GetDayName (dfi.Calendar.GetDayOfWeek (this))); break; case 'M': // Month.m(m?) = month # (with leading 0 if two mm) // mmm = 3 letter name // mmmm+ = full name tokLen = CountRepeat (format, i, ch); int month = dfi.Calendar.GetMonth(this); if (tokLen <= 2) ZeroPad (result, month, tokLen); else if (tokLen == 3) result.Append (dfi.GetAbbreviatedMonthName (month)); else result.Append (dfi.GetMonthName (month)); break; case 'y': // Year. y(y?) = two digit year, with leading 0 if yy // yyy+ full year, if yyy and yr < 1000, displayed as three digits tokLen = CountRepeat (format, i, ch); if (tokLen <= 2) ZeroPad (result, dfi.Calendar.GetYear (this) % 100, tokLen); else ZeroPad (result, dfi.Calendar.GetYear (this), (tokLen == 3 ? 3 : 4)); break; case 'g': // Era name tokLen = CountRepeat (format, i, ch); result.Append (dfi.GetEraName (dfi.Calendar.GetEra (this))); break; // // Other // case ':': result.Append (dfi.TimeSeparator); tokLen = 1; break; case '/': result.Append (dfi.DateSeparator); tokLen = 1; break; case '\'': case '"': tokLen = ParseQuotedString (format, i, result); break; case '%': if (i >= format.Length - 1) throw new FormatException ("% at end of date time string"); if (format [i + 1] == '%') throw new FormatException ("%% in date string"); // Look for the next char tokLen = 1; break; case '\\': // C-Style escape if (i >= format.Length - 1) throw new FormatException ("\\ at end of date time string"); result.Append (format [i + 1]); tokLen = 2; break; default: // catch all result.Append (ch); tokLen = 1; break; } i += tokLen; } return result.ToString (); } static int CountRepeat (string fmt, int p, char c) { int l = fmt.Length; int i = p + 1; while ((i < l) && (fmt [i] == c)) i++; return i - p; } static int ParseQuotedString (string fmt, int pos, StringBuilder output) { // pos == position of " or ' int len = fmt.Length; int start = pos; char quoteChar = fmt [pos++]; while (pos < len) { char ch = fmt [pos++]; if (ch == quoteChar) return pos - start; if (ch == '\\') { // C-Style escape if (pos >= len) throw new FormatException("Un-ended quote"); output.Append (fmt [pos++]); } else { output.Append (ch); } } throw new FormatException("Un-ended quote"); } static unsafe void ZeroPad (StringBuilder output, int digits, int len) { // more than enough for an int char* buffer = stackalloc char [16]; int pos = 16; do { buffer [-- pos] = (char) ('0' + digits % 10); digits /= 10; len --; } while (digits > 0); while (len -- > 0) buffer [-- pos] = '0'; output.Append (new string (buffer, pos, 16 - pos)); } public string ToString (string format, IFormatProvider fp) { DateTimeFormatInfo dfi = DateTimeFormatInfo.GetInstance(fp); if (format == null) format = "G"; bool useutc = false, use_invariant = false; if (format.Length == 1) { char fchar = format [0]; format = _GetStandardPattern (fchar, dfi, out useutc, out use_invariant); } // Don't convert UTC value. It just adds 'Z' for // 'u' format, for the same ticks. return this._ToString (format, dfi); } public DateTime ToLocalTime () { return TimeZone.CurrentTimeZone.ToLocalTime (this); } public DateTime ToUniversalTime() { return TimeZone.CurrentTimeZone.ToUniversalTime (this); } /* OPERATORS */ public static DateTime operator +(DateTime d, TimeSpan t) { return new DateTime (true, d.ticks + t); } public static bool operator ==(DateTime d1, DateTime d2) { return (d1.ticks == d2.ticks); } public static bool operator >(DateTime t1,DateTime t2) { return (t1.ticks > t2.ticks); } public static bool operator >=(DateTime t1,DateTime t2) { return (t1.ticks >= t2.ticks); } public static bool operator !=(DateTime d1, DateTime d2) { return (d1.ticks != d2.ticks); } public static bool operator <(DateTime t1, DateTime t2) { return (t1.ticks < t2.ticks ); } public static bool operator <=(DateTime t1,DateTime t2) { return (t1.ticks <= t2.ticks); } public static TimeSpan operator -(DateTime d1,DateTime d2) { return new TimeSpan((d1.ticks - d2.ticks).Ticks); } public static DateTime operator -(DateTime d,TimeSpan t) { return new DateTime (true, d.ticks - t); } bool IConvertible.ToBoolean(IFormatProvider provider) { throw new InvalidCastException(); } byte IConvertible.ToByte(IFormatProvider provider) { throw new InvalidCastException(); } char IConvertible.ToChar(IFormatProvider provider) { throw new InvalidCastException(); } System.DateTime IConvertible.ToDateTime(IFormatProvider provider) { return this; } decimal IConvertible.ToDecimal(IFormatProvider provider) { throw new InvalidCastException(); } double IConvertible.ToDouble(IFormatProvider provider) { throw new InvalidCastException(); } Int16 IConvertible.ToInt16(IFormatProvider provider) { throw new InvalidCastException(); } Int32 IConvertible.ToInt32(IFormatProvider provider) { throw new InvalidCastException(); } Int64 IConvertible.ToInt64(IFormatProvider provider) { throw new InvalidCastException(); } SByte IConvertible.ToSByte(IFormatProvider provider) { throw new InvalidCastException(); } Single IConvertible.ToSingle(IFormatProvider provider) { throw new InvalidCastException(); } object IConvertible.ToType (Type conversionType, IFormatProvider provider) { if (conversionType == null) throw new ArgumentNullException ("conversionType"); if (conversionType == typeof (DateTime)) return this; else if (conversionType == typeof (String)) return this.ToString (provider); else if (conversionType == typeof (Object)) return this; else throw new InvalidCastException(); } UInt16 IConvertible.ToUInt16(IFormatProvider provider) { throw new InvalidCastException(); } UInt32 IConvertible.ToUInt32(IFormatProvider provider) { throw new InvalidCastException(); } UInt64 IConvertible.ToUInt64(IFormatProvider provider) { throw new InvalidCastException(); } } }