Skip to content

Commit

Permalink
func: datetime.str{f,p}time
Browse files Browse the repository at this point in the history
  • Loading branch information
litlighilit committed Jul 12, 2024
1 parent abdcc99 commit 1933d88
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 33 deletions.
6 changes: 4 additions & 2 deletions src/pylib/Lib/datetime_impl/datetime_impl/meth.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import ./meth/[
consts, statics, init, getter, getter_requires_op, getter_of_date, hashImpl, op, aszone
consts, statics, init, getter, getter_requires_op, getter_of_date,
hashImpl, op, aszone, require_time_module
]

export consts, statics, init, getter, getter_requires_op, getter_of_date, hashImpl, op, aszone
export consts, statics, init, getter, getter_requires_op, getter_of_date,
hashImpl, op, aszone, require_time_module
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@

import ../../../time_impl/[
types, converters, nstrfptime
]

from std/strutils import multiReplace, replace, align, parseInt
import ../../timedelta_impl/decl
import ../../timezone_impl/[
decl, meth_by_datetime_getter
]

include ./common
from std/times import DateTime, yearday, toParts
import ./getter_of_date

from ./importer import NotImplementedError

using self: datetime

# shall also has a `self: date`
func timetuple*(self): struct_time =
var dstflag = -1
if not self.tzinfo.isTzNone:
let dst = self.tzinfo.dst(self)
if not dst.isTimeDeltaNone:
dstflag = int bool dst
self.asNimDatetime.dtToStructTime result

using tzinfoarg: datetime

proc add_somezreplacement(s: var string, sep: string, tzinfo: tzinfo, tzinfoarg) =
var offset: timedelta
try:
offset = tzinfo.utcoffset(tzinfoarg)
except ValueError, NotImplementedError:
return
s.add format_utcoffset(offset.asDuration.toParts(), sep, prefix="")

const Py_NORMALIZE_CENTURY = true # since CPython gh-120713
proc add_Zreplacement(s: var string, tzinfo: tzinfo, tzinfoarg) =
try:
s.add tzinfo.tzname(tzinfoarg)
except NotImplementedError:
return
#[Since the tzname is getting stuffed into the
format, we have to double any % signs so that
strftime doesn't treat them as format codes.]#
s = s.replace("%", "%%")

func wrap_strftime(self; format: string, tzinfoarg): string =
#[Scan the input format, looking for %z/%Z/%f escapes, building
a new format. Since computing the replacements for those codes
is expensive, don't unless they're actually used.]#

let tzinfo = self.tzinfo
var z_repl, Z_repl, z_col_repl: string
if not tzinfo.isTzNone:
z_repl.add_somezreplacement("", tzinfo, self)
Z_repl.add_Zreplacement(tzinfo, self)
z_col_repl.add_somezreplacement(":", tzinfo, self)

let baseRepl = {
"%z": z_repl,
"%Z": Z_repl,
"%:z": z_col_repl,
"%f": align($self.microsecond, 6, '0')
}
when Py_NORMALIZE_CENTURY:
let year = self.year
template pad4(i: SomeInteger): string =
align($i, 4, '0')
let
iG = parseInt(strftime("%G", self.asNimDatetime))
iY = self.year
let newfmt = format.multiReplace(
@baseRepl & @{
"%G": iG.pad4,
"%Y": iY.pad4,
}
)
else:
let newfmt = format.multiReplace(baseRepl)

strftime(newfmt, self.asNimDatetime)

func strftime*(self; format: string): string =
wrap_strftime(self, format, self)

using _: typedesc[datetime]
func strptime*(_; datetime_string, format: string): datetime =
var ndt: DateTime
ndt.strptime(datetime_string, format)
result = newDatetime(
ndt,
)

# TODO: easy: support all in nstrfptime.NotImplDirectives, with the help of calendar_utils

77 changes: 46 additions & 31 deletions src/pylib/Lib/datetime_impl/timezone_impl/decl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -82,49 +82,64 @@ func repr*(self: timezone): string =
result.add self.name
result.add ')'

func `$`*(self: timezone): string =
if self.name.len != 0: return self.name
let parts = self.offset.asDuration.toParts()
let
days = parts[Days]
secs = parts[Seconds]
us = parts[Microseconds]
if self.is_const_utc or
days == 0 and secs == 0 and us == 0:
return "UTC"
func format_utcoffset(hours, minutes, seconds, microseconds: int,
sep: char|string = ':', prefix="UTC"): string =
var
sign: char
offset: timedelta
if days < 0:
hours = hours
minutes = minutes
seconds = seconds
microseconds = microseconds
var sign = '+'
if hours < 0 or minutes < 0 or seconds < 0 or microseconds < 0:
template rev(i: var SomeInteger) =
i = -i
sign = '-'
offset = -self.offset
else:
sign = '+'
offset = self.offset # new ref
let
nparts = offset.asDuration.toParts()
microseconds = nparts[Microseconds]
var seconds = nparts[Seconds]
var minutes = divmod(seconds, 60, seconds)
let hours = divmod(minutes, 60, minutes)
result = newStringOfCap 12
result.add "UTC"
template add(c: char) = result.add c
add sign
rev hours
rev minutes
rev seconds
rev microseconds
let sepLen = when sep is char: 1 else: sep.len
result = newStringOfCap 7 + prefix.len + 3 * sepLen
result.add prefix
result.add sign
template add(cs: char|string) = result.add cs
template add0Nd(d: SomeInteger, n: int) =
# we know d >= 0
# we know `d` >= 0
let sd = $d
for _ in 1..(n-sd.len):
result.add '0'
result.add sd
template add02d(d: SomeInteger) = add0Nd(d, 2)
add02d hours
add ':'
add sep
add02d minutes
if seconds != 0:
add ':'
add sep
add02d seconds
if microseconds != 0:
result.setLen 19
result.setLen result.len + 7
add '.'
add0Nd microseconds, 6

func format_utcoffset*(parts: DurationParts, sep: string|char = ':',
prefix="UTC"): string =
## common code of CPython C-API `timezone_str` and `format_utcoffset`
## in `_datetimemodule.c
when not defined(release):
let days = parts[Days] + parts[Weeks] * 7
assert days == 0, "timezone's init shall check its offset is within one day"
let
hours = parts[Hours]
minutes = parts[Minutes]
seconds = parts[Seconds]
microseconds = parts[Microseconds]
format_utcoffset(hours, minutes, seconds, microseconds,
sep=sep, prefix=prefix)

func `$`*(self: timezone): string =
if self.isTzNone: return "None"
if self.name.len != 0: return self.name
if self.is_const_utc or
bool(self.offset) == false:
return "UTC"
format_utcoffset(self.offset.asDuration.toParts(), sep=':', prefix="UTC")
17 changes: 17 additions & 0 deletions src/pylib/Lib/test/test_datetime.nim
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,20 @@ suite "TestDate":
base = theclass(2000, 2, 29)
expect(ValueError):
_ = base.replace(year=2001)
test "strftime":
def test_strftime():
t = theclass(2005, 3, 2)
assertEqual(t.strftime("m:%m d:%d y:%Y"), "m:03 d:02 y:2005")
assertEqual(t.strftime(""), "") # SF bug #761337
assertEqual(t.strftime('x'*1000), 'x'*1000) # SF bug #1556784

check not compiles(t.strftime) # needs an arg
check not compiles(t.strftime("one", "two")) # too many args
check not compiles(t.strftime(42)) # arg wrong type

# test that unicode input is allowed (issue 2782)
assertEqual(t.strftime("%m"), "03")

# A naive object replaces %z, %:z and %Z w/ empty strings.
assertEqual(t.strftime("'%z' '%:z' '%Z'"), "'' '' ''")
test_strftime()

0 comments on commit 1933d88

Please sign in to comment.