Skip to content

Commit

Permalink
Merge pull request #8 from mojoto/feat/format
Browse files Browse the repository at this point in the history
feat: supoort format
  • Loading branch information
Prodesire authored Feb 17, 2024
2 parents 896385a + efb0ad7 commit 8ff075a
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 6 deletions.
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()

0 comments on commit 8ff075a

Please sign in to comment.