wasd wasd - 22 days ago 18
Python Question

How to replace missing parts of datetime strftime with zeroes?

I receive date objects and need to turn them into string according to format:

"%a %b %d %H:%M:%S %z %Y"


To achieve this I use Python's datetime.strftime method. The problem is that sometimes these date objects doesn't have all this data, for example, I can have both:

a = datetime.date(1999, 1, 2)
b = datetime.datetime(2015, 10, 1, 9, 38, 50, 920000, tzinfo=datetime.timezone.utc)


Tried with string format method:

"{:%a %b %d %H:%M:%S %z %Y}".format(a)


But if timezone is not set then it is dropped, so:

"{:%a %b %d %H:%M:%S %z %Y}".format(b)
'Thu Oct 01 09:38:50 +0000 2015'


but

"{:%a %b %d %H:%M:%S %z %Y}".format(a)
'Sat Jan 02 00:00:00 1999'


While for a it is expected to be:

'Sat Jan 02 00:00:00 +0000 1999'


Is it possible somehow to fill timezone with zeros?

Answer

As you've probably noticed from the strftime documentation, %z and %Z will yield an empty string if the datetime object is naive, i.e. if it doesn't have a timezone set.

If you always want it to emit +0000 even if you don't know the timezone, what you want is to treat it as UTC.

So set a default. If timezone isn't passed to you, use the timezone info as you did, i.e. tzinfo = datetime.timezone.utc. You'll always get a +0000 in that case.

Update, in response to comment:

Realize, that by the time you're at the lines beginning format, you're already committed. Your datetime at that point is already naive or aware, and it's well defined how strftime will behave in either situation. When setting the default, you probably shouldn't do it at the format point, but at the point of constructing the datetime.

I can't give further specific help without seeing more of your code, but you obviously have something that differentiates the data you have for a and b, so you must know if you have the tzinfo available. I'm going to make up an example, so I can show you where to put your default:

for it in magic_iterator:
   (year, month, day, hour, minute, second, microsecond, timezone) = it.get_data()
   # note, some of these can be None.  Set defaults:
   hour = hour or 0
   minute = minute or 0
   second = second or 0
   timezone = timezone or datetime.tzinfo.utc

   foo = datetime(year, month, day, hour, minute, second, microsecond, timezone)
   # do something with foo

This construct with the or will see if those values are falsey (i.e. False, 0, or an empty string), and if so, set them appropriately. Note that in the case of hour, minute and second, if the actual value is 0, the or part of the clause will execute, since 0 is falsey. This is normally a logic error (solved by doing something like hour = hour if hour is not None else 0) but in this case, you'd be setting something to 0 that's already 0, so it's ok.

Come to think of it, you can do it as a one liner attached to the format, but it's ugly:

"{:%a %b %d %H:%M:%S %z %Y}".format(a if a.tzinfo else datetime(a.year, a.month, a.day, a.hour, a.minute, a.second, a.microsecond, datetime.tzinfo.utc))

I would much prefer to put the default in the construction of the datetime than in the formatting line.

Comments