3
0
Fork 0

feat: add localtime() function, app method

This commit is contained in:
Lance Edgar 2025-12-15 22:05:33 -06:00
parent e76a6e5f6d
commit dac91406c7
4 changed files with 122 additions and 0 deletions

View file

@ -35,6 +35,7 @@ from importlib.metadata import version
import humanize
from wuttjamaican.util import (
localtime,
load_entry_points,
load_object,
make_title,
@ -527,6 +528,14 @@ class AppHandler: # pylint: disable=too-many-public-methods
"""
return make_full_name(*parts)
def localtime(self, dt=None, tzinfo=True):
"""
This returns a datetime in the system-local timezone. It is a
convenience wrapper around
:func:`~wuttjamaican.util.localtime()`.
"""
return localtime(dt=dt, tzinfo=tzinfo)
def make_utc(self, dt=None, tzinfo=False):
"""
This returns a datetime local to the UTC timezone. It is a
@ -799,6 +808,10 @@ class AppHandler: # pylint: disable=too-many-public-methods
"""
if value is None:
return ""
if not value.tzinfo:
value = self.localtime(value)
return value.strftime(self.display_format_datetime)
def render_error(self, error):

View file

@ -190,6 +190,58 @@ def make_full_name(*parts):
return " ".join(parts)
def localtime(dt=None, tzinfo=True):
"""
This returns a datetime in the system-local timezone. By default
it will be *zone-aware*.
See also the shortcut
:meth:`~wuttjamaican.app.AppHandler.localtime()` method on the app
handler.
See also :func:`make_utc()` which is sort of the inverse.
:param dt: Optional :class:`python:datetime.datetime` instance.
If not specified, the current time will be used.
:param tzinfo: Boolean indicating whether the return value should
have its :attr:`~python:datetime.datetime.tzinfo` attribute
set. This is true by default in which case the return value
will be zone-aware.
:returns: :class:`python:datetime.datetime` instance in
system-local timezone.
"""
# thanks to this stackoverflow post for the timezone logic,
# since as of now we don't have that anywhere in config.
# https://stackoverflow.com/a/39079819
# https://docs.python.org/3/library/datetime.html#datetime.datetime.astimezone
# use current time if none provided
if dt is None:
dt = datetime.datetime.now(datetime.timezone.utc)
dt = dt.astimezone()
if tzinfo:
return dt
return dt.replace(tzinfo=None)
# otherwise may need to convert timezone
if dt.tzinfo:
dt = dt.astimezone()
if tzinfo:
return dt
return dt.replace(tzinfo=None)
# naive value returned as-is..
if not tzinfo:
return dt
# ..unless tzinfo is wanted, in which case this assumes naive
# value is in the UTC timezone
dt = dt.replace(tzinfo=datetime.timezone.utc)
return dt.astimezone()
def make_utc(dt=None, tzinfo=False):
"""
This returns a datetime local to the UTC timezone. By default it
@ -200,6 +252,8 @@ def make_utc(dt=None, tzinfo=False):
:meth:`~wuttjamaican.app.AppHandler.make_utc()` method on the app
handler.
See also :func:`localtime()` which is sort of the inverse.
:param dt: Optional :class:`python:datetime.datetime` instance.
If not specified, the current time will be used.

View file

@ -426,6 +426,11 @@ app_title = WuttaTest
name = self.app.make_full_name("Fred", "", "Flintstone", "")
self.assertEqual(name, "Fred Flintstone")
def test_localtime(self):
dt = self.app.localtime()
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNotNone(dt.tzinfo)
def test_make_utc(self):
dt = self.app.make_utc()
self.assertIsInstance(dt, datetime.datetime)
@ -516,6 +521,11 @@ app_title = WuttaTest
dt = datetime.datetime(2024, 12, 11, 8, 30, tzinfo=datetime.timezone.utc)
self.assertEqual(self.app.render_datetime(dt), "2024-12-11 08:30+0000")
dt = datetime.datetime(2024, 12, 11, 8, 30)
text = self.app.render_datetime(dt)
# TODO: should override local timezone for more complete test
self.assertTrue(text.startswith("2024-12-"))
def test_render_error(self):
# with description

View file

@ -165,6 +165,51 @@ class TestLoadObject(TestCase):
self.assertIs(result, TestCase)
class TestLocaltime(TestCase):
def test_current_time(self):
# has tzinfo by default
dt = mod.localtime()
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNotNone(dt.tzinfo)
now = datetime.datetime.now()
self.assertAlmostEqual(int(dt.timestamp()), int(now.timestamp()))
# no tzinfo
dt = mod.localtime(tzinfo=False)
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNone(dt.tzinfo)
now = datetime.datetime.now()
self.assertAlmostEqual(int(dt.timestamp()), int(now.timestamp()))
def test_convert_with_tzinfo(self):
sample = datetime.datetime(2024, 9, 15, 13, 30, tzinfo=datetime.timezone.utc)
# has tzinfo by default
dt = mod.localtime(sample)
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNotNone(dt.tzinfo)
# no tzinfo
dt = mod.localtime(sample, tzinfo=False)
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNone(dt.tzinfo)
def test_convert_without_tzinfo(self):
sample = datetime.datetime(2024, 9, 15, 13, 30)
# has tzinfo by default
dt = mod.localtime(sample)
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNotNone(dt.tzinfo)
# no tzinfo
dt = mod.localtime(sample, tzinfo=False)
self.assertIsInstance(dt, datetime.datetime)
self.assertIsNone(dt.tzinfo)
class TestMakeUTC(TestCase):
def test_current_time(self):