Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: supoort format #8

Merged
merged 3 commits into from
Feb 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ print(str(utc_t)) # 2023-09-30T16:00:00.000000+00:00
let m = Morrow(2023, 10, 1, 0, 0, 0, 1234)
print(m.isoformat()) # 2023-10-01T00:00:00.001234

# custom format
let m = Morrow(2023, 10, 1, 0, 0, 0, 1234)
print(m.format("YYYY-MM-DD HH:mm:ss.SSSSSS ZZ")) # 2023-10-01 00:00:00.001234 +00:00
print(m.format("dddd, DD MMM YYYY HH:mm:ss ZZZ")) # Sunday, 01 Oct 2023 00:00:00 UTC
print(m.format("YYYY[Y]MM[M]DD[D]")) # 2023Y10M01D

# Get ISO format with time zone.
let m_beijing = Morrow(2023, 10, 1, 0, 0, 0, 1234, TimeZone(28800, 'Bejing'))
print(m_beijing.isoformat(timespec="seconds")) # 2023-10-01T00:00:00+08:00
Expand Down
2 changes: 1 addition & 1 deletion morrow/__init__.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ from .morrow import Morrow
from .timezone import TimeZone
from .timedelta import TimeDelta

alias __version__ = "0.2.0"
alias __version__ = "0.3.0"
47 changes: 47 additions & 0 deletions morrow/constants.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,50 @@ alias _DAYS_IN_MONTH = VariadicList[Int](
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.


alias MONTH_NAMES = StaticTuple[13](
"",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
)

alias MONTH_ABBREVIATIONS = StaticTuple[13](
"",
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
)

alias DAY_NAMES = StaticTuple[8](
"",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday",
)
alias DAY_ABBREVIATIONS = StaticTuple[8](
"", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
)
185 changes: 185 additions & 0 deletions morrow/formatter.mojo
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
from collections.vector import InlinedFixedVector
from utils.static_tuple import StaticTuple
from .util import rjust
from .constants import MONTH_NAMES, MONTH_ABBREVIATIONS, DAY_NAMES, DAY_ABBREVIATIONS
from .timezone import UTC_TZ

alias formatter = _Formatter()


struct _Formatter:
var _sub_chrs: InlinedFixedVector[Int, 128]

fn __init__(inout self):
self._sub_chrs = InlinedFixedVector[Int, 128](0)
for i in range(128):
self._sub_chrs[i] = 0
self._sub_chrs[_Y] = 4
self._sub_chrs[_M] = 4
self._sub_chrs[_D] = 2
self._sub_chrs[_d] = 4
self._sub_chrs[_H] = 2
self._sub_chrs[_h] = 2
self._sub_chrs[_m] = 2
self._sub_chrs[_s] = 2
self._sub_chrs[_S] = 6
self._sub_chrs[_Z] = 3
self._sub_chrs[_A] = 1
self._sub_chrs[_a] = 1

fn format(self, m: Morrow, fmt: String) raises -> String:
"""
"YYYY[abc]MM" -> repalce("YYYY") + "abc" + replace("MM")
"""
if len(fmt) == 0:
return ""
var ret: String = ""
var in_bracket = False
var start_idx = 0
for i in range(len(fmt)):
if fmt[i] == "[":
if in_bracket:
ret += "["
else:
in_bracket = True
ret += self.replace(m, fmt[start_idx:i])
start_idx = i + 1
elif fmt[i] == "]":
if in_bracket:
ret += fmt[start_idx:i]
in_bracket = False
else:
ret += self.replace(m, fmt[start_idx:i])
ret += "]"
start_idx = i + 1
if in_bracket:
ret += "["
if start_idx < len(fmt):
ret += self.replace(m, fmt[start_idx:])
return ret

fn replace(self, m: Morrow, s: String) raises -> String:
"""
split token and replace
"""
if len(s) == 0:
return ""
var ret: String = ""
var match_chr_ord = 0
var match_count = 0
for i in range(len(s)):
let c = ord(s[i])
if 0 < c < 128 and self._sub_chrs[c] > 0:
if c == match_chr_ord:
match_count += 1
else:
ret += self.replace_token(m, match_chr_ord, match_count)
match_chr_ord = c
match_count = 1
if match_count == self._sub_chrs[c]:
ret += self.replace_token(m, match_chr_ord, match_count)
match_chr_ord = 0
else:
if match_chr_ord > 0:
ret += self.replace_token(m, match_chr_ord, match_count)
match_chr_ord = 0
ret += s[i]
if match_chr_ord > 0:
ret += self.replace_token(m, match_chr_ord, match_count)
return ret

fn replace_token(self, m: Morrow, token: String, token_count: Int) raises -> String:
if token == _Y:
if token_count == 1:
return "Y"
if token_count == 2:
return rjust(m.year, 4, "0")[2:4]
if token_count == 4:
return rjust(m.year, 4, "0")
elif token == _M:
if token_count == 1:
return String(m.month)
if token_count == 2:
return rjust(m.month, 2, "0")
if token_count == 3:
return String(MONTH_ABBREVIATIONS[m.month])
if token_count == 4:
return String(MONTH_NAMES[m.month])
elif token == _D:
if token_count == 1:
return String(m.day)
if token_count == 2:
return rjust(m.day, 2, "0")
elif token == _H:
if token_count == 1:
return String(m.hour)
if token_count == 2:
return rjust(m.hour, 2, "0")
elif token == _h:
var h_12 = m.hour
if m.hour > 12:
h_12 -= 12
if token_count == 1:
return String(h_12)
if token_count == 2:
return rjust(h_12, 2, "0")
elif token == _m:
if token_count == 1:
return String(m.minute)
if token_count == 2:
return rjust(m.minute, 2, "0")
elif token == _s:
if token_count == 1:
return String(m.second)
if token_count == 2:
return rjust(m.second, 2, "0")
elif token == _S:
if token_count == 1:
return String(m.microsecond // 100000)
if token_count == 2:
return rjust(m.microsecond // 10000, 2, "0")
if token_count == 3:
return rjust(m.microsecond // 1000, 3, "0")
if token_count == 4:
return rjust(m.microsecond // 100, 4, "0")
if token_count == 5:
return rjust(m.microsecond // 10, 5, "0")
if token_count == 6:
return rjust(m.microsecond, 6, "0")
elif token == _d:
if token_count == 1:
return String(m.isoweekday())
if token_count == 3:
return String(DAY_ABBREVIATIONS[m.isoweekday()])
if token_count == 4:
return String(DAY_NAMES[m.isoweekday()])
elif token == _Z:
if token_count == 3:
return UTC_TZ.name if m.tz.is_none() else m.tz.name
var separator = "" if token_count == 1 else ":"
if m.tz.is_none():
return UTC_TZ.format(separator)
else:
return m.tz.format(separator)

elif token == _a:
return "am" if m.hour < 12 else "pm"
elif token == _A:
return "AM" if m.hour < 12 else "PM"
return ""


alias _Y = ord("Y")
alias _M = ord("M")
alias _D = ord("D")
alias _d = ord("d")
alias _H = ord("H")
alias _h = ord("h")
alias _m = ord("m")
alias _s = ord("s")
alias _S = ord("S")
alias _X = ord("X")
alias _x = ord("x")
alias _Z = ord("Z")
alias _A = ord("A")
alias _a = ord("a")
26 changes: 26 additions & 0 deletions morrow/morrow.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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 .formatter import formatter
from .constants import _DAYS_BEFORE_MONTH, _DAYS_IN_MONTH
from python.object import PythonObject
from python import Python
Expand Down Expand Up @@ -129,6 +130,26 @@ struct Morrow(StringableRaising):
let tzinfo = TimeZone.from_utc(tz_str)
return Self.strptime(date_str, fmt, tzinfo)

fn format(self, fmt: String = "YYYY-MM-DD HH:mm:ss ZZ") raises -> String:
"""Returns a string representation of the `Morrow`
formatted according to the provided format string.

:param fmt: the format string.

Usage::
>>> let m = Morrow.now()
>>> m.format('YYYY-MM-DD HH:mm:ss ZZ')
'2013-05-09 03:56:47 -00:00'

>>> m.format('MMMM DD, YYYY')
'May 09, 2013'

>>> m.format()
'2013-05-09 03:56:47 -00:00'

"""
return formatter.format(self, fmt)

fn isoformat(
self, sep: String = "T", timespec: StringLiteral = "auto"
) raises -> String:
Expand Down Expand Up @@ -276,6 +297,11 @@ struct Morrow(StringableRaising):
# start of that month: we're done!
return Self(year, month, n + 1)

fn isoweekday(self) raises -> Int:
# "Return day of the week, where Monday == 1 ... Sunday == 7."
# 1-Jan-0001 is a Monday
return self.toordinal() % 7 or 7

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

Expand Down
6 changes: 4 additions & 2 deletions morrow/timezone.mojo
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from .util import rjust
from ._libc import c_localtime

alias UTC_TZ = TimeZone(0, "UTC")


@value
struct TimeZone(Stringable):
Expand Down Expand Up @@ -60,7 +62,7 @@ struct TimeZone(Stringable):
let offset: Int = sign * (hours * 3600 + minutes * 60)
return TimeZone(offset)

fn format(self) -> String:
fn format(self, sep: String = ":") -> String:
let sign: String
let offset_abs: Int
if self.offset < 0:
Expand All @@ -71,4 +73,4 @@ struct TimeZone(Stringable):
offset_abs = self.offset
let hh = offset_abs // 3600
let mm = offset_abs % 3600
return sign + rjust(hh, 2, "0") + ":" + rjust(mm, 2, "0")
return sign + rjust(hh, 2, "0") + sep + rjust(mm, 2, "0")
18 changes: 15 additions & 3 deletions test.mojo
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ def test_time_zone():

def test_strptime():
print("Running test_strptime()")
m = Morrow.strptime("20-01-2023 15:49:10", "%d-%m-%Y %H:%M:%S", TimeZone.local())
assert_equal(str(m), "2023-01-20T15:49:10.000000+08:00")
m = Morrow.strptime("20-01-2023 15:49:10", "%d-%m-%Y %H:%M:%S", TimeZone.none())
assert_equal(str(m), "2023-01-20T15:49:10.000000+00:00")

m = Morrow.strptime("2023-10-18 15:49:10 +0800", "%Y-%m-%d %H:%M:%S %z")
assert_equal(str(m), "2023-10-18T15:49:10.000000+08:00")
Expand Down Expand Up @@ -148,7 +148,7 @@ def test_timedelta():


def test_from_to_py():
print("Running test_from_to_py")
print("Running test_from_to_py()")
m = Morrow.now()
dt = m.to_py()
assert_datetime_equal(m, dt)
Expand All @@ -157,6 +157,17 @@ def test_from_to_py():
assert_datetime_equal(m2, dt)


def test_format():
print("Running test_format()")
let m = Morrow(2024, 2, 1, 3, 4, 5, 123456)
assert_equal(m.format("YYYY-MM-DD HH:mm:ss.SSS ZZ"), "2024-02-01 03:04:05.123 +00:00")
assert_equal(m.format("Y-YY-YYY-YYYY M-MM D-DD"), "Y-24--2024 2-02 1-01")
assert_equal(m.format("H-HH-h-hh m-mm s-ss"), "3-03-3-03 4-04 5-05")
assert_equal(m.format("S-SS-SSS-SSSS-SSSSS-SSSSSS"), "1-12-123-1234-12345-123456")
assert_equal(m.format("d-dd-ddd-dddd"), "4--Thu-Thursday")
assert_equal(m.format("YYYY[Y] [[]MM[]][M]"), "2024Y [02]M")


def main():
test_now()
test_utcnow()
Expand All @@ -168,3 +179,4 @@ def main():
test_strptime()
test_timedelta()
test_from_to_py()
test_format()
Loading