// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Scripting;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Utils;
using IronPython.Runtime;
using IronPython.Runtime.Operations;
using IronPython.Runtime.Types;
[assembly: PythonModule("_datetime", typeof(IronPython.Modules.PythonDateTime))]
namespace IronPython.Modules {
public class PythonDateTime {
public static readonly int MAXYEAR = DateTime.MaxValue.Year;
public static readonly int MINYEAR = DateTime.MinValue.Year;
public const string __doc__ = "Provides functions and types for working with dates and times.";
[PythonType]
public class timedelta : ICodeFormattable {
internal int _days;
internal int _seconds;
internal int _microseconds;
private TimeSpan _tsWithDaysAndSeconds, _tsWithSeconds; // value type
private bool _fWithDaysAndSeconds = false; // whether _tsWithDaysAndSeconds initialized
private bool _fWithSeconds = false;
internal static readonly timedelta _DayResolution = new timedelta(1, 0, 0);
// class attributes:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
public static readonly timedelta resolution = new timedelta(0, 0, 1);
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
public static readonly timedelta min = new timedelta(-MAXDAYS, 0, 0);
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
public static readonly timedelta max = new timedelta(MAXDAYS, 86399, 999999);
private const int MAXDAYS = 999999999;
private const double SECONDSPERDAY = 24 * 60 * 60;
internal timedelta(double days, double seconds, double microsecond)
: this(days, seconds, microsecond, 0, 0, 0, 0) {
}
internal timedelta(TimeSpan ts, double microsecond)
: this(ts.Days, ts.Seconds, microsecond, ts.Milliseconds, ts.Minutes, ts.Hours, 0) {
}
public timedelta(double days, double seconds, double microseconds, double milliseconds, double minutes, double hours, double weeks) {
double totalDays = weeks * 7 + days;
double totalSeconds = ((totalDays * 24 + hours) * 60 + minutes) * 60 + seconds;
double totalSecondsSharp = Math.Floor(totalSeconds);
double totalSecondsFloat = totalSeconds - totalSecondsSharp;
double totalMicroseconds = Math.Round(totalSecondsFloat * 1e6 + milliseconds * 1000 + microseconds);
double otherSecondsFromMicroseconds = Math.Floor(totalMicroseconds / 1e6);
totalSecondsSharp += otherSecondsFromMicroseconds;
totalMicroseconds -= otherSecondsFromMicroseconds * 1e6;
if (totalSecondsSharp > 0 && totalMicroseconds < 0) {
totalSecondsSharp -= 1;
totalMicroseconds += 1e6;
}
_days = (int)(totalSecondsSharp / SECONDSPERDAY);
_seconds = (int)(totalSecondsSharp - _days * SECONDSPERDAY);
if (_seconds < 0) {
_days--;
_seconds += (int)SECONDSPERDAY;
}
_microseconds = (int)(totalMicroseconds);
if (Math.Abs(_days) > MAXDAYS) {
throw PythonOps.OverflowError("days={0}; must have magnitude <= 999999999", _days);
}
}
public static timedelta __new__(CodeContext context, PythonType cls,
double days=0D,
double seconds=0D,
double microseconds=0D,
double milliseconds=0D,
double minutes=0D,
double hours=0D,
double weeks=0D) {
if (cls == DynamicHelpers.GetPythonTypeFromType(typeof(timedelta))) {
return new timedelta(days, seconds, microseconds, milliseconds, minutes, hours, weeks);
} else {
timedelta delta = cls.CreateInstance(context, days, seconds, microseconds, milliseconds, minutes, hours, weeks) as timedelta;
if (delta == null) throw PythonOps.TypeError("{0} is not a subclass of datetime.timedelta", cls);
return delta;
}
}
// instance attributes:
public int days {
get { return _days; }
}
public int seconds {
get { return _seconds; }
}
public int microseconds {
get { return _microseconds; }
}
internal TimeSpan TimeSpanWithDaysAndSeconds {
get {
if (!_fWithDaysAndSeconds) {
_tsWithDaysAndSeconds = new TimeSpan(_days, 0, 0, _seconds);
_fWithDaysAndSeconds = true;
}
return _tsWithDaysAndSeconds;
}
}
internal TimeSpan TimeSpanWithSeconds {
get {
if (!_fWithSeconds) {
_tsWithSeconds = TimeSpan.FromSeconds(_seconds);
_fWithSeconds = true;
}
return _tsWithSeconds;
}
}
// supported operations:
public static timedelta operator +(timedelta self, timedelta other) {
return new timedelta(self._days + other._days, self._seconds + other._seconds, self._microseconds + other._microseconds);
}
public static timedelta operator -(timedelta self, timedelta other) {
return new timedelta(self._days - other._days, self._seconds - other._seconds, self._microseconds - other._microseconds);
}
public static timedelta operator -(timedelta self) {
return new timedelta(-self._days, -self._seconds, -self._microseconds);
}
public static timedelta operator +(timedelta self) {
return new timedelta(self._days, self._seconds, self._microseconds);
}
public static timedelta operator *(timedelta self, int other) {
return new timedelta(self._days * other, self._seconds * other, self._microseconds * other);
}
public static timedelta operator *(int other, timedelta self) {
return new timedelta(self._days * other, self._seconds * other, self._microseconds * other);
}
public static timedelta operator /(timedelta self, int other) {
return new timedelta((double)self._days / other, (double)self._seconds / other, (double)self._microseconds / other);
}
public static timedelta operator *(timedelta self, BigInteger other) {
return self * (int)other;
}
public static timedelta operator *(BigInteger other, timedelta self) {
return (int)other * self;
}
public static timedelta operator /(timedelta self, BigInteger other) {
return self / (int)other;
}
public timedelta __pos__() { return +this; }
public timedelta __neg__() { return -this; }
public timedelta __abs__() { return (_days > 0) ? this : -this; }
[SpecialName]
public timedelta FloorDivide(int y) {
return this / y;
}
[SpecialName]
public timedelta ReverseFloorDivide(int y) {
return this / y;
}
public double total_seconds() {
var total_microseconds = (double) this.microseconds + (this.seconds + this.days * 24.0 * 3600.0) * 1000000.0;
return total_microseconds / 1000000.0;
}
public bool __bool__() {
return this._days != 0 || this._seconds != 0 || this._microseconds != 0;
}
public PythonTuple __reduce__() {
return PythonTuple.MakeTuple(
DynamicHelpers.GetPythonType(this),
PythonTuple.MakeTuple(_days, _seconds, _microseconds)
);
}
public static object __getnewargs__(int days, int seconds, int microseconds) {
return PythonTuple.MakeTuple(new timedelta(days, seconds, microseconds, 0, 0, 0, 0));
}
public override bool Equals(object obj) {
timedelta delta = obj as timedelta;
if (delta == null) return false;
return this._days == delta._days && this._seconds == delta._seconds && this._microseconds == delta._microseconds;
}
public override int GetHashCode() {
return this._days ^ this._seconds ^ this._microseconds;
}
public override string ToString() {
StringBuilder sb = new StringBuilder();
if (_days != 0) {
sb.Append(_days);
if (Math.Abs(_days) == 1)
sb.Append(" day, ");
else
sb.Append(" days, ");
}
sb.AppendFormat("{0}:{1:d2}:{2:d2}", TimeSpanWithSeconds.Hours, TimeSpanWithSeconds.Minutes, TimeSpanWithSeconds.Seconds);
if (_microseconds != 0)
sb.AppendFormat(".{0:d6}", _microseconds);
return sb.ToString();
}
#region Rich Comparison Members
private int CompareTo(object other) {
timedelta delta = other as timedelta;
if (delta == null)
throw PythonOps.TypeError("can't compare datetime.timedelta to {0}", PythonTypeOps.GetName(other));
int res = this._days - delta._days;
if (res != 0) return res;
res = this._seconds - delta._seconds;
if (res != 0) return res;
return this._microseconds - delta._microseconds;
}
public static bool operator >(timedelta self, object other) {
return self.CompareTo(other) > 0;
}
public static bool operator <(timedelta self, object other) {
return self.CompareTo(other) < 0;
}
public static bool operator >=(timedelta self, object other) {
return self.CompareTo(other) >= 0;
}
public static bool operator <=(timedelta self, object other) {
return self.CompareTo(other) <= 0;
}
#endregion
#region ICodeFormattable Members
public virtual string/*!*/ __repr__(CodeContext/*!*/ context) {
if (_seconds == 0 && _microseconds == 0) {
return String.Format("datetime.timedelta({0})", _days);
} else if (_microseconds == 0) {
return String.Format("datetime.timedelta({0}, {1})", _days, _seconds);
} else {
return String.Format("datetime.timedelta({0}, {1}, {2})", _days, _seconds, _microseconds);
}
}
#endregion
}
internal static void ThrowIfInvalid(timedelta delta, string funcname) {
if (delta != null) {
if (delta._microseconds != 0 || delta._seconds % 60 != 0) {
throw PythonOps.ValueError("tzinfo.{0}() must return a whole number of minutes", funcname);
}
int minutes = (int)(delta.TimeSpanWithDaysAndSeconds.TotalSeconds / 60);
if (Math.Abs(minutes) >= 1440) {
throw PythonOps.ValueError("tzinfo.{0}() returned {1}; must be in -1439 .. 1439", funcname, minutes);
}
}
}
internal enum InputKind { Year, Month, Day, Hour, Minute, Second, Microsecond }
internal static void ValidateInput(InputKind kind, int value) {
switch (kind) {
case InputKind.Year:
if (value > DateTime.MaxValue.Year || value < DateTime.MinValue.Year) {
throw PythonOps.ValueError("year is out of range");
}
break;
case InputKind.Month:
if (value > 12 || value < 1) {
throw PythonOps.ValueError("month must be in 1..12");
}
break;
case InputKind.Day:
// TODO: changing upper bound
if (value > 31 || value < 1) {
throw PythonOps.ValueError("day is out of range for month");
}
break;
case InputKind.Hour:
if (value > 23 || value < 0) {
throw PythonOps.ValueError("hour must be in 0..23");
}
break;
case InputKind.Minute:
if (value > 59 || value < 0) {
throw PythonOps.ValueError("minute must be in 0..59");
}
break;
case InputKind.Second:
if (value > 59 || value < 0) {
throw PythonOps.ValueError("second must be in 0..59");
}
break;
case InputKind.Microsecond:
if (value > 999999 || value < 0) {
throw PythonOps.ValueError("microsecond must be in 0..999999");
}
break;
}
}
internal static bool IsNaiveTimeZone(tzinfo tz) {
if (tz?.utcoffset(null) == null) return true;
return false;
}
internal static int CastToInt(object o) {
return o is BigInteger ? (int)(BigInteger)o : (int)o;
}
[PythonType]
public class date : ICodeFormattable {
internal DateTime _dateTime;
// class attributes
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
public static readonly date min = new date(new DateTime(1, 1, 1));
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
public static readonly date max = new date(new DateTime(9999, 12, 31));
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
public static readonly timedelta resolution = timedelta._DayResolution;
// Make this parameterless constructor internal
// so that the datetime module subclasses can use it,
// if this was protected instead, then you couldn't
// successfully call the public date constructor.
// Due to overload resolution failing.
// The protected version of this constructor matches
// the public constructor due to KeywordArgReturnBuilder
// related parameter processing,
internal date() { }
public date(int year, int month, int day) {
PythonDateTime.ValidateInput(InputKind.Year, year);
PythonDateTime.ValidateInput(InputKind.Month, month);
PythonDateTime.ValidateInput(InputKind.Day, day);
_dateTime = new DateTime(year, month, day);
}
internal date(DateTime value) {
_dateTime = value.Date; // no hour, minute, second
}
// other constructors, all class methods
public static object today() {
return new date(DateTime.Today);
}
public static date fromordinal(int d) {
if (d < 1) {
throw PythonOps.ValueError("ordinal must be >= 1");
}
return new date(min._dateTime.AddDays(d - 1));
}
public static date fromtimestamp(double timestamp) {
DateTime dt = PythonTime.TimestampToDateTime(timestamp);
dt = dt.AddSeconds(-PythonTime.timezone);
return new date(dt.Year, dt.Month, dt.Day);
}
// instance attributes
public int year {
get { return _dateTime.Year; }
}
public int month {
get { return _dateTime.Month; }
}
public int day {
get { return _dateTime.Day; }
}
internal DateTime InternalDateTime {
get { return _dateTime; }
set { _dateTime = value; }
}
public static implicit operator DateTime(date self) {
return self._dateTime;
}
// supported operations
public static date operator +([NotNull]date self, [NotNull]timedelta other) {
try {
return new date(self._dateTime.AddDays(other.days));
} catch {
throw PythonOps.OverflowError("date value out of range");
}
}
public static date operator +([NotNull]timedelta other, [NotNull]date self) {
try {
return new date(self._dateTime.AddDays(other.days));
} catch {
throw PythonOps.OverflowError("date value out of range");
}
}
public static date operator -(date self, timedelta delta) {
try {
return new date(self._dateTime.AddDays(-1 * delta.days));
} catch {
throw PythonOps.OverflowError("date value out of range");
}
}
public static timedelta operator -(date self, date other) {
TimeSpan ts = self._dateTime - other._dateTime;
return new timedelta(0, ts.TotalSeconds, ts.Milliseconds * 1000);
}
public virtual PythonTuple __reduce__() {
return PythonTuple.MakeTuple(DynamicHelpers.GetPythonType(this), PythonTuple.MakeTuple(_dateTime.Year, _dateTime.Month, _dateTime.Day));
}
public static object __getnewargs__(CodeContext context, int year, int month, int day) {
return PythonTuple.MakeTuple(new date(year, month, day));
}
public object replace() {
return this;
}
// instance methods
public virtual date replace(CodeContext/*!*/ context, [ParamDictionary]IDictionary