8000 make format codes c std compatible, adjust concise defaults · matplotlib/matplotlib@8e4aab3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8e4aab3

Browse files
committed
make format codes c std compatible, adjust concise defaults
1 parent 7cbc56b commit 8e4aab3

File tree

2 files changed

+102
-59
lines changed

2 files changed

+102
-59
lines changed

lib/matplotlib/dates.py

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -612,23 +612,61 @@ def _wrap_in_tex(text):
612612
class _TimedeltaFormatTemplate(string.Template):
613613
# formatting template for datetime-like formatter strings
614614
delimiter = '%'
615+
idpattern = r'[>-]?[dHMSf]{1}'
616+
617+
# add VERBOSE to override default IGNORECASE; VERBOSE is always added
618+
# anyway by the template class, so this is effectively a None flag here
619+
flags = re.VERBOSE
615620

616621

617622
def strftimedelta(td, fmt_str):
618623
"""
619624
Return a string representing a timedelta, controlled by an explicit
620625
format string.
621626
627+
The format codes are similar to the C standard format codes for formatting
628+
dates. All format codes that are reasonably transferable to timedelta are
629+
supported. Additionally, some extensions to the C standard are defined.
630+
631+
The following is a full list of the format codes that are supported.
632+
633+
+-----------+---------------------------------------+---------------------+
634+
| Directive | Meaning | Example |
635+
+-----------+---------------------------------------+---------------------+
636+
| %d | Days | 0, 1, 2, ... |
637+
+-----------+---------------------------------------+---------------------+
638+
| %H | Hours as zero-padded decimal number | 00, 01, ..., 23 |
639+
+-----------+---------------------------------------+---------------------+
640+
| %M | Minutes as zero-padded decimal number | 00, 01, ..., 59 |
641+
+-----------+---------------------------------------+---------------------+
642+
| %S | Seconds as zero-padded decimal number | 00, 01, ..., 59 |
643+
+-----------+---------------------------------------+---------------------+
644+
| %-H | Hours as decimal number | 0, 1, ..., 23 |
645+
+-----------+---------------------------------------+---------------------+
646+
| %-M | Minutes as decimal number | 0, 1, ..., 59 |
647+
+-----------+---------------------------------------+---------------------+
648+
| %-S | Seconds as decimal number | 0, 1, ..., 59 |
649+
+-----------+---------------------------------------+---------------------+
650+
| %>H | Total number of hours including days | 0, 1, ..., 100, ... |
651+
+-----------+---------------------------------------+---------------------+
652+
| %>M | Total number of minutes including | 0, 1, ..., 100, ... |
653+
| | days and hours | |
654+
+-----------+---------------------------------------+---------------------+
655+
| %>S | Total number of seconds including | 0, 1, ..., 100, ... |
656+
| | days, hours and minutes | |
657+
+-----------+---------------------------------------+---------------------+
658+
| %f | Microseconds as a decimal number, | 000000, 000001, ... |
659+
| | zero-padded to 6 digits | 999999 |
660+
+-----------+---------------------------------------+---------------------+
661+
662+
# TODO: move format code docs to general section at the top
663+
622664
Arguments
623665
---------
624666
td : datetime.timedelta
625667
fmt_str : str
626668
format string
627669
"""
628-
# TODO: make as compatible as possible with strftime format strings,
629-
# remove %day
630-
# *_t values are not partially consumed by there next larger unit
631-
# e.g. for timedelta(days=1.5): d=1, h=12, H=36
632670
s_t = td.total_seconds()
633671
sign = '-' if s_t < 0 else ''
634672
s_t = abs(s_t)
@@ -639,20 +677,22 @@ def strftimedelta(td, fmt_str):
639677
h_t, _ = divmod(s_t, SEC_PER_HOUR)
640678

641679
us = td.microseconds
642-
ms, us = divmod(us, 1e3)
643-
644-
# create correctly zero padded string for substitution
645-
# last one is a special for correct day(s) plural
646-
values = {'d': int(d),
647-
'H': int(h_t),
648-
'M': int(m_t),
649-
'S': int(s_t),
650-
'h': '{:02d}'.format(int(h)),
651-
'm': '{:02d}'.format(int(m)),
652-
's': '{:02d}'.format(int(s)),
653-
'ms': '{:03d}'.format(int(ms)),
654-
'us': '{:03d}'.format(int(us)),
655-
'day': 'day' if d == 1 else 'days'}
680+
681+
# define substitution strings; all reasonable equivalents from the c
682+
# standard implementation for strftime for dates are supported
683+
# >H, >M, >S are total values and not partially consumed by there next
684+
# larger units e.g. for timedelta(days=1.5): d=1, h=12, H=36
685+
values = {'d': int(d), # days; equivalent to c standard
686+
'>H': int(h_t), # total number of h, m, s;
687+
'>M': int(m_t), # extension to c standard
688+
'>S': int(s_t),
689+
'H': '{:02d}'.format(int(h)), # zero-padded h, m, s;
690+
'M': '{:02d}'.format(int(m)), # equivalent to c std
691+
'S': '{:02d}'.format(int(s)),
692+
'-H': int(h), # h, m, s without zero-padding;
693+
'-M': int(m), # platform specific in c std
694+
'-S': int(s),
695+
'f': '{:06d}'.format(int(us))} # microseconds, equiv. c std
656696

657697
try:
658698
result = _TimedeltaFormatTemplate(fmt_str).substitute(**values)
@@ -999,10 +1039,10 @@ def __init__(self, locator, formats=None, offset_formats=None,
9991039
'4 format strings (or None)')
10001040
self.formats = formats
10011041
else:
1002-
self.formats = ['%{d}D', # days
1003-
'%h:%m', # hours
1004-
'%h:%m', # minutes
1005-
'%s.%ms%us', # secs
1042+
self.formats = ['%{d} days', # days
1043+
'%-H:%M', # hours
1044+
'%-H:%M', # minutes
1045+
'%-S.%f', # secs
10061046
]
10071047
# fmt for zeros ticks at this level. These are
10081048
# ticks that should be labeled w/ info the level above.
@@ -1024,8 +1064,8 @@ def __init__(self, locator, formats=None, offset_formats=None,
10241064
else:
10251065
self.offset_formats = ['',
10261066
'',
1027-
'%{d}D',
1028-
'%{d}D %h:%m']
1067+
'%{d} days',
1068+
'%{d} days %-H:%M']
10291069

10301070
def __call__(self, x, pos=None):
10311071
formatter = TimedeltaFormatter(self.defaultfmt, usetex=self._usetex)
@@ -1057,7 +1097,7 @@ def format_ticks(self, values):
10571097
return super()._format_ticks(ticktimedelta, ticktuple)
10581098

10591099
def format_data_short(self, value):
1060-
return strftimedelta(num2timedelta(value), '%{d}D %h:%m:%s')
1100+
return strftimedelta(num2timedelta(value), '%{d}D %H:%M:%S')
10611101

10621102
def _format_string(self, value, fmt):
10631103
return strftimedelta(value, fmt)
@@ -1140,7 +1180,7 @@ class AutoTimedeltaFormatter(_AutoTimevalueFormatter):
11401180
11411181
locator = AutoTimedeltaLocator()
11421182
formatter = AutoTimedeltaFormatter(locator)
1143-
formatter.scaled[1/(24*60)] = '%m:%s' # only show min and sec
1183+
formatter.scaled[1/(24*60)] = '%M:%S' # only show min and sec
11441184
11451185
Custom callables can also be used instead of format strings. The following
11461186
example shows how to use a custom format function to strip trailing zeros
@@ -1152,7 +1192,7 @@ def my_format_function(x, pos=None):
11521192
formatter.scaled[1/(24*60)] = my_format_function
11531193
"""
11541194

1155-
def __init__(self, locator, defaultfmt='%d %day %h:%m', *, usetex=None):
1195+
def __init__(self, locator, defaultfmt='%d days %H:%M', *, usetex=None):
11561196
"""
11571197
Autoformat the timedelta labels.
11581198
@@ -1173,12 +1213,12 @@ def __init__(self, locator, defaultfmt='%d %day %h:%m', *, usetex=None):
11731213
"""
11741214
super().__init__(locator, defaultfmt=defaultfmt, usetex=usetex)
11751215
self.scaled = {
1176-
1: "%d %day",
1177-
1 / HOURS_PER_DAY: '%d %day, %h:%m',
1178-
1 / MINUTES_PER_DAY: '%d %day, %h:%m',
1179-
1 / SEC_PER_DAY: '%d %day, %h:%m:%s',
1180-
1e3 / MUSECONDS_PER_DAY: '%d %day, %h:%m:%s.%ms',
1181-
1 / MUSECONDS_PER_DAY: '%d %day, %h:%m:%s.%ms%us',
1216+
1: "%d days",
1217+
1 / HOURS_PER_DAY: '%d days, %H:%M',
1218+
1 / MINUTES_PER_DAY: '%d days, %H:%M',
1219+
1 / SEC_PER_DAY: '%d days, %H:%M:%S',
1220+
1e3 / MUSECONDS_PER_DAY: '%d days, %H:%M:%S.%f',
1221+
1 / MUSECONDS_PER_DAY: '%d days, %H:%M:%S.%f',
11821222
}
11831223

11841224
def _get_template_formatter(self, fmt):

lib/matplotlib/tests/test_dates.py

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,15 +1554,17 @@ def test_timedelta_locators_fixed(kwargs, dt1, expected):
15541554
@pytest.mark.parametrize(
15551555
"td, fmt, expected",
15561556
[
1557-
(datetime.timedelta(days=1), "%d %day, %h:%m", "1 day, 00:00"),
1558-
(datetime.timedelta(days=2.25), "%d %day, %h:%m", "2 days, 06:00"),
1559-
(datetime.timedelta(seconds=362), "%h:%m:%s.%ms", "00:06:02.000"),
1560-
(datetime.timedelta(microseconds=1250), "%s.%ms%us", "00.001250"),
1561-
(datetime.timedelta(days=-0.25), "%h:%m", "-06:00"),
1562-
(datetime.timedelta(days=-1.5), "%d %day, %h:%m", "-1 day, 12:00"),
1563-
(datetime.timedelta(days=2), "%H hours", "48 hours"),
1564-
(datetime.timedelta(days=0.25), "%M min", "360 min"),
1565-
(datetime.timedelta(seconds=362.13), "%S.%ms", "362.130")
1557+
(datetime.timedelta(days=1), "%d days, %H:%M", "1 days, 00:00"),
1558+
(datetime.timedelta(days=2.25), "%d days, %H:%M", "2 days, 06:00"),
1559+
(datetime.timedelta(seconds=362), "%H:%M:%S.%f", "00:06:02.000000"),
1560+
(datetime.timedelta(microseconds=1250), "%S.%f", "00.001250"),
1561+
(datetime.timedelta(days=-0.25), "%H:%M", "-06:00"),
1562+
(datetime.timedelta(days=-1.5), "%d days, %H:%M", "-1 days, 12:00"),
1563+
(datetime.timedelta(days=2), "%>H hours", "48 hours"),
1564+
(datetime.timedelta(days=0.25), "%>M min", "360 min"),
1565+
(datetime.timedelta(hours=2), "%H hours", "02 hours"),
1566+
(datetime.timedelta(hours=2), "%-H hours", "2 hours"),
1567+
(datetime.timedelta(seconds=362.13), "%>S.%f", "362.130000")
15661568
]
15671569
)
15681570
def test_strftimedelta(td, fmt, expected):
@@ -1572,23 +1574,23 @@ def test_strftimedelta(td, fmt, expected):
15721574
@pytest.mark.parametrize(
15731575
"t_delta, fmt, expected",
15741576
[
1575-
[datetime.timedelta(days=141), "%d %day",
1577+
[datetime.timedelta(days=141), "%d days",
15761578
['100 days', '120 days', '140 days', '160 days', '180 days',
15771579
'200 days', '220 days', '240 days', '260 days']],
1578-
[datetime.timedelta(hours=40), "%d %day %h:%m",
1580+
[datetime.timedelta(hours=40), "%d days %H:%M",
15791581
['100 days 00:00', '100 days 06:00', '100 days 12:00',
15801582
'100 days 18:00', '101 days 00:00', '101 days 06:00',
15811583
'101 days 12:00', '101 days 18:00', '102 days 00:00']],
15821584
1583-
[datetime.timedelta(minutes=30), "%m:%s.0",
1585+
[datetime.timedelta(minutes=30), "%M:%S.0",
15841586
['40:00.0', '45:00.0', '50:00.0', '55:00.0', '00:00.0',
15851587
'05:00.0', '10:00.0', '15:00.0', '20:00.0']],
15861588
1587-
[datetime.timedelta(seconds=30), "% 1241 s.%ms",
1588-
['00.000', '05.000', '10.000', '15.000', '20.000', '25.000',
1589-
'30.000', '35.000']],
1589+
[datetime.timedelta(seconds=30), "%S.%f",
1590+
['00.000000', '05.000000', '10.000000', '15.000000', '20.000000',
1591+
'25.000000', '30.000000', '35.000000']],
15901592
1591-
[datetime.timedelta(microseconds=600), "%s.%ms%us",
1593+
[datetime.timedelta(microseconds=600), "%S.%f",
15921594
['59.999600', '59.999800', '00.000000', '00.000200',
15931595
'00.000400', '00.000600', '00.000800', '00.001000']]
15941596
]
@@ -1646,21 +1648,22 @@ def test_timedelta_formatter_usetex(delta, expected):
16461648

16471649
@pytest.mark.parametrize('t_delta, expected', [
16481650
[datetime.timedelta(days=141), # label on days
1649-
['100D', '120D', '140D', '160D', '180D', '200D', '220D', '240D', '260D']
1651+
['100 days', '120 days', '140 days', '160 days', '180 days',
1652+
'200 days', '220 days', '240 days', '260 days']
16501653
],
16511654
[datetime.timedelta(hours=40), # label on hh:mm, zero format on days
1652-
['100D', '06:00', '12:00', '18:00', '101D', '06:00', '12:00',
1653-
'18:00', '102D']
1655+
['100 days', '6:00', '12:00', '18:00', '101 days', '6:00', '12:00',
1656+
'18:00', '102 days']
16541657
],
16551658
[datetime.timedelta(minutes=30), # label on hh:mm, same for zero format
1656-
['03:40', '03:45', '03:50', '03:55', '04:00', '04:05', '04:10',
1657-
'04:15', '04:20']
1659+
['3:40', '3:45', '3:50', '3:55', '4:00', '4:05', '4:10',
1660+
'4:15', '4:20']
16581661
],
16591662
[datetime.timedelta(seconds=30), # label on seconds, zero format hh:mm
1660-
['03:45', '05', '10', '15', '20', '25', '30', '35']
1663+
['3:45', '5', '10', '15', '20', '25', '30', '35']
16611664
],
16621665
[datetime.timedelta(seconds=2), # label on seconds.f, zero format hh:mm
1663-
['03:45', '00.5', '01.0', '01.5', '02.0', '02.5']
1666+
['3:45', '0.5', '1.0', '1.5', '2.0', '2.5']
16641667
],
16651668
])
16661669
def test_concise_timedelta_formatter(t_delta, expected):
@@ -1683,9 +1686,9 @@ de 80E9 f test_concise_timedelta_formatter(t_delta, expected):
16831686
@pytest.mark.parametrize('t_delta, expected', [
16841687
[datetime.timedelta(days=141), ""],
16851688
[datetime.timedelta(hours=40), ""],
1686-
[datetime.timedelta(minutes=30), "100D"],
1687-
[datetime.timedelta(seconds=30), "100D 03:45"],
1688-
[datetime.timedelta(seconds=2), "100D 03:45"],
1689+
[datetime.timedelta(minutes=30), "100 days"],
1690+
[datetime.timedelta(seconds=30), "100 days 3:45"],
1691+
[datetime.timedelta(seconds=2), "100 days 3:45"],
16891692
])
16901693
def test_concise_timedelta_formatter_show_offset(t_delta, expected):
16911694
t1 = datetime.timedelta(days=100, hours=3, minutes=45)

0 commit comments

Comments
 (0)
0