Search code examples
pythondatetimestring-formatting

How to force pythons format method to place values as they are evaluated


I need to figure out the accounting year based on a date, while I am using the format method with datetime.datetime object it is generating the unexpected results for the same type objects with different values

Below is my code.

from datetime import datetime

dt = datetime.strptime('2019-03-03','%Y-%m-%d')
## Below line is killing my mind as it is resulting 2019-2018
print('{}-{}'.format(dt.year, (dt.year+1)%100 if dt.month > 3 else dt.year-1,(dt.year)%100))
# This will produce the result 3 2019 19
print(dt.month, dt.year, (dt.year)%100)

dt = datetime.strptime('2019-04-04','%Y-%m-%d')
# But the below line is working fine as it is resulting 2019-20
print('{}-{}'.format(dt.year, (dt.year+1)%100 if dt.month > 3 else dt.year-1,(dt.year)%100))
# This will produce the result 4 2019 19
print(dt.month, dt.year, (dt.year)%100)

I am expecting the result

2018-19 if dt = datetime.strptime('2019-03-03','%Y-%m-%d')
2019-20 if dt = datetime.strptime('2019-04-04','%Y-%m-%d')

I am not able to figure out the problem with the code.


Solution

  • ## Below line is killing my mind as it is resulting 2019-2018
    print('{}-{}'.format(dt.year, (dt.year+1)%100 if dt.month > 3 else dt.year-1,(dt.year)%100))
    

    Okay, so let's break down your code:

    '{}-{}'.format(dt.year, (dt.year+1)%100 if dt.month > 3 else dt.year-1,(dt.year)%100)

    You have 2 {}s but 3(!) arguments:

    • dt.year,
    • (dt.year+1)%100 if dt.month > 3 else dt.year-1,
    • (dt.year)%100 (ignored because there are only 2 {}s)

    As you can see, if/else works only for the middle argument.

    What you want is to use this if on both arguments, so you either need to repeat the if or use parentheses to group things. But the grouping will result in a tuple, so you need to unpack the values with * (I mentioned grouping in the comments, but forgot about unpacking).

    Solution with 2 ifs:

    '{}-{}'.format(dt.year if dt.month > 3 else dt.year-1, 
                   (dt.year+1)%100 if dt.month > 3 else (dt.year)%100)
    

    As you can see, one comma - two arguments. Broke it down into two lines for readability.

    Solution with one if and tuple unpacking:

    '{}-{}'.format( *(dt.year, (dt.year+1)%100) if dt.month > 3 else *(dt.year-1,(dt.year)%100) )
    

    Why unpacking? Because '{}-{}'.format( ('2018','19') ) gets a single argument that is a tuple, not two arguments. It doesn't know what to do with it. * in front unpacks lists or tuples and provide them as normal arguments. - Read more about it here in the documentation.