Skip to content

Commit

Permalink
feat: add Morrow.fromordinal; refactor Morrow to Self
Browse files Browse the repository at this point in the history
  • Loading branch information
Prodesire committed Oct 24, 2023
1 parent 6182db9 commit 4804099
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 26 deletions.
9 changes: 7 additions & 2 deletions morrow/constants.mojo
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from ._py import py_dt_datetime

# todo: hardcode for tmp
alias _MAX_TIMESTAMP: Int = 32503737600
alias MAX_TIMESTAMP = _MAX_TIMESTAMP
alias MAX_TIMESTAMP_MS = MAX_TIMESTAMP * 1000
alias MAX_TIMESTAMP_US = MAX_TIMESTAMP * 1_000_000

alias _DAYS_IN_MONTH = VariadicList[Int](
-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
)
alias _DAYS_BEFORE_MONTH = VariadicList[Int](
-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
) # -1 is a placeholder for indexing purposes.
113 changes: 97 additions & 16 deletions morrow/morrow.mojo
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
from ._py import py_dt_datetime
from .util import normalize_timestamp, num2str, _ymd2ord
from .util import normalize_timestamp, num2str, _ymd2ord, _days_before_year
from ._libc import c_gettimeofday, c_localtime, c_gmtime, c_strptime
from ._libc import CTimeval, CTm
from .timezone import TimeZone
from .timedelta import TimeDelta
from .constants import _DAYS_BEFORE_MONTH, _DAYS_IN_MONTH


alias _DI400Y = 146097 # number of days in 400 years
alias _DI100Y = 36524 # " " " " 100 "
alias _DI4Y = 1461 # " " " " 4 "


@value
Expand Down Expand Up @@ -38,17 +44,17 @@ struct Morrow:
self.TimeZone = TimeZone

@staticmethod
fn now() raises -> Morrow:
fn now() raises -> Self:
let t = c_gettimeofday()
return Morrow._fromtimestamp(t, False)
return Self._fromtimestamp(t, False)

@staticmethod
fn utcnow() raises -> Morrow:
fn utcnow() raises -> Self:
let t = c_gettimeofday()
return Morrow._fromtimestamp(t, True)
return Self._fromtimestamp(t, True)

@staticmethod
fn _fromtimestamp(t: CTimeval, utc: Bool) raises -> Morrow:
fn _fromtimestamp(t: CTimeval, utc: Bool) raises -> Self:
let tm: CTm
let tz: TimeZone
if utc:
Expand All @@ -58,7 +64,7 @@ struct Morrow:
tm = c_localtime(t.tv_sec)
tz = TimeZone(tm.tm_gmtoff.to_int(), "local")

let result = Morrow(
let result = Self(
tm.tm_year.to_int() + 1900,
tm.tm_mon.to_int() + 1,
tm.tm_mday.to_int(),
Expand All @@ -71,21 +77,21 @@ struct Morrow:
return result

@staticmethod
fn fromtimestamp(timestamp: Float64) raises -> Morrow:
fn fromtimestamp(timestamp: Float64) raises -> Self:
let timestamp_ = normalize_timestamp(timestamp)
let t = CTimeval(timestamp_.to_int())
return Morrow._fromtimestamp(t, False)
return Self._fromtimestamp(t, False)

@staticmethod
fn utcfromtimestamp(timestamp: Float64) raises -> Morrow:
fn utcfromtimestamp(timestamp: Float64) raises -> Self:
let timestamp_ = normalize_timestamp(timestamp)
let t = CTimeval(timestamp_.to_int())
return Morrow._fromtimestamp(t, True)
return Self._fromtimestamp(t, True)

@staticmethod
fn strptime(
date_str: String, fmt: String, tzinfo: TimeZone = TimeZone.none()
) raises -> Morrow:
) raises -> Self:
"""
Create a Morrow instance from a date string and format,
in the style of ``datetime.strptime``. Optionally replaces the parsed TimeZone.
Expand All @@ -97,7 +103,7 @@ struct Morrow:
"""
let tm = c_strptime(date_str, fmt)
let tz = TimeZone(tm.tm_gmtoff.to_int()) if tzinfo.is_none() else tzinfo
return Morrow(
return Self(
tm.tm_year.to_int() + 1900,
tm.tm_mon.to_int() + 1,
tm.tm_mday.to_int(),
Expand All @@ -109,7 +115,7 @@ struct Morrow:
)

@staticmethod
fn strptime(date_str: String, fmt: String, tz_str: String) raises -> Morrow:
fn strptime(date_str: String, fmt: String, tz_str: String) raises -> Self:
"""
Create a Morrow instance by time_zone_string with utc format
Expand All @@ -119,7 +125,7 @@ struct Morrow:
<Morrow [2019-01-20T15:49:10+08:00]>
"""
let tzinfo = TimeZone.from_utc(tz_str)
return Morrow.strptime(date_str, fmt, tzinfo)
return Self.strptime(date_str, fmt, tzinfo)

fn isoformat(
self, sep: String = "T", timespec: StringLiteral = "auto"
Expand Down Expand Up @@ -193,10 +199,85 @@ struct Morrow:
"""
return _ymd2ord(self.year, self.month, self.day)

@staticmethod
fn fromordinal(ordinal: Int) raises -> Self:
"""Construct a Morrow from a proleptic Gregorian ordinal.
January 1 of year 1 is day 1. Only the year, month and day are
non-zero in the result.
"""
# n is a 1-based index, starting at 1-Jan-1. The pattern of leap years
# repeats exactly every 400 years. The basic strategy is to find the
# closest 400-year boundary at or before n, then work with the offset
# from that boundary to n. Life is much clearer if we subtract 1 from
# n first -- then the values of n at 400-year boundaries are exactly
# those divisible by _DI400Y:
#
# D M Y n n-1
# -- --- ---- ---------- ----------------
# 31 Dec -400 -_DI400Y -_DI400Y -1
# 1 Jan -399 -_DI400Y +1 -_DI400Y 400-year boundary
# ...
# 30 Dec 000 -1 -2
# 31 Dec 000 0 -1
# 1 Jan 001 1 0 400-year boundary
# 2 Jan 001 2 1
# 3 Jan 001 3 2
# ...
# 31 Dec 400 _DI400Y _DI400Y -1
# 1 Jan 401 _DI400Y +1 _DI400Y 400-year boundary
var n = ordinal
n -= 1
let n400 = n // _DI400Y
n = n % _DI400Y
var year = n400 * 400 + 1 # ..., -399, 1, 401, ...

# Now n is the (non-negative) offset, in days, from January 1 of year, to
# the desired date. Now compute how many 100-year cycles precede n.
# Note that it's possible for n100 to equal 4! In that case 4 full
# 100-year cycles precede the desired day, which implies the desired
# day is December 31 at the end of a 400-year cycle.
let n100 = n // _DI100Y
n = n % _DI100Y

# Now compute how many 4-year cycles precede it.
let n4 = n // _DI4Y
n = n % _DI4Y

# And now how many single years. Again n1 can be 4, and again meaning
# that the desired day is December 31 at the end of the 4-year cycle.
let n1 = n // 365
n = n % 365

year += n100 * 100 + n4 * 4 + n1
if n1 == 4 or n100 == 4:
return Self(year - 1, 12, 31)

# Now the year is correct, and n is the offset from January 1. We find
# the month via an estimate that's either exact or one too large.
let leapyear = n1 == 3 and (n4 != 24 or n100 == 3)
var month = (n + 50) >> 5
var preceding: Int
if month > 2 and leapyear:
preceding = _DAYS_BEFORE_MONTH[month] + 1
else:
preceding = _DAYS_BEFORE_MONTH[month]
if preceding > n: # estimate is too large
month -= 1
if month == 2 and leapyear:
preceding -= (_DAYS_BEFORE_MONTH[month] + 1)
else:
preceding -= _DAYS_BEFORE_MONTH[month]
n -= preceding

# Now the year and month are correct, and n is the offset from the
# start of that month: we're done!
return Self(year, month, n+1)

fn __str__(self) raises -> String:
return self.isoformat()

fn __sub__(self, other: Morrow) raises -> TimeDelta:
fn __sub__(self, other: Self) raises -> TimeDelta:
let days1 = self.toordinal()
let days2 = other.toordinal()
let secs1 = self.second + self.minute * 60 + self.hour * 3600
Expand Down
9 changes: 1 addition & 8 deletions morrow/util.mojo
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
from .constants import MAX_TIMESTAMP, MAX_TIMESTAMP_MS, MAX_TIMESTAMP_US


alias _DAYS_IN_MONTH = VariadicList[Int](
-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
)
alias _DAYS_BEFORE_MONTH = VariadicList[Int](
-1, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
) # -1 is a placeholder for indexing purposes.
from .constants import _DAYS_IN_MONTH, _DAYS_BEFORE_MONTH


fn _is_leap(year: Int) -> Bool:
Expand Down
11 changes: 11 additions & 0 deletions test.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ def test_strptime():
m = Morrow.strptime("2023-10-18 15:49:10", "%Y-%m-%d %H:%M:%S", "+09:00")
assert_equal(m.__str__(), "2023-10-18T15:49:10.000000+09:00")

def test_ordinal():
print("Running test_ordinal()")
m = Morrow(2023, 10, 1)
o = m.toordinal()
assert_equal(o, 738794)

m2 = Morrow.fromordinal(o)
assert_equal(m2.year, 2023)
assert_equal(m.month, 10)
assert_equal(m.day, 1)


def test_sub():
print("Running test_sub()")
Expand Down

0 comments on commit 4804099

Please sign in to comment.