101

I want to store a datetime object with a localized UTC timezone. The method that stores the datetime object can be given a non-localized datetime (naive) object or an object that already has been localized. How do I determine if localization is needed?

Code with missing if condition:

class MyClass:
  def set_date(self, d):
    # what do i check here?
    # if(d.tzinfo):
      self.date = d.astimezone(pytz.utc)
    # else:
      self.date = pytz.utc.localize(d)
chiborg
  • 26,978
  • 14
  • 97
  • 115

4 Answers4

175

How do I determine if localization is needed?

From datetime docs:

  • a datetime object d is aware iff:

    d.tzinfo is not None and d.tzinfo.utcoffset(d) is not None
    
  • d is naive iff:

    d.tzinfo is None or d.tzinfo.utcoffset(d) is None
    

Though if d is a datetime object representing time in UTC timezone then you could use in both cases:

self.date = d.replace(tzinfo=pytz.utc)

It works regardless d is timezone-aware or naive.

Note: don't use datetime.replace() method with a timezone with a non-fixed utc offset (it is ok to use it with UTC timezone but otherwise you should use tz.localize() method).

Demetris
  • 2,981
  • 2
  • 25
  • 33
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • I know that's what the docs say, but in what scenario can you have a tzinfo object that is not really specifying a timezone and thus `d.tzinfo.utcoffset(d) is None` is needed? – Geekfish Apr 08 '15 at 10:37
  • 1
    @Geekfish: no idea. All sane implementations should either return non-None value or raise an exception. – jfs Apr 08 '15 at 11:01
  • 3
    Since Python 3.6 `astimezone` can be called on naive instances and it assumes system timezone. – Mitar Apr 17 '18 at 12:09
  • 2
    @Mitar: yes. It is unfortunate. – jfs Apr 17 '18 at 17:19
  • Why is this **not** a method of `datetime.datetime` and `datetime.time`? why?! – pablete Jun 17 '21 at 23:44
  • 1
    @pablete: It is a valid question. If you feel strongly about it, you could submit a patch such as: `def is_naive(self): return self.tzinfo is None or self.tzinfo.utcoffset(self) is None` https://devguide.python.org/ – jfs Jun 18 '21 at 19:53
  • is there a discussion about that? that you are aware of? I would like to understand why such a method is not implemented in a battle tested module as datetime – pablete Jun 19 '21 at 23:02
  • When calling `utcoffset` from a `datetime` instance, no parameters are passed, so your example should rather be: `d.tzinfo is not None and d.tzinfo.utcoffset() is not None`, same for the naive example. – ryanjdillon Aug 09 '21 at 12:17
  • 1
    @ryanjdillon: The [`utcoffset` method on `tzinfo`](https://docs.python.org/3/library/datetime.html#datetime.tzinfo.utcoffset) (as opposed to on a `datetime`) requires a datetime argument and so the code in the answer is correct. – JKillian Aug 11 '21 at 17:19
  • I missed that. Thanks @JKillian! – ryanjdillon Aug 12 '21 at 10:44
26

if you want to check if a datetime object 'd' is localized, check the d.tzinfo, if it is None, no localization.

Cédric Julien
  • 78,516
  • 15
  • 127
  • 132
  • But what if it has a tzinfo object that is not from pytz? – chiborg Apr 27 '11 at 10:00
  • 10
    note: it is not enough to check if `d.tzinfo is not None`. Also, [`d.tzinfo.utcoffset(d)` should not be `None` for `d` to be called an aware datetime object](http://stackoverflow.com/a/27596917/4279). – jfs Dec 22 '14 at 05:22
  • 3
    This answer it is not true, here is the definition of when it is naive or aware , from datetime official docs : "An object of type time or datetime may be naive or aware. A datetime object d is aware if d.tzinfo is not None and d.tzinfo.utcoffset(d) does not return None. If d.tzinfo is None, or if d.tzinfo is not None but d.tzinfo.utcoffset(d) returns None, d is naive. A time object t is aware if t.tzinfo is not None and t.tzinfo.utcoffset(None) does not return None. Otherwise, t is naive." – Gonzalo Jan 05 '19 at 20:11
14

Here is a function wrapping up the top answer.

def tz_aware(dt):
    return dt.tzinfo is not None and dt.tzinfo.utcoffset(dt) is not None
kblst
  • 493
  • 8
  • 12
3

Here's a more complete function to convert or coerce a timestamp obj to utc. If it reaches the exception this means the timestamp is not localized. Since it's good practice to always work in UTC within the code, this function is very useful at the entry level from persistence.

def convert_or_coerce_timestamp_to_utc(timeobj):
        out = timeobj
        try:
            out = timeobj.astimezone(pytz.utc) # aware object can be in any timezone
        except (ValueError,TypeError) as exc: # naive
            out = timeobj.replace(tzinfo=pytz.utc)
        return out

The small addition from the 'try catch' in the answer by J.F. Sebastian is the additional catch condition, without which not all naive cases will be caught by the function.

eiTan LaVi
  • 2,901
  • 24
  • 15
  • Why do you catch `TypeError` as well? I don't see that mentioned in the other answers. – Daenyth Mar 09 '16 at 15:16
  • As I wrote, without the additional catch not all naive cases will be caught. This is simply an addition based on direct experience. I had originally worked with the first catch alone, and some naive timestamps got through :) – eiTan LaVi Mar 13 '16 at 16:00
  • The answer could be improved if you show a specific example of a naive object that throws TypeError here. – Daenyth Mar 14 '16 at 13:46
  • As far it has been already mentioned - Since Python 3.6 astimezone can be called on naive instances and it assumes system timezone. So your implementation is dangerous without specific checks. – Nikolay Prokopyev Jul 02 '21 at 08:11