Skip to content

Commit 04117d8

Browse files
authored
Add datetime.timezone (#1137)
* Add datetime.timezone * Add NotNull annotation
1 parent 4caba8c commit 04117d8

File tree

2 files changed

+89
-30
lines changed

2 files changed

+89
-30
lines changed

Src/IronPython.Modules/_datetime.cs

Lines changed: 87 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public class timedelta : ICodeFormattable {
3535
private bool _fWithDaysAndSeconds = false; // whether _tsWithDaysAndSeconds initialized
3636
private bool _fWithSeconds = false;
3737

38+
internal static readonly timedelta Zero = new timedelta(0, 0, 0);
3839
internal static readonly timedelta _DayResolution = new timedelta(1, 0, 0);
3940
// class attributes:
4041
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
@@ -211,12 +212,11 @@ public static object __getnewargs__(int days, int seconds, int microseconds) {
211212
return PythonTuple.MakeTuple(new timedelta(days, seconds, microseconds, 0, 0, 0, 0));
212213
}
213214

214-
public override bool Equals(object obj) {
215-
timedelta delta = obj as timedelta;
216-
if (delta == null) return false;
215+
internal bool Equals(timedelta delta)
216+
=> _days == delta._days && _seconds == delta._seconds && _microseconds == delta._microseconds;
217217

218-
return this._days == delta._days && this._seconds == delta._seconds && this._microseconds == delta._microseconds;
219-
}
218+
public override bool Equals(object obj)
219+
=> obj is timedelta delta && Equals(delta);
220220

221221
public override int GetHashCode() {
222222
return this._days ^ this._seconds ^ this._microseconds;
@@ -242,11 +242,7 @@ public override string ToString() {
242242

243243
#region Rich Comparison Members
244244

245-
private int CompareTo(object other) {
246-
timedelta delta = other as timedelta;
247-
if (delta == null)
248-
throw PythonOps.TypeError("can't compare datetime.timedelta to {0}", PythonTypeOps.GetName(other));
249-
245+
private int CompareTo(timedelta delta) {
250246
int res = this._days - delta._days;
251247
if (res != 0) return res;
252248

@@ -256,21 +252,13 @@ private int CompareTo(object other) {
256252
return this._microseconds - delta._microseconds;
257253
}
258254

259-
public static bool operator >(timedelta self, object other) {
260-
return self.CompareTo(other) > 0;
261-
}
255+
public static bool operator >([NotNull] timedelta self, [NotNull] timedelta other) => self.CompareTo(other) > 0;
262256

263-
public static bool operator <(timedelta self, object other) {
264-
return self.CompareTo(other) < 0;
265-
}
257+
public static bool operator <([NotNull] timedelta self, [NotNull] timedelta other) => self.CompareTo(other) < 0;
266258

267-
public static bool operator >=(timedelta self, object other) {
268-
return self.CompareTo(other) >= 0;
269-
}
259+
public static bool operator >=([NotNull] timedelta self, [NotNull] timedelta other) => self.CompareTo(other) >= 0;
270260

271-
public static bool operator <=(timedelta self, object other) {
272-
return self.CompareTo(other) <= 0;
273-
}
261+
public static bool operator <=([NotNull] timedelta self, [NotNull] timedelta other) => self.CompareTo(other) <= 0;
274262

275263
#endregion
276264

@@ -987,7 +975,8 @@ public override date replace(CodeContext/*!*/ context, [ParamDictionary]IDiction
987975
return new datetime(lyear, lmonth, lday, lhour, lminute, lsecond, lmicrosecond, tz);
988976
}
989977

990-
public object astimezone(tzinfo tz) {
978+
public object astimezone(tzinfo tz = null) {
979+
// TODO: https://github.com/IronLanguages/ironpython3/issues/1136
991980
if (tz == null)
992981
throw PythonOps.TypeError("astimezone() argument 1 must be datetime.tzinfo, not None");
993982

@@ -1553,14 +1542,87 @@ public virtual timedelta utcoffset(object dt) {
15531542
}
15541543

15551544
public PythonTuple __reduce__(CodeContext/*!*/ context) {
1545+
object args = PythonTuple.EMPTY;
1546+
if (PythonOps.TryGetBoundAttr(context, this, "__getinitargs__", out var getinitargs)) {
1547+
args = PythonOps.CallWithContext(context, getinitargs);
1548+
}
1549+
15561550
object dict;
15571551
if (GetType() == typeof(tzinfo) ||
15581552
!PythonOps.TryGetBoundAttr(context, this, "__dict__", out dict)) {
1559-
return PythonTuple.MakeTuple(DynamicHelpers.GetPythonType(this), PythonTuple.EMPTY);
1553+
return PythonTuple.MakeTuple(DynamicHelpers.GetPythonType(this), args);
15601554
}
15611555

1562-
return PythonTuple.MakeTuple(DynamicHelpers.GetPythonType(this), PythonTuple.EMPTY, dict);
1556+
return PythonTuple.MakeTuple(DynamicHelpers.GetPythonType(this), args, dict);
1557+
}
1558+
}
1559+
1560+
#nullable enable
1561+
1562+
[PythonType]
1563+
public sealed class timezone : tzinfo, ICodeFormattable {
1564+
private readonly timedelta _offset;
1565+
private readonly string? _name;
1566+
1567+
private timezone(timedelta offset, string? name = null) {
1568+
if (offset <= -timedelta._DayResolution || offset >= timedelta._DayResolution)
1569+
throw PythonOps.ValueError($"offset must be a timedelta strictly between -timedelta(hours=24) and timedelta(hours=24), not {PythonOps.Repr(DefaultContext.Default, offset)}.");
1570+
_offset = offset;
1571+
_name = name;
1572+
}
1573+
1574+
public static timezone __new__(CodeContext context, [NotNull] PythonType cls, [NotNull] timedelta offset)
1575+
=> __new__(context, cls, offset, null!);
1576+
1577+
public static timezone __new__(CodeContext context, [NotNull] PythonType cls, [NotNull] timedelta offset, [NotNull] string name) {
1578+
if (name is null && offset.Equals(timedelta.Zero))
1579+
return utc;
1580+
return new timezone(offset, name);
1581+
}
1582+
1583+
public static timezone utc { get; } = new timezone(timedelta.Zero);
1584+
1585+
public override timedelta utcoffset(object? dt) => _offset;
1586+
1587+
public override timedelta? dst(object? dt) => null;
1588+
1589+
public override object fromutc([NotNull] datetime dt) {
1590+
if (!ReferenceEquals(this, dt.tzinfo)) throw PythonOps.ValueError("fromutc: dt.tzinfo is not self");
1591+
return dt + _offset;
1592+
}
1593+
1594+
private bool IsUtc => ReferenceEquals(this, utc);
1595+
1596+
public override string tzname(object? dt) {
1597+
if (_name is not null) return _name;
1598+
1599+
if (IsUtc) return "UTC";
1600+
1601+
var totalSeconds = _offset.total_seconds();
1602+
var time = TimeSpan.FromSeconds(totalSeconds).ToString("c");
1603+
if (totalSeconds >= 0) time = "+" + time; // prefix with 0
1604+
if (time.EndsWith(":00", StringComparison.OrdinalIgnoreCase)) time = time.Substring(0, time.Length - 3); // remove trailing seconds
1605+
return $"UTC" + time;
1606+
}
1607+
1608+
#region ICodeFormattable Members
1609+
1610+
public string __repr__(CodeContext context) {
1611+
if (IsUtc)
1612+
return "datetime.timezone.utc";
1613+
if (_name is null)
1614+
return $"datetime.timezone({PythonOps.Repr(context, _offset)})";
1615+
return $"datetime.timezone({PythonOps.Repr(context, _offset)}, {PythonOps.Repr(context, _name)})";
1616+
}
1617+
1618+
#endregion
1619+
1620+
public PythonTuple __getinitargs__(CodeContext context) {
1621+
if (_name is null) return PythonTuple.MakeTuple(_offset);
1622+
return PythonTuple.MakeTuple(_offset, _name);
15631623
}
15641624
}
1625+
1626+
#nullable restore
15651627
}
15661628
}

Src/IronPythonTest/Cases/CPythonCasesManifest.ini

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -401,13 +401,10 @@ Ignore=true
401401
Ignore=true
402402

403403
[CPython.test_email.test_headerregistry]
404-
Ignore=true # blocked by https://github.com/IronLanguages/ironpython3/issues/1133
405-
406-
[CPython.test_email.test_pickleable]
407-
Ignore=true # blocked by https://github.com/IronLanguages/ironpython3/issues/1133
404+
Ignore=true # blocked by https://github.com/IronLanguages/ironpython3/issues/1121
408405

409406
[CPython.test_email.test_utils]
410-
Ignore=true # blocked by https://github.com/IronLanguages/ironpython3/issues/1133
407+
Ignore=true # blocked by https://github.com/IronLanguages/ironpython3/issues/1121 and https://github.com/IronLanguages/ironpython3/issues/1136
411408

412409
[CPython.test_ensurepip]
413410
Ignore=true

0 commit comments

Comments
 (0)