Search code examples
pythonformattingf-string

How do I format a date and also pad it with spaces?


Formatting appears to work differently if the object you're formatting is a date.

today = "aaa"
print(f'{today:>10}')

returns

       aaa

i.e. it has the padding.

If I now do this:

today = datetime.date.today()
print(f'{today:>10}')

then the response is

>10

Which is obviously not what I want. I've tried various other combinations where I put in the date format as well, but all it does is draw the date out and then also add in '>10' also.

How do I format a date with padding?


Solution

  • Python's scheme for formatting via f-strings (and the .format method of strings) allows the inserted data to override how the format specification works, using the __format__ magic method:

    >>> class Example:
    ...     def __format__(self, template):
    ...         return f'{template} formatting of {self.__class__.__name__} instance'
    ... 
    >>> f'{Example():test}'
    'test formatting of Example instance'
    

    datetime.date does this, so that time.strftime is used to do the formatting (after some manipulation, e.g. inserting a proxy time for dates and vice-versa):

    >>> help(today.__format__)
    Help on built-in function __format__:
    
    __format__(...) method of datetime.date instance
        Formats self with strftime.
    

    This means that codes like %Y etc. can be used, but field width specifiers (like >10) are not supported. The format string >10 doesn't contain any placeholders for any components of the date (or time), so you just get a literal >10 back.

    Fortunately, it is trivial to work around this. Simply coerce the date to string, and pad the string:

    >>> f'{str(today):>20}'
    '          2022-06-13'
    

    Or better yet, use the built-in syntax for such coercion:

    >>> f'{today!s:>20}' # !s for str(), !r for repr()
    '          2022-06-13'
    

    If you want to use the strftime formatting as well, do the formatting in two steps:

    >>> formatted = f'{today:%B %d, %Y}'
    >>> f'{formatted:>20}'
    '       June 13, 2022'
    

    Note that it does not work to nest format specifiers:

    >>> # the {{ is interpreted as an escaped literal {
    >>> f'{{today:%B %d, %Y}:>20}' 
      File "<stdin>", line 1
    SyntaxError: f-string: single '}' is not allowed
    >>> # the inner {} looks like a dict, but %B isn't an identifier
    >>> f'{ {today:%B %d, %Y}:>20}'
      File "<fstring>", line 1
        ( {today:%B %d, %Y})
                 ^
    SyntaxError: invalid syntax
    

    However, f-strings themselves can be nested (this is obviously not very elegant and will not scale well):

    >>> # instead of trying to format the result from another placeholder,
    >>> # we reformat an entire separately-formatted string:
    >>> f'{f"{today:%B %d, %Y}":>20}'
    '       June 13, 2022'