Skip to content

Commit 5b0897b

Browse files
authored
Merge pull request #5169 from grondo/utildatetime-hyphen
python: improve handling of `!d` conversion specifier in output formats
2 parents e3c8698 + c73aa4f commit 5b0897b

File tree

4 files changed

+59
-13
lines changed

4 files changed

+59
-13
lines changed

doc/man1/flux-jobs.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,8 @@ the following conversion flags are supported by *flux-jobs*:
221221
convert a timestamp to a Python datetime object. This allows datetime
222222
specific format to be used, e.g. *{t_inactive!d:%H:%M:%S}*. Additionally,
223223
width and alignment can be specified after the time format by using
224-
two colons (``::``), e.g. *{t_inactive!d:%H:%M:%S::>20}*. Defaults to
225-
datetime of epoch if timestamp field does not exist.
224+
two colons (``::``), e.g. *{t_inactive!d:%H:%M:%S::>20}*. Returns an
225+
empty string (or "-" if the *h* suffix is used) for an unset timestamp.
226226

227227
**!F**
228228
convert a time duration in floating point seconds to Flux Standard

src/bindings/python/flux/util.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -312,16 +312,28 @@ class UtilDatetime(datetime):
312312
def __format__(self, fmt):
313313
# The string "::" is used to split the strftime() format from
314314
# any Python format spec:
315-
vals = fmt.split("::", 1)
315+
timefmt, *spec = fmt.split("::", 1)
316316

317-
# Call strftime() to get the formatted datetime as a string
318-
result = self.strftime(vals[0])
317+
if self == datetime.fromtimestamp(0.0):
318+
result = ""
319+
else:
320+
# Call strftime() to get the formatted datetime as a string
321+
result = self.strftime(timefmt or "%FT%T")
322+
323+
spec = spec[0] if spec else ""
324+
325+
# Handling of the 'h' suffix on spec is required here, since the
326+
# UtilDatetime format() is called _after_ UtilFormatter.format_field()
327+
# (where this option is handled for other types)
328+
if spec.endswith("h"):
329+
if not result:
330+
result = "-"
331+
spec = spec[:-1] + "s"
319332

320333
# If there was a format spec, apply it here:
321-
try:
322-
return f"{{0:{vals[1]}}}".format(result)
323-
except IndexError:
324-
return result
334+
if spec:
335+
return f"{{0:{spec}}}".format(result)
336+
return result
325337

326338

327339
def fsd(secs):
@@ -421,7 +433,10 @@ def format_field(self, value, spec):
421433
denote_truncation = True
422434
spec = spec[:-1]
423435

424-
if spec.endswith("h"):
436+
# Note: handling of the 'h' suffix for UtilDatetime objects
437+
# must be deferred to the UtilDatetetime format() method, since
438+
# that method will be called after this one:
439+
if spec.endswith("h") and not isinstance(value, UtilDatetime):
425440
localepoch = datetime.fromtimestamp(0.0).strftime("%FT%T")
426441
basecases = ("", "0s", "0.0", "0:00:00", "1970-01-01T00:00:00", localepoch)
427442
value = "-" if str(value) in basecases else str(value)

t/python/t0024-util.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from pycotap import TAPTestRunner
1616

1717
from datetime import datetime, timedelta
18-
from flux.util import parse_datetime
18+
from flux.util import parse_datetime, UtilDatetime
1919

2020

2121
def ts(year, month, day, hour=0, minute=0, sec=0, us=0):
@@ -70,6 +70,25 @@ def test_nlp(self):
7070
self.assertEqual(self.parse("noon tomorrow"), ts(2021, 6, 11, 12))
7171
self.assertEqual(self.parse("last wed"), ts(2021, 6, 9))
7272

73+
class TestUtilDatetime(unittest.TestCase):
74+
@classmethod
75+
def setUpClass(self):
76+
self.zero = UtilDatetime.fromtimestamp(0.)
77+
self.ts = UtilDatetime(2021, 6, 10, 8, 0, 0)
78+
79+
def test_zero(self):
80+
self.assertEqual(f"{self.zero}", "")
81+
self.assertEqual(f"{self.zero:::h}", "-")
82+
self.assertEqual(f"{self.zero:%FT%T::<6}", " ")
83+
self.assertEqual(f"{self.zero:%FT%T::<6h}", "- ")
84+
85+
def test_fmt(self):
86+
self.assertEqual(f"{self.ts}", "2021-06-10T08:00:00")
87+
self.assertEqual(f"{self.ts:::h}", "2021-06-10T08:00:00")
88+
self.assertEqual(f"{self.ts:%b%d %R}", "Jun10 08:00")
89+
self.assertEqual(f"{self.ts:%b%d %R::h}", "Jun10 08:00")
90+
self.assertEqual(f"{self.ts:%b%d %R::>12}", " Jun10 08:00")
91+
self.assertEqual(f"{self.ts:%b%d %R::>12h}", " Jun10 08:00")
7392

7493
if __name__ == "__main__":
7594
unittest.main(testRunner=TAPTestRunner())

t/t2800-jobs-cmd.t

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -937,9 +937,20 @@ test_expect_success 'flux-jobs --format={expiration!d:%FT%T::>20.20} works' '
937937
cat expiration.in | \
938938
flux jobs --from-stdin -o "{expiration!d:%b%d %R::>20.20}" \
939939
>exp-fmt.out &&
940+
cat expiration.in | \
941+
flux jobs --from-stdin -o "{expiration!d:%b%d %R::>20.20h}" \
942+
>exp-fmth.out &&
940943
test_debug "cat exp-fmt.out" &&
941944
grep " EXPIRATION" exp-fmt.out &&
942-
grep " $(date --date=@${exp} +%b%d\ %R)" exp-fmt.out
945+
grep " $(date --date=@${exp} +%b%d\ %R)" exp-fmt.out &&
946+
test_debug "cat exp-fmth.out" &&
947+
grep " EXPIRATION" exp-fmth.out &&
948+
grep " $(date --date=@${exp} +%b%d\ %R)" exp-fmth.out
949+
'
950+
test_expect_success 'flux-jobs --format={expiration!d} works' '
951+
cat expiration.in | flux jobs --from-stdin -o "{expiration!d}" \
952+
>exp-fmtd.out &&
953+
grep "$(date --date=@${exp} +%FT%T)" exp-fmtd.out
943954
'
944955
test_expect_success 'flux-jobs --format={expiration!d:%FT%T::=^20} works' '
945956
cat expiration.in | \
@@ -1002,13 +1013,14 @@ test_expect_success 'flux-jobs emits empty string for special case t_estimate' '
10021013
fmt="${fmt},{annotations.sched.t_estimate!D}" &&
10031014
fmt="${fmt},{annotations.sched.t_estimate!F}" &&
10041015
fmt="${fmt},{annotations.sched.t_estimate!H}" &&
1016+
fmt="${fmt},{annotations.sched.t_estimate!d:%H:%M::h}" &&
10051017
fmt="${fmt},{annotations.sched.t_estimate!D:h}" &&
10061018
fmt="${fmt},{annotations.sched.t_estimate!F:h}" &&
10071019
fmt="${fmt},{annotations.sched.t_estimate!H:h}" &&
10081020
flux jobs -no "${fmt}" >t_estimate_annotations.out 2>&1 &&
10091021
test_debug "cat t_estimate_annotations.out" &&
10101022
for i in `seq 1 $(state_count active)`; do
1011-
echo ",00:00,,,,-,-,-" >> t_estimate_annotations.exp
1023+
echo ",,,,,-,-,-,-" >> t_estimate_annotations.exp
10121024
done &&
10131025
test_cmp t_estimate_annotations.out t_estimate_annotations.exp
10141026
'

0 commit comments

Comments
 (0)