5 // Marcel Narings (marcel@narings.nl)
6 // Martin Baulig (martin@gnome.org)
7 // Atsushi Enomoto (atsushi@ximian.com)
9 // (C) 2001 Marcel Narings
12 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
25 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System
.Collections
;
36 using System
.Globalization
;
37 using System
.Runtime
.CompilerServices
;
38 using System
.Runtime
.InteropServices
;
44 /// The DateTime structure represents dates and time ranging from
45 /// 1-1-0001 12:00:00 AM to 31-12-9999 23:59:00 Common Era.
49 [StructLayout (LayoutKind
.Auto
)]
50 public struct DateTime
: IFormattable
, IConvertible
,
52 IComparable
, IComparable
<DateTime
>
57 private TimeSpan ticks
;
59 private const int dp400
= 146097;
60 private const int dp100
= 36524;
61 private const int dp4
= 1461;
63 // w32 file time starts counting from 1/1/1601 00:00 GMT
64 // which is the constant ticks from the .NET epoch
65 private const long w32file_epoch
= 504911232000000000L;
67 //private const long MAX_VALUE_TICKS = 3155378975400000000L;
68 // -- Microsoft .NET has this value.
69 private const long MAX_VALUE_TICKS
= 3155378975999999999L;
72 // The UnixEpoch, it begins on Jan 1, 1970 at 0:0:0, expressed
75 internal const long UnixEpoch
= 621355968000000000L;
77 // for OLE Automation dates
78 private const long ticks18991230
= 599264352000000000L;
79 private const double OAMinValue
= -657435.0d
;
80 private const double OAMaxValue
= 2958466.0d
;
82 public static readonly DateTime MaxValue
= new DateTime (false, MAX_VALUE_TICKS
);
83 public static readonly DateTime MinValue
= new DateTime (false, 0);
85 private static readonly string[] commonFormats
= {
86 // For compatibility with MS's CLR, this format (which
87 // doesn't have a one-letter equivalent) is parsed
88 // too. It's important because it's used in XML
91 // Note that those format should be tried only for
94 // FIXME: SOME OF those patterns looks tried against
95 // the current culture, since some patterns fail in
99 "yyyy-MM-ddTHH:mm:sszzz",
100 "yyyy-MM-ddTHH:mm:ss.fffffff",
101 "yyyy-MM-ddTHH:mm:ss.fffffffzzz",
102 // UTC / allow any separator
103 "yyyy/MM/ddTHH:mm:ssZ",
108 "yyyy/MM/dd HH:mm:ss 'GMT'",
109 // Close to RFC1123, but without 'GMT'
110 "ddd, d MMM yyyy HH:mm:ss",
111 // use UTC ('Z'; not literal "'Z'")
112 // FIXME: 1078(af-ZA) and 1079(ka-GE) reject it
113 "yyyy/MM/dd HH':'mm':'ssZ",
115 // DayOfTheWeek, dd full_month_name yyyy
116 // FIXME: 1054(th-TH) rejects it
117 "dddd, dd MMMM yyyy",
118 // DayOfTheWeek, dd yyyy. This works for every locales.
121 // X509Certificate pattern is accepted by Parse() *in every culture*
124 // In Parse() the 'r' equivalent pattern is first parsed as universal time
125 "ddd, dd MMM yyyy HH':'mm':'ss 'GMT'",
127 // Full date and time
128 "F", "G", "r", "s", "u", "U",
129 // Full date and time, but no seconds
135 // Only date, but no year
137 // Only date, but no day
150 private static readonly int[] daysmonth
= { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
;
151 private static readonly int[] daysmonthleap
= { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
;
153 private static int AbsoluteDays (int year
, int month
, int day
)
162 days
= (IsLeapYear(year
) ? daysmonthleap
: daysmonth
);
166 return ((day
-1) + temp
+ (365* (year
-1)) + ((year
-1)/4) - ((year
-1)/100) + ((year
-1)/400));
169 private int FromTicks(Which what
)
171 int num400
, num100
, num4
, numyears
;
174 int[] days
= daysmonth
;
175 int totaldays
= this.ticks
.Days
;
177 num400
= (totaldays
/ dp400
);
178 totaldays
-= num400
* dp400
;
180 num100
= (totaldays
/ dp100
);
181 if (num100
== 4) // leap
183 totaldays
-= (num100
* dp100
);
185 num4
= totaldays
/ dp4
;
186 totaldays
-= (num4
* dp4
);
188 numyears
= totaldays
/ 365 ;
190 if (numyears
== 4) //leap
192 if (what
== Which
.Year
)
193 return num400
*400 + num100
*100 + num4
*4 + numyears
+ 1;
195 totaldays
-= (numyears
* 365) ;
196 if (what
== Which
.DayYear
)
197 return totaldays
+ 1;
199 if ((numyears
==3) && ((num100
== 3) || !(num4
== 24)) ) //31 dec leapyear
200 days
= daysmonthleap
;
202 while (totaldays
>= days
[M
])
203 totaldays
-= days
[M
++];
205 if (what
== Which
.Month
)
215 /// Constructs a DateTime for specified ticks
218 public DateTime (long newticks
)
219 // `local' must default to false here to avoid
221 : this (false, newticks
) {}
223 internal DateTime (bool local
, long newticks
)
225 ticks
= new TimeSpan (newticks
);
227 TimeZone tz
= TimeZone
.CurrentTimeZone
;
229 TimeSpan utcoffset
= tz
.GetUtcOffset (this);
231 ticks
= ticks
+ utcoffset
;
233 if (ticks
.Ticks
< MinValue
.Ticks
|| ticks
.Ticks
> MaxValue
.Ticks
)
234 throw new ArgumentOutOfRangeException ();
237 public DateTime (int year
, int month
, int day
)
238 : this (year
, month
, day
,0,0,0,0) {}
240 public DateTime (int year
, int month
, int day
, int hour
, int minute
, int second
)
241 : this (year
, month
, day
, hour
, minute
, second
, 0) {}
243 public DateTime (int year
, int month
, int day
, int hour
, int minute
, int second
, int millisecond
)
245 if ( year
< 1 || year
> 9999 ||
246 month
< 1 || month
>12 ||
247 day
< 1 || day
> DaysInMonth(year
, month
) ||
248 hour
< 0 || hour
> 23 ||
249 minute
< 0 || minute
> 59 ||
250 second
< 0 || second
> 59 ||
251 millisecond
< 0 || millisecond
> 999)
252 throw new ArgumentOutOfRangeException ("Parameters describe an " +
253 "unrepresentable DateTime.");
255 ticks
= new TimeSpan (AbsoluteDays(year
,month
,day
), hour
, minute
, second
, millisecond
);
258 public DateTime (int year
, int month
, int day
, Calendar calendar
)
259 : this (year
, month
, day
, 0, 0, 0, 0, calendar
) {}
262 public DateTime (int year
, int month
, int day
, int hour
, int minute
, int second
, Calendar calendar
)
263 : this (year
, month
, day
, hour
, minute
, second
, 0, calendar
) {}
266 public DateTime (int year
, int month
, int day
, int hour
, int minute
, int second
, int millisecond
, Calendar calendar
)
267 : this (year
, month
, day
, hour
, minute
, second
, millisecond
)
269 if (calendar
== null)
270 throw new ArgumentNullException();
273 internal DateTime (bool check
, TimeSpan
value)
275 if (check
&& (value.Ticks
< MinValue
.Ticks
|| value.Ticks
> MaxValue
.Ticks
))
276 throw new ArgumentOutOfRangeException ();
287 return new DateTime (Year
, Month
, Day
);
295 return FromTicks(Which
.Month
);
304 return FromTicks(Which
.Day
);
308 public DayOfWeek DayOfWeek
312 return ( (DayOfWeek
) ((ticks
.Days
+1) % 7) );
320 return FromTicks(Which
.DayYear
);
324 public TimeSpan TimeOfDay
328 return new TimeSpan(ticks
.Ticks
% TimeSpan
.TicksPerDay
);
345 return ticks
.Minutes
;
353 return ticks
.Seconds
;
357 public int Millisecond
361 return ticks
.Milliseconds
;
365 [MethodImplAttribute(MethodImplOptions
.InternalCall
)]
366 internal static extern long GetNow ();
368 public static DateTime Now
372 return new DateTime (true, GetNow ());
384 public static DateTime Today
388 return new DateTime (now
.Year
, now
.Month
, now
.Day
);
392 public static DateTime UtcNow
395 return new DateTime (GetNow ());
403 return FromTicks(Which
.Year
);
409 public DateTime
Add (TimeSpan ts
)
411 return AddTicks (ts
.Ticks
);
414 public DateTime
AddDays (double days
)
416 return AddMilliseconds (Math
.Round (days
* 86400000));
419 public DateTime
AddTicks (long t
)
421 if ((t
+ ticks
.Ticks
) > MAX_VALUE_TICKS
|| (t
+ ticks
.Ticks
) < 0) {
422 throw new ArgumentOutOfRangeException();
424 return new DateTime (t
+ ticks
.Ticks
);
427 public DateTime
AddHours (double hours
)
429 return AddMilliseconds (hours
* 3600000);
432 public DateTime
AddMilliseconds (double ms
)
434 if ((ms
* TimeSpan
.TicksPerMillisecond
) > long.MaxValue
||
435 (ms
* TimeSpan
.TicksPerMillisecond
) < long.MinValue
) {
436 throw new ArgumentOutOfRangeException();
438 long msticks
= (long) (ms
* TimeSpan
.TicksPerMillisecond
);
440 return AddTicks (msticks
);
443 // required to match MS implementation for OADate (OLE Automation)
444 private DateTime
AddRoundedMilliseconds (double ms
)
446 if ((ms
* TimeSpan
.TicksPerMillisecond
) > long.MaxValue
||
447 (ms
* TimeSpan
.TicksPerMillisecond
) < long.MinValue
) {
448 throw new ArgumentOutOfRangeException ();
450 long msticks
= (long) (ms
+= ms
> 0 ? 0.5 : -0.5) * TimeSpan
.TicksPerMillisecond
;
452 return AddTicks (msticks
);
455 public DateTime
AddMinutes (double minutes
)
457 return AddMilliseconds (minutes
* 60000);
460 public DateTime
AddMonths (int months
)
462 int day
, month
, year
, maxday
;
466 month
= this.Month
+ (months
% 12);
467 year
= this.Year
+ months
/12 ;
479 maxday
= DaysInMonth(year
, month
);
483 temp
= new DateTime (year
, month
, day
);
484 return temp
.Add (this.TimeOfDay
);
487 public DateTime
AddSeconds (double seconds
)
489 return AddMilliseconds (seconds
*1000);
492 public DateTime
AddYears (int years
)
494 return AddMonths(years
* 12);
497 public static int Compare (DateTime t1
, DateTime t2
)
499 if (t1
.ticks
< t2
.ticks
)
501 else if (t1
.ticks
> t2
.ticks
)
507 public int CompareTo (object v
)
512 if (!(v
is System
.DateTime
))
513 throw new ArgumentException (Locale
.GetText (
514 "Value is not a System.DateTime"));
516 return Compare (this, (DateTime
) v
);
520 public int CompareTo (DateTime
value)
522 return Compare (this, value);
525 public bool Equals (DateTime
value)
527 return value.ticks
== ticks
;
531 public static int DaysInMonth (int year
, int month
)
535 if (month
< 1 || month
>12)
536 throw new ArgumentOutOfRangeException ();
538 days
= (IsLeapYear(year
) ? daysmonthleap
: daysmonth
);
542 public override bool Equals (object o
)
544 if (!(o
is System
.DateTime
))
547 return ((DateTime
) o
).ticks
== ticks
;
550 public static bool Equals (DateTime t1
, DateTime t2
)
552 return (t1
.ticks
== t2
.ticks
);
555 public static DateTime
FromFileTime (long fileTime
)
558 throw new ArgumentOutOfRangeException ("fileTime", "< 0");
560 return new DateTime (true, w32file_epoch
+ fileTime
);
564 public static DateTime
FromFileTimeUtc (long fileTime
)
567 throw new ArgumentOutOfRangeException ("fileTime", "< 0");
569 return new DateTime (false, w32file_epoch
+ fileTime
);
573 public static DateTime
FromOADate (double d
)
575 // An OLE Automation date is implemented as a floating-point number
576 // whose value is the number of days from midnight, 30 December 1899.
578 // d must be negative 657435.0 through positive 2958466.0.
579 if ((d
<= OAMinValue
) || (d
>= OAMaxValue
))
580 throw new ArgumentException ("d", "[-657435,2958466]");
582 DateTime dt
= new DateTime (ticks18991230
);
584 Double days
= Math
.Ceiling (d
);
585 // integer part is the number of days (negative)
586 dt
= dt
.AddRoundedMilliseconds (days
* 86400000);
587 // but decimals are the number of hours (in days fractions) and positive
588 Double hours
= (days
- d
);
589 dt
= dt
.AddRoundedMilliseconds (hours
* 86400000);
592 dt
= dt
.AddRoundedMilliseconds (d
* 86400000);
598 public string[] GetDateTimeFormats()
600 return GetDateTimeFormats (CultureInfo
.CurrentCulture
);
603 public string[] GetDateTimeFormats(char format
)
605 if ("dDgGfFmMrRstTuUyY".IndexOf (format
) < 0)
606 throw new FormatException ("Invalid format character.");
607 string[] result
= new string[1];
608 result
[0] = this.ToString(format
.ToString());
612 public string[] GetDateTimeFormats(IFormatProvider provider
)
614 DateTimeFormatInfo info
= (DateTimeFormatInfo
) provider
.GetFormat (typeof(DateTimeFormatInfo
));
615 // return GetDateTimeFormats (info.GetAllDateTimePatterns ());
616 ArrayList al
= new ArrayList ();
617 foreach (char c
in "dDgGfFmMrRstTuUyY")
618 al
.AddRange (GetDateTimeFormats (c
, info
));
619 return al
.ToArray (typeof (string)) as string [];
622 public string[] GetDateTimeFormats(char format
,IFormatProvider provider
)
624 if ("dDgGfFmMrRstTuUyY".IndexOf (format
) < 0)
625 throw new FormatException ("Invalid format character.");
627 // LAMESPEC: There is NO assurance that 'U' ALWAYS
628 // euqals to 'F', but since we have to iterate all
629 // the pattern strings, we cannot just use
630 // ToString("U", provider) here. I believe that the
631 // method's behavior cannot be formalized.
632 bool adjustutc
= false;
641 DateTimeFormatInfo info
= (DateTimeFormatInfo
) provider
.GetFormat (typeof(DateTimeFormatInfo
));
642 return GetDateTimeFormats (adjustutc
, info
.GetAllDateTimePatterns (format
), info
);
645 private string [] GetDateTimeFormats (bool adjustutc
, string [] patterns
, DateTimeFormatInfo dfi
)
647 string [] results
= new string [patterns
.Length
];
648 DateTime val
= adjustutc
? ToUniversalTime () : this;
649 for (int i
= 0; i
< results
.Length
; i
++)
650 results
[i
] = val
._ToString (patterns
[i
], dfi
);
654 public override int GetHashCode ()
656 return (int) ticks
.Ticks
;
659 public TypeCode
GetTypeCode ()
661 return TypeCode
.DateTime
;
664 public static bool IsLeapYear (int year
)
666 return ( (year
% 4 == 0 && year
% 100 != 0) || year
% 400 == 0) ;
669 public static DateTime
Parse (string s
)
671 return Parse (s
, null);
674 public static DateTime
Parse (string s
, IFormatProvider fp
)
676 return Parse (s
, fp
, DateTimeStyles
.AllowWhiteSpaces
);
679 [MonoTODO ("see the comments inline")]
680 public static DateTime
Parse (string s
, IFormatProvider fp
, DateTimeStyles styles
)
682 // This method should try only expected patterns.
683 // Should not try extra patterns.
684 // Right now we also try InvariantCulture, but I
685 // confirmed in some cases this method rejects what
686 // InvariantCulture supports (can be checked against
687 // "th-TH" with Gregorian Calendar). So basically it
688 // should not be done.
689 // I think it should be CurrentCulture to be tested,
690 // but right now we don't support all the supported
691 // patterns for each culture, so try InvariantCulture
692 // as a quick remedy.
694 throw new ArgumentNullException (Locale
.GetText ("s is null"));
698 fp
= CultureInfo
.CurrentCulture
;
699 DateTimeFormatInfo dfi
= DateTimeFormatInfo
.GetInstance (fp
);
701 bool longYear
= false;
702 // Try common formats.
703 if (ParseExact (s
, commonFormats
, dfi
, styles
, out result
, false, ref longYear
))
706 // Try common formats, also with invariant culture
707 if (ParseExact (s
, commonFormats
, DateTimeFormatInfo
.InvariantInfo
, styles
, out result
, false, ref longYear
))
710 // Next, try all the patterns
711 string [] patterns
= new string [] {"d", "D", "g", "G", "f", "F", "m", "M", "r", "R", "s", "t", "T", "u", "U", "y", "Y"}
;
713 if (ParseExact (s
, patterns
, dfi
, styles
, out result
, false, ref longYear
))
717 throw new ArgumentOutOfRangeException ("year",
718 "Valid values are between 1 and 9999 inclusive");
721 throw new FormatException ("String was not recognized as a valid DateTime.");
724 public static DateTime
ParseExact (string s
, string format
, IFormatProvider fp
)
726 return ParseExact (s
, format
, fp
, DateTimeStyles
.None
);
729 internal static int _ParseNumber (string s
, int valuePos
,
743 for (i
= valuePos
; i
< s
.Length
&& i
< digits
+ valuePos
; i
++) {
744 if (!Char
.IsDigit (s
[i
]))
750 digits
= real_digits
;
753 if (s
.Length
- valuePos
< digits
) {
758 if (s
.Length
- valuePos
> digits
&&
760 Char
.IsDigit (s
[digits
+ valuePos
])) {
761 /* More digits left over */
766 for (i
= valuePos
; i
< digits
+ valuePos
; i
++) {
768 if (!Char
.IsDigit (c
)) {
773 number
= number
* 10 + (byte) (c
- '0');
780 internal static int _ParseEnum (string s
, int sPos
, string[] values
, out int num_parsed
)
784 for (i
= 0; i
< values
.Length
; i
++) {
785 if (s
.Length
- sPos
< values
[i
].Length
)
787 else if (values
[i
].Length
== 0)
789 String tmp
= s
.Substring (sPos
, values
[i
].Length
);
790 if (String
.Compare (tmp
, values
[i
], true) == 0) {
791 num_parsed
= values
[i
].Length
;
800 internal static bool _ParseString (string s
, int sPos
, int maxlength
, string value, out int num_parsed
)
803 maxlength
= value.Length
;
805 if (String
.Compare (s
, sPos
, value, 0, maxlength
, true, CultureInfo
.InvariantCulture
) == 0) {
806 num_parsed
= maxlength
;
814 private static bool _DoParse (string s
, string format
, bool exact
,
816 DateTimeFormatInfo dfi
,
817 DateTimeStyles style
,
820 bool useutc
= false, use_localtime
= true;
821 bool use_invariant
= false;
822 bool sloppy_parsing
= false;
824 if (format
.Length
== 1)
825 format
= _GetStandardPattern (format
[0], dfi
, out useutc
, out use_invariant
);
826 else if (!exact
&& format
.IndexOf ("GMT") >= 0)
829 if ((style
& DateTimeStyles
.AllowLeadingWhite
) != 0) {
830 format
= format
.TrimStart (null);
832 s
= s
.TrimStart (null); // it could be optimized, but will make little good.
835 if ((style
& DateTimeStyles
.AllowTrailingWhite
) != 0) {
836 format
= format
.TrimEnd (null);
837 s
= s
.TrimEnd (null); // it could be optimized, but will make little good.
841 dfi
= DateTimeFormatInfo
.InvariantInfo
;
843 if ((style
& DateTimeStyles
.AllowInnerWhite
) != 0)
844 sloppy_parsing
= true;
846 string chars
= format
;
847 int len
= format
.Length
, pos
= 0, num
= 0;
849 int day
= -1, dayofweek
= -1, month
= -1, year
= -1;
850 int hour
= -1, minute
= -1, second
= -1;
851 double fractionalSeconds
= -1;
853 int tzsign
= -1, tzoffset
= -1, tzoffmin
= -1;
856 result
= new DateTime (0);
857 while (pos
+num
< len
)
859 if (s
.Length
== valuePos
)
862 if (chars
[pos
] == '\'') {
864 while (pos
+num
< len
) {
865 if (chars
[pos
+num
] == '\'')
868 if (valuePos
== s
.Length
)
870 if (s
[valuePos
] != chars
[pos
+ num
])
882 } else if (chars
[pos
] == '"') {
884 while (pos
+num
< len
) {
885 if (chars
[pos
+num
] == '"')
888 if (valuePos
== s
.Length
)
890 if (s
[valuePos
] != chars
[pos
+num
])
902 } else if (chars
[pos
] == '\\') {
906 if (s
[valuePos
] != chars
[pos
+ num
])
909 if (valuePos
== s
.Length
)
914 } else if (chars
[pos
] == '%') {
917 } else if (Char
.IsWhiteSpace (s
[valuePos
])) {
920 if (Char
.IsWhiteSpace (chars
[pos
])) {
925 if (exact
&& (style
& DateTimeStyles
.AllowInnerWhite
) == 0)
928 while (ws
< s
.Length
) {
929 if (Char
.IsWhiteSpace (s
[ws
]))
938 if ((pos
+num
+1 < len
) && (chars
[pos
+num
+1] == chars
[pos
+num
])) {
945 if (pos
+num
+1 < len
) {
946 char next_char
= chars
[pos
+num
+1];
948 next_not_digit
= !(next_char
== 'd' ||
959 Char
.IsDigit (next_char
));
961 next_not_digit
= true;
970 day
= _ParseNumber (s
, valuePos
, 2, false, sloppy_parsing
, next_not_digit
, out num_parsed
);
972 day
= _ParseNumber (s
, valuePos
, 2, true, sloppy_parsing
, next_not_digit
, out num_parsed
);
974 dayofweek
= _ParseEnum (s
, valuePos
, dfi
.AbbreviatedDayNames
, out num_parsed
);
977 dayofweek
= _ParseEnum (s
, valuePos
, dfi
.DayNames
, out num_parsed
);
985 month
= _ParseNumber (s
, valuePos
, 2, false, sloppy_parsing
, next_not_digit
, out num_parsed
);
987 month
= _ParseNumber (s
, valuePos
, 2, true, sloppy_parsing
, next_not_digit
, out num_parsed
);
989 month
= _ParseEnum (s
, valuePos
, dfi
.AbbreviatedMonthNames
, out num_parsed
) + 1;
992 month
= _ParseEnum (s
, valuePos
, dfi
.MonthNames
, out num_parsed
) + 1;
1001 year
= _ParseNumber (s
, valuePos
, 2, false, sloppy_parsing
, next_not_digit
, out num_parsed
);
1002 } else if (num
< 3) {
1003 year
= _ParseNumber (s
, valuePos
, 2, true, sloppy_parsing
, next_not_digit
, out num_parsed
);
1005 year
= _ParseNumber (s
, valuePos
, 4, false, sloppy_parsing
, next_not_digit
, out num_parsed
);
1006 if ((year
>= 1000) && (num_parsed
== 4) && (!longYear
) && (s
.Length
> 4 + valuePos
)) {
1008 int ly
= _ParseNumber (s
, valuePos
, 5, false, sloppy_parsing
, next_not_digit
, out np
);
1009 longYear
= (ly
> 9999);
1014 //FIXME: We should do use dfi.Calendat.TwoDigitYearMax
1015 if (num_parsed
<= 2)
1016 year
+= (year
< 30) ? 2000 : 1900;
1022 hour
= _ParseNumber (s
, valuePos
, 2, false, sloppy_parsing
, next_not_digit
, out num_parsed
);
1025 hour
= _ParseNumber (s
, valuePos
, 2, true, sloppy_parsing
, next_not_digit
, out num_parsed
);
1036 if ((hour
!= -1) || (ampm
>= 0))
1039 hour
= _ParseNumber (s
, valuePos
, 2, false, sloppy_parsing
, next_not_digit
, out num_parsed
);
1042 hour
= _ParseNumber (s
, valuePos
, 2, true, sloppy_parsing
, next_not_digit
, out num_parsed
);
1054 minute
= _ParseNumber (s
, valuePos
, 2, false, sloppy_parsing
, next_not_digit
, out num_parsed
);
1057 minute
= _ParseNumber (s
, valuePos
, 2, true, sloppy_parsing
, next_not_digit
, out num_parsed
);
1068 second
= _ParseNumber (s
, valuePos
, 2, false, sloppy_parsing
, next_not_digit
, out num_parsed
);
1071 second
= _ParseNumber (s
, valuePos
, 2, true, sloppy_parsing
, next_not_digit
, out num_parsed
);
1079 if (fractionalSeconds
!= -1)
1081 num
= Math
.Min (num
, 6);
1082 double decimalNumber
= (double) _ParseNumber (s
, valuePos
, num
+1, true, sloppy_parsing
, next_not_digit
, out num_parsed
);
1083 if (num_parsed
== -1)
1087 fractionalSeconds
= decimalNumber
/ Math
.Pow(10.0, num_parsed
);
1094 if (_ParseString (s
, valuePos
, 1, dfi
.AMDesignator
, out num_parsed
))
1096 else if (_ParseString (s
, valuePos
, 1, dfi
.PMDesignator
, out num_parsed
))
1103 if (_ParseString (s
, valuePos
, 0, dfi
.AMDesignator
, out num_parsed
))
1105 else if (_ParseString (s
, valuePos
, 0, dfi
.PMDesignator
, out num_parsed
))
1115 if (s
[valuePos
] == '+')
1117 else if (s
[valuePos
] == '-')
1123 tzoffset
= _ParseNumber (s
, valuePos
, 2, false, sloppy_parsing
, next_not_digit
, out num_parsed
);
1125 tzoffset
= _ParseNumber (s
, valuePos
, 2, true, sloppy_parsing
, false, out num_parsed
);
1128 tzoffset
= _ParseNumber (s
, valuePos
, 2, true, sloppy_parsing
, next_not_digit
, out num_parsed
);
1131 valuePos
+= num_parsed
;
1132 if (Char
.IsDigit (s
[valuePos
]))
1134 else if (!_ParseString (s
, valuePos
, 0, dfi
.TimeSeparator
, out num_parsed
))
1136 valuePos
+= num_parsed
;
1137 tzoffmin
= _ParseNumber (s
, valuePos
, 2, true, sloppy_parsing
, false, out num_parsed
);
1144 // LAMESPEC: This should be part of UTCpattern
1145 // string and thus should not be considered here.
1147 // Note that 'Z' is not defined as a pattern
1148 // character. Keep it for X509 certificate
1149 // verification. Also, "Z" != "'Z'" under MS.NET
1150 // ("'Z'" is just literal; handled above)
1152 if (s
[valuePos
] != 'Z')
1160 if (!_ParseString (s
, valuePos
, 0, dfi
.TimeSeparator
, out num_parsed
))
1164 /* Accept any character for
1165 * DateSeparator, except
1166 * TimeSeparator, a digit or a
1167 * letter. Not documented,
1168 * but seems to be MS
1169 * behaviour here. See bug
1172 if (exact
&& s
[valuePos
] != '/')
1175 if (_ParseString (s
, valuePos
, 0,
1178 Char
.IsDigit (s
[valuePos
]) ||
1179 Char
.IsLetter (s
[valuePos
])) {
1184 if (num_parsed
<= 0) {
1190 if (s
[valuePos
] != chars
[pos
]) {
1191 // FIXME: It is not sure, but
1192 // IsLetter() is introduced
1193 // because we have to reject
1194 // "2002a02b25" but have to
1195 // allow "2002$02$25". The same
1196 // thing applies to '/' case.
1198 Char
.IsDigit (s
[valuePos
]) ||
1199 Char
.IsLetter (s
[valuePos
]))
1210 valuePos
+= num_parsed
;
1213 switch (chars
[pos
]) {
1218 if (s
.Length
> valuePos
&& s
[valuePos
] == 'Z'
1219 && (pos
+ 1 == chars
.Length
1220 || chars
[pos
+ 1] != 'Z')) {
1228 pos
= pos
+ num
+ 1;
1232 if (exact
&& pos
< len
)
1235 if (s
.Length
!= valuePos
) // extraneous tail.
1245 if (fractionalSeconds
== -1)
1246 fractionalSeconds
= 0;
1248 // If no date was given
1249 if ((day
== -1) && (month
== -1) && (year
== -1)) {
1250 if ((style
& DateTimeStyles
.NoCurrentDateDefault
) != 0) {
1256 month
= Today
.Month
;
1267 if ((style
& DateTimeStyles
.NoCurrentDateDefault
) != 0)
1276 // For anything out of range
1278 if ( year
< 1 || year
> 9999 ||
1279 month
< 1 || month
>12 ||
1280 day
< 1 || day
> DaysInMonth(year
, month
) ||
1281 hour
< 0 || hour
> 23 ||
1282 minute
< 0 || minute
> 59 ||
1283 second
< 0 || second
> 59 )
1286 result
= new DateTime (year
, month
, day
, hour
, minute
, second
, 0);
1287 result
= result
.AddSeconds(fractionalSeconds
);
1289 //Console.WriteLine ("**** Parsed as {1} {0} {2}", new object [] {useutc ? "[u]" : "", format, use_localtime ? "[lt]" : ""});
1290 if ((dayofweek
!= -1) && (dayofweek
!= (int) result
.DayOfWeek
))
1291 throw new FormatException (Locale
.GetText ("String was not recognized as valid DateTime because the day of week was incorrect."));
1293 // If no timezone was specified, default to the local timezone.
1297 utcoffset
= new TimeSpan (0, 0, 0);
1298 else if (tzsign
== -1) {
1299 TimeZone tz
= TimeZone
.CurrentTimeZone
;
1300 utcoffset
= tz
.GetUtcOffset (result
);
1302 if ((style
& DateTimeStyles
.AdjustToUniversal
) != 0)
1303 use_localtime
= false;
1310 tzoffset
= -tzoffset
;
1312 utcoffset
= new TimeSpan (tzoffset
, tzoffmin
, 0);
1315 long newticks
= (result
.ticks
- utcoffset
).Ticks
;
1317 result
= new DateTime (use_localtime
, newticks
);
1323 public static DateTime
ParseExact (string s
, string format
,
1324 IFormatProvider fp
, DateTimeStyles style
)
1328 formats
= new string [1];
1329 formats
[0] = format
;
1331 return ParseExact (s
, formats
, fp
, style
);
1334 public static DateTime
ParseExact (string s
, string[] formats
,
1336 DateTimeStyles style
)
1338 DateTimeFormatInfo dfi
= DateTimeFormatInfo
.GetInstance (fp
);
1341 throw new ArgumentNullException (Locale
.GetText ("s is null"));
1342 if (formats
== null || formats
.Length
== 0)
1343 throw new ArgumentNullException (Locale
.GetText ("format is null"));
1346 bool longYear
= false;
1347 if (!ParseExact (s
, formats
, dfi
, style
, out result
, true, ref longYear
))
1348 throw new FormatException ();
1352 private static bool ParseExact (string s
, string [] formats
,
1353 DateTimeFormatInfo dfi
, DateTimeStyles style
, out DateTime ret
,
1354 bool exact
, ref bool longYear
)
1357 for (i
= 0; i
< formats
.Length
; i
++)
1361 if (_DoParse (s
, formats
[i
], exact
, out result
, dfi
, style
, ref longYear
)) {
1366 ret
= DateTime
.MinValue
;
1370 public TimeSpan
Subtract(DateTime dt
)
1372 return new TimeSpan(ticks
.Ticks
) - dt
.ticks
;
1375 public DateTime
Subtract(TimeSpan ts
)
1379 newticks
= (new TimeSpan (ticks
.Ticks
)) - ts
;
1380 return new DateTime(true,newticks
);
1383 public long ToFileTime()
1385 DateTime universalTime
= ToUniversalTime();
1387 if (universalTime
.Ticks
< w32file_epoch
) {
1388 throw new ArgumentOutOfRangeException("file time is not valid");
1391 return(universalTime
.Ticks
- w32file_epoch
);
1395 public long ToFileTimeUtc()
1397 if (Ticks
< w32file_epoch
) {
1398 throw new ArgumentOutOfRangeException("file time is not valid");
1401 return (Ticks
- w32file_epoch
);
1405 public string ToLongDateString()
1407 return ToString ("D");
1410 public string ToLongTimeString()
1412 return ToString ("T");
1415 public double ToOADate ()
1417 long t
= this.Ticks
;
1418 // uninitialized DateTime case
1421 // we can't reach minimum value
1422 if (t
< 31242239136000000)
1423 return OAMinValue
+ 0.001;
1425 TimeSpan ts
= new TimeSpan (this.Ticks
- ticks18991230
);
1426 double result
= ts
.TotalDays
;
1427 // t < 0 (where 599264352000000000 == 0.0d for OA)
1428 if (t
< 599264352000000000) {
1429 // negative days (int) but decimals are positive
1430 double d
= Math
.Ceiling (result
);
1431 result
= d
- 2 - (result
- d
);
1434 // we can't reach maximum value
1435 if (result
>= OAMaxValue
)
1436 result
= OAMaxValue
- 0.00000001d
;
1441 public string ToShortDateString()
1443 return ToString ("d");
1446 public string ToShortTimeString()
1448 return ToString ("t");
1451 public override string ToString ()
1453 return ToString ("G", null);
1456 public string ToString (IFormatProvider fp
)
1458 return ToString (null, fp
);
1461 public string ToString (string format
)
1463 return ToString (format
, null);
1466 internal static string _GetStandardPattern (char format
, DateTimeFormatInfo dfi
, out bool useutc
, out bool use_invariant
)
1471 use_invariant
= false;
1476 pattern
= dfi
.ShortDatePattern
;
1479 pattern
= dfi
.LongDatePattern
;
1482 pattern
= dfi
.LongDatePattern
+ " " + dfi
.ShortTimePattern
;
1485 pattern
= dfi
.FullDateTimePattern
;
1488 pattern
= dfi
.ShortDatePattern
+ " " + dfi
.ShortTimePattern
;
1491 pattern
= dfi
.ShortDatePattern
+ " " + dfi
.LongTimePattern
;
1495 pattern
= dfi
.MonthDayPattern
;
1499 pattern
= dfi
.RFC1123Pattern
;
1500 // commented by LP 09/jun/2002, rfc 1123 pattern is always in GMT
1501 // uncommented by AE 27/may/2004
1503 use_invariant
= true;
1506 pattern
= dfi
.SortableDateTimePattern
;
1509 pattern
= dfi
.ShortTimePattern
;
1512 pattern
= dfi
.LongTimePattern
;
1515 pattern
= dfi
.UniversalSortableDateTimePattern
;
1519 // pattern = dfi.LongDatePattern + " " + dfi.LongTimePattern;
1520 pattern
= dfi
.FullDateTimePattern
;
1525 pattern
= dfi
.YearMonthPattern
;
1535 internal string _ToString (string format
, DateTimeFormatInfo dfi
)
1537 // the length of the format is usually a good guess of the number
1538 // of chars in the result. Might save us a few bytes sometimes
1539 // Add + 10 for cases like mmmm dddd
1540 StringBuilder result
= new StringBuilder (format
.Length
+ 10);
1542 // For some cases, the output should not use culture dependent calendar
1543 DateTimeFormatInfo inv
= DateTimeFormatInfo
.InvariantInfo
;
1544 if (format
== inv
.RFC1123Pattern
)
1546 else if (format
== inv
.UniversalSortableDateTimePattern
)
1551 while (i
< format
.Length
) {
1553 char ch
= format
[i
];
1562 tokLen
= CountRepeat (format
, i
, ch
);
1564 int hr
= this.Hour
% 12;
1568 ZeroPad (result
, hr
, tokLen
== 1 ? 1 : 2);
1572 tokLen
= CountRepeat (format
, i
, ch
);
1573 ZeroPad (result
, this.Hour
, tokLen
== 1 ? 1 : 2);
1577 tokLen
= CountRepeat (format
, i
, ch
);
1578 ZeroPad (result
, this.Minute
, tokLen
== 1 ? 1 : 2);
1582 tokLen
= CountRepeat (format
, i
, ch
);
1583 ZeroPad (result
, this.Second
, tokLen
== 1 ? 1 : 2);
1586 // fraction of second, to same number of
1587 // digits as there are f's
1589 tokLen
= CountRepeat (format
, i
, ch
);
1591 throw new FormatException ("Invalid Format String");
1593 int dec
= (int)((long)(this.Ticks
% TimeSpan
.TicksPerSecond
) / (long) Math
.Pow (10, 7 - tokLen
));
1594 ZeroPad (result
, dec
, tokLen
);
1598 // AM/PM. t == first char, tt+ == full
1599 tokLen
= CountRepeat (format
, i
, ch
);
1600 string desig
= this.Hour
< 12 ? dfi
.AMDesignator
: dfi
.PMDesignator
;
1603 if (desig
.Length
>= 1)
1604 result
.Append (desig
[0]);
1607 result
.Append (desig
);
1611 // timezone. t = +/-h; tt = +/-hh; ttt+=+/-hh:mm
1612 tokLen
= CountRepeat (format
, i
, ch
);
1613 TimeSpan offset
= TimeZone
.CurrentTimeZone
.GetUtcOffset (this);
1615 if (offset
.Ticks
>= 0)
1616 result
.Append ('+');
1618 result
.Append ('-');
1622 result
.Append (Math
.Abs (offset
.Hours
));
1625 result
.Append (Math
.Abs (offset
.Hours
).ToString ("00"));
1628 result
.Append (Math
.Abs (offset
.Hours
).ToString ("00"));
1629 result
.Append (':');
1630 result
.Append (Math
.Abs (offset
.Minutes
).ToString ("00"));
1638 // day. d(d?) = day of month (leading 0 if two d's)
1639 // ddd = three leter day of week
1640 // dddd+ full day-of-week
1641 tokLen
= CountRepeat (format
, i
, ch
);
1644 ZeroPad (result
, dfi
.Calendar
.GetDayOfMonth (this), tokLen
== 1 ? 1 : 2);
1645 else if (tokLen
== 3)
1646 result
.Append (dfi
.GetAbbreviatedDayName (dfi
.Calendar
.GetDayOfWeek (this)));
1648 result
.Append (dfi
.GetDayName (dfi
.Calendar
.GetDayOfWeek (this)));
1652 // Month.m(m?) = month # (with leading 0 if two mm)
1653 // mmm = 3 letter name
1654 // mmmm+ = full name
1655 tokLen
= CountRepeat (format
, i
, ch
);
1656 int month
= dfi
.Calendar
.GetMonth(this);
1658 ZeroPad (result
, month
, tokLen
);
1659 else if (tokLen
== 3)
1660 result
.Append (dfi
.GetAbbreviatedMonthName (month
));
1662 result
.Append (dfi
.GetMonthName (month
));
1666 // Year. y(y?) = two digit year, with leading 0 if yy
1667 // yyy+ full year, if yyy and yr < 1000, displayed as three digits
1668 tokLen
= CountRepeat (format
, i
, ch
);
1671 ZeroPad (result
, dfi
.Calendar
.GetYear (this) % 100, tokLen
);
1673 ZeroPad (result
, dfi
.Calendar
.GetYear (this), (tokLen
== 3 ? 3 : 4));
1678 tokLen
= CountRepeat (format
, i
, ch
);
1679 result
.Append (dfi
.GetEraName (dfi
.Calendar
.GetEra (this)));
1686 result
.Append (dfi
.TimeSeparator
);
1690 result
.Append (dfi
.DateSeparator
);
1693 case '\'': case '"':
1694 tokLen
= ParseQuotedString (format
, i
, result
);
1697 if (i
>= format
.Length
- 1)
1698 throw new FormatException ("% at end of date time string");
1699 if (format
[i
+ 1] == '%')
1700 throw new FormatException ("%% in date string");
1702 // Look for the next char
1707 if (i
>= format
.Length
- 1)
1708 throw new FormatException ("\\ at end of date time string");
1710 result
.Append (format
[i
+ 1]);
1722 return result
.ToString ();
1725 static int CountRepeat (string fmt
, int p
, char c
)
1729 while ((i
< l
) && (fmt
[i
] == c
))
1735 static int ParseQuotedString (string fmt
, int pos
, StringBuilder output
)
1737 // pos == position of " or '
1739 int len
= fmt
.Length
;
1741 char quoteChar
= fmt
[pos
++];
1744 char ch
= fmt
[pos
++];
1746 if (ch
== quoteChar
)
1752 throw new FormatException("Un-ended quote");
1754 output
.Append (fmt
[pos
++]);
1760 throw new FormatException("Un-ended quote");
1763 static unsafe void ZeroPad (StringBuilder output
, int digits
, int len
)
1765 // more than enough for an int
1766 char* buffer
= stackalloc char [16];
1770 buffer
[-- pos
] = (char) ('0' + digits
% 10);
1773 } while (digits
> 0);
1776 buffer
[-- pos
] = '0';
1778 output
.Append (new string (buffer
, pos
, 16 - pos
));
1781 public string ToString (string format
, IFormatProvider fp
)
1784 DateTimeFormatInfo dfi
= DateTimeFormatInfo
.GetInstance(fp
);
1789 bool useutc
= false, use_invariant
= false;
1791 if (format
.Length
== 1) {
1792 char fchar
= format
[0];
1793 format
= _GetStandardPattern (fchar
, dfi
, out useutc
, out use_invariant
);
1796 // Don't convert UTC value. It just adds 'Z' for
1797 // 'u' format, for the same ticks.
1798 return this._ToString (format
, dfi
);
1801 public DateTime
ToLocalTime()
1803 TimeZone tz
= TimeZone
.CurrentTimeZone
;
1805 TimeSpan offset
= tz
.GetUtcOffset (this);
1807 if (offset
.Ticks
> 0) {
1808 if (DateTime
.MaxValue
- offset
< this)
1809 return DateTime
.MaxValue
;
1810 } else if (offset
.Ticks
< 0) {
1811 // MS.NET fails to check validity here
1812 // - it may throw ArgumentOutOfRangeException
1814 if (DateTime.MinValue - offset > this)
1815 return DateTime.MinValue;
1819 DateTime lt
= new DateTime(true, ticks
+offset
);
1820 TimeSpan ltoffset
= tz
.GetUtcOffset(lt
);
1821 if(ltoffset
!= offset
)
1822 lt
= lt
.Add(ltoffset
.Subtract(offset
));
1827 public DateTime
ToUniversalTime()
1829 TimeZone tz
= TimeZone
.CurrentTimeZone
;
1831 TimeSpan offset
= tz
.GetUtcOffset (this);
1833 if (offset
.Ticks
< 0) {
1834 if (DateTime
.MaxValue
+ offset
< this)
1835 return DateTime
.MaxValue
;
1836 } else if (offset
.Ticks
> 0) {
1837 if (DateTime
.MinValue
+ offset
> this)
1838 return DateTime
.MinValue
;
1841 return new DateTime (false, ticks
- offset
);
1846 public static DateTime
operator +(DateTime d
, TimeSpan t
)
1848 return new DateTime (true, d
.ticks
+ t
);
1851 public static bool operator ==(DateTime d1
, DateTime d2
)
1853 return (d1
.ticks
== d2
.ticks
);
1856 public static bool operator >(DateTime t1
,DateTime t2
)
1858 return (t1
.ticks
> t2
.ticks
);
1861 public static bool operator >=(DateTime t1
,DateTime t2
)
1863 return (t1
.ticks
>= t2
.ticks
);
1866 public static bool operator !=(DateTime d1
, DateTime d2
)
1868 return (d1
.ticks
!= d2
.ticks
);
1871 public static bool operator <(DateTime t1
, DateTime t2
)
1873 return (t1
.ticks
< t2
.ticks
);
1876 public static bool operator <=(DateTime t1
,DateTime t2
)
1878 return (t1
.ticks
<= t2
.ticks
);
1881 public static TimeSpan
operator -(DateTime d1
,DateTime d2
)
1883 return new TimeSpan((d1
.ticks
- d2
.ticks
).Ticks
);
1886 public static DateTime
operator -(DateTime d
,TimeSpan t
)
1888 return new DateTime (true, d
.ticks
- t
);
1891 bool IConvertible
.ToBoolean(IFormatProvider provider
)
1893 throw new InvalidCastException();
1896 byte IConvertible
.ToByte(IFormatProvider provider
)
1898 throw new InvalidCastException();
1902 char IConvertible
.ToChar(IFormatProvider provider
)
1904 throw new InvalidCastException();
1907 System
.DateTime IConvertible
.ToDateTime(IFormatProvider provider
)
1912 decimal IConvertible
.ToDecimal(IFormatProvider provider
)
1914 throw new InvalidCastException();
1917 double IConvertible
.ToDouble(IFormatProvider provider
)
1919 throw new InvalidCastException();
1922 Int16 IConvertible
.ToInt16(IFormatProvider provider
)
1924 throw new InvalidCastException();
1927 Int32 IConvertible
.ToInt32(IFormatProvider provider
)
1929 throw new InvalidCastException();
1932 Int64 IConvertible
.ToInt64(IFormatProvider provider
)
1934 throw new InvalidCastException();
1937 SByte IConvertible
.ToSByte(IFormatProvider provider
)
1939 throw new InvalidCastException();
1942 Single IConvertible
.ToSingle(IFormatProvider provider
)
1944 throw new InvalidCastException();
1947 object IConvertible
.ToType (Type conversionType
, IFormatProvider provider
)
1949 if (conversionType
== null)
1950 throw new ArgumentNullException ("conversionType");
1952 if (conversionType
== typeof (DateTime
))
1954 else if (conversionType
== typeof (String
))
1955 return this.ToString (provider
);
1956 else if (conversionType
== typeof (Object
))
1959 throw new InvalidCastException();
1962 UInt16 IConvertible
.ToUInt16(IFormatProvider provider
)
1964 throw new InvalidCastException();
1967 UInt32 IConvertible
.ToUInt32(IFormatProvider provider
)
1969 throw new InvalidCastException();
1972 UInt64 IConvertible
.ToUInt64(IFormatProvider provider
)
1974 throw new InvalidCastException();
1981 public enum DayOfWeek