Strange behavior of datetime.replace and pytz [duplicate] - python

This question already has answers here:
How to make a timezone aware datetime object
(15 answers)
Closed 6 years ago.
I've got a datetime which has no timezone information. I'm now getting the timezone info and would like to add the timezone into the existed datetime instance, how can I do?
d = datetime.datetime.now()
tz = pytz.timezone('Asia/Taipei')
How to add the timezone info tz into datetime a

Use tz.localize(d) to localize the instance. From the documentation:
The first is to use the localize() method provided by the pytz library. This is used to localize a naive datetime (datetime with no timezone information):
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
>>> print(loc_dt.strftime(fmt))
2002-10-27 06:00:00 EST-0500
If you don't use tz.localize(), but use datetime.replace(), chances are that a historical offset is used instead; tz.localize() will pick the right offset in effect for the given date. The US Eastern timezone DST start and end dates have changed over time, for example.
When you try to localize a datetime value that is ambiguous because it straddles the transition period from summer to winter time or vice-versa, the timezone will be consulted to see if the resulting datetime object should have .dst() return True or False. You can override the default for the timezone with the is_dst keyword argument for .localize():
dt = tz.localize(naive, is_dst=True)
or even switch off the choice altogether by setting is_dst=None. In that case, or in the rare cases there is no default set for a timezone, an ambiguous datetime value would lead to a AmbiguousTimeError exception being raised. The is_dst flag is only consulted for datetime values that are ambiguous and is ignored otherwise.
To go back the other way, turn a timezone-aware object back to a naive object, use .replace(tzinfo=None):
naivedt = awaredt.replace(tzinfo=None)

If you know that your original datetime was "measured" in the time zone you are trying to add to it, you could (but probably shouldn't) use replace rather than localize.
# d = datetime.datetime.now()
# tz = pytz.timezone('Asia/Taipei')
d = d.replace(tzinfo=tz)
I can imagine 2 times when this might make sense (the second one happened to me):
Your server locale is set to the incorrect time zone and you are trying to correct a datetime instance by making it aware of this incorrect timezone (and presumably later localizing it to the "correct" time zone so the values of now() match up to other times you are comparing it to (your watch, perhaps)
You want to "tag" a time instance (NOT a datetime) with a time zone (tzinfo) attribute so that attribute can be used later to form a full datetime instance.

Related

Unix to Timezone Conversion, Offset element

I converted a UNIX timestamp to a human format (sorry I do not know the exact name of it) in a specific timezone (Africa/Algeria) and it evaluated to this: 2020-06-05 19:45:21+01:00. I looked into the datetime module documentation and from what I understood the +01:00, it is the +/-HH:MM offset from the UTC.
What I do not understand is why it is returned with the datetime object given it is already converted to the indicated timezone?
Could someone explain it to me?
Thanks.
POSIX timestamps (Unix time) represent time in seconds since the epoch, 1970-01-01 UTC. No time zone issues involved here. datetime objects on the other hand can be naive (not contain any time zone information) or time zone aware. What you have is a time zone aware datetime object - its string representation prints out as "2020-06-05 19:45:21+01:00". If it had been naive, it would only print "2020-06-05 19:45:21". The repr should also show you a specific time zone, e.g.
print(repr(dt_obj))
>>> datetime.datetime(2020, 6, 5, 19, 45, 21, tzinfo=tzfile('Africa/Algiers'))
The important point is that 2020-06-05 19:45:21+01:00 can be converted back to POSIX timestamp without ambiguity:
from datetime import datetime
datetime.fromisoformat("2020-06-05 19:45:21+01:00").timestamp()
# 1591382721.0
If it wasn't for the +01:00 (no tzinfo), Python would assume that the datetime object belongs in local time, i.e. OS setting; meaning that machines in different time zones would get a different timestamp. I'm on UTC+2, so I would get:
datetime.fromisoformat("2020-06-05 19:45:21").timestamp()
# 1591379121.0
which gives the same timestamp as
datetime.fromisoformat("2020-06-05 19:45:21+02:00").timestamp()
# 1591379121.0
The difference is that with the "+02:00" (tzinfo defined), it is obvious what is happening.

python: utcfromtimestamp vs fromtimestamp, when the timestamp is based on utcnow()

Pretty sure it's an easy one but I don't get it.
My local TZ is currently GMT+3, and when I take timestamp from datetime.utcnow().timestamp() it is indeed giving me 3 hours less than datetime.now().timestamp()
During another process in my flow, I take that utc timestamp and need to turn it into datetime.
When I'm doing fromtimestamp I get the right utc hour, but when I'm using utcfromtimestamp I get another 3 hours offset.
The documentation, though, asks me to use fromtimestamp for local timezone, and utcfromtimestamp for utc usages.
What am I missing ? is the initial assumption for both funcs is that the timestamp is given in local timezone ?
Thank you :)
The key thing to notice when working with datetime objects and their POSIX timestamps (Unix time) at the same time is that naive datetime objects (the ones without time zone information) are assumed by Python to refer to local time (OS setting). In contrast, a POSIX timestamp (should) always refer to seconds since the epoch UTC. You can unambiguously obtain that e.g. from time.time(). In your example, not-so-obvious things happen:
datetime.now().timestamp() - now() gives you a naive datetime object that resembles local time. If you call for the timestamp(), Python converts the datetime to UTC and calculates the timestamp for that.
datetime.utcnow().timestamp() - utcnow() gives you a naive datetime object that resembles UTC. However, if you call timestamp(), Python assumes (since naive) that the datetime is local time - and converts to UTC again before calculating the timestamp! The resulting timestamp is therefore off from UTC by twice your local time's UTC offset.
A code example. Let's make some timestamps. Note that I'm on UTC+2 (CEST), so offset is -7200 s.
import time
from datetime import datetime, timezone
ts_ref = time.time() # reference POSIX timestamp
ts_utcnow = datetime.utcnow().timestamp() # dt obj UTC but naive - so also assumed local
ts_now = datetime.now().timestamp() # dt obj naive, assumed local
ts_loc_utc = datetime.now(tz=timezone.utc).timestamp() # dt obj localized to UTC
print(int(ts_utcnow - ts_ref))
# -7200 # -> ts_utcnow doesn't refer to UTC!
print(int(ts_now - ts_ref))
# 0 # -> correct
print(int(ts_loc_utc - ts_ref))
# 0 # -> correct
I hope this clarifies that if you call datetime.utcfromtimestamp(ts_utcnow), you get double the local time's UTC offset. Python assumes (which I think is pretty sane) that the timestamp refers to UTC - which in fact, it does not.
My suggestion would be to use timezone-aware datetime objects; like datetime.now(tz=timezone.utc). If you're working with time zones, the dateutil library or Python 3.9's zoneinfo module are very helpful. And if you want to dig deep, have a look at the datetime src code.

str to time object in python 3

Given a pair of str objects representing an ISO 8601 time and time zone:
time_str = '09:30'
time_zone_str = 'America/New_York'
How can these 2 strings be parsed into a time (not datetime) object?
Note: It's obviously possible to split the time_str by ':' and use the time constructor but then the parsing would be a little tricky to count the number of elements in the resulting list to know the resolution (minute, second, microsecond) of the str. This is because ISO 8601 allows for different representations:
time_str_short = '09:30'
time_str_long = '09:30:00'
Thank you in advance for your consideration and response.
The answer to "can I do this?" (with a timezone) is both yes and no. Firstly let's convert the string to a time object. As one commenter mentioned, you can do this in python 3.7 with the fromisoformat method:
from datetime import time
time.fromisoformat("09:30")
If you are not using 3.7, you can do this by creating a datetime and then converting to a time object:
from datetime import datetime, time
as_time = datetime.datetime.strptime("09:00", "%H:%M").time()
Now to deal with the timezone. As the timezone is a name, we can use the very convenient pytz module to convert it to a tzinfo object:
pytz.timezone('America/New_York')
At this point you're probably tempted to just pass it to the time constructor as the tzinfo argument, but unfortunately that does not work with pytz:
Unfortunately using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones.
~ http://pytz.sourceforge.net/
So we will have to use the localize method of the newly created tzinfo object. But unfortunately we will still not be able to successfully localize the time object with this timezone. The reason for this is that pytz needs to know the date in order to determine if this timezone is in daylight savings time or not. As we have not provided the date, achieving this is quite impossible and you will get odd results like:
>>> pytz.timezone('America/New_York').localize(as_dt).isoformat()
'1900-01-01T09:00:00-04:56'
Note the -04:56 offset, which is gibberish. There are a few options for getting to what you ultimately want.
One option is to assume the time is a time today:
as_time = datetime.datetime.strptime("09:00", "%H:%M").time()
tz = pytz.timezone('America/New_York')
local_time = tz.localize(datetime.datetime.now().replace(hour=as_time.hour, minute=as_time.minute))
The other option is to use naive timezone offsets rather than timezone names:
from datetime import timezone, timedelta
naive_tz = timezone(timedelta(hours=5))
datetime.time(9, 30).replace(tz_info=naive_tz)
But I would not recommend this method as it's quite brittle and would require some intermediate steps to derive from the TZ location name that are non-trivial.
A timezone without a date is meaningless, so no, you can't use both to produce a time object. While the standard library time object does support having a tzinfo attribute, the 'timezone' object is not really a timezone, but merely a time offset.
A timezone is more than just an offset from UTC. Timezone offsets are date-dependent, and because such details as the Daylight Savings winter / summer time distinction is partly the result of political decisions, what dates the timezone offset changes is also dependent on the year.
To be explicit, America/New_York is a timezone, not a time offset. The exact offset from UTC depends on the date; it'll be minus 4 hours in summer, 5 hours in winter!
So for a timezone such as America/New_York, you need to pick a date too. If you don't care about the date, pick a fixed date so your offset is at least consistent. If you are converting a lot of time stamps, store the timezone offset once as a timedelta(), then use that timedelta to shift time() objects to the right offset.
To parse just a timestring, pretend there is a date attached by using the datetime.strptime() method, then extract the time object:
from datetime import datetime
try:
timeobject = datetime.strptime(time_str, '%H:%M').time()
except ValueError:
# input includes seconds, perhaps
timeobject = datetime.strptime(time_str, '%H:%M:%S').time()
To update the time given a timezone, get a timezone database that supports your timezone string first; the pytz library is regularly updated.
from pytz import timezone
timezone = pytz.timezone(time_zone_str)
How you use it depends on what you are trying to do. If the input time is not in UTC, you can simply attach the timezone to a datetime() object with the datetime.combine()method, after which you can move it to the UTC timezone:
dt_in_timezone = datetime.combine(datetime.now(), timeobject, timezone)
utc_timeobject = dt_in_timezone.astimezone(pytz.UTC).time()
This assumes that 'today' is good enough to determine the correct offset.
If your time is a UTC timestamp, combine it with the UTC timezone, then use the pytz timezone; effectively the reverse:
dt_in_utc = datetime.combine(datetime.now(), timeobject, pytz.UTC)
timeobject_in_timezone = dt_in_timezone.astimezone(timezone).time()
To store just the offset for bulk application, pass in a reference date to the timezone.utcoffset() method:
utc_offset = timezone.utcoffset(datetime.now())
after which you can add this to any datetime object as needed to move from UTC to local time, or subtract it to go from local to UTC. Note that I said datetime, as time objects also don't support timedelta arithmetic; a timedelta can be larger than the number of seconds left in the day or the number of seconds since midnight, after all, so adding or subtracting could shift days as well as the time:
# new time after shifting
(datetime.combine(datetime.now(), timeobject) + utc_offset).time()
For completion sake, you can't pass in a pytz timezone to a time object; it just doesn't have any effect on the time. The timezone object returns None for the UTC offset in that case, because it can't give any meaningful answer without a date:
>>> from datetime import time
>>> from pytz import timezone
>>> tz = timezone('America/New_York')
>>> time_with_zone = time(12, 34, tzinfo=tz)
>>> time_with_zone
datetime.time(12, 34, tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>)
>>> time_with_zone.utcoffset()
>>> time_with_zone.utcoffset() is None
True
>>> tz.utcoffset(None) is None # what time_with_zone.utcoffset() does under the hood
None
So for all intents an purposes, time_with_zone is just another naive time object as the tzinfo object attached doesn't actually have any effect.
Moreover, because there is no date to determine the correct timezone information, pytz selects the earliest known 'New York' timezone, and that's not exactly a recent one; look closely at that tzinfo representation:
tzinfo=<DstTzInfo 'America/New_York' LMT-1 day, 19:04:00 STD>
^^^^^^^^^^^^^^^^^^^^^^^
That's the timezone introduced in 1883 by the railroads; the 'modern' EST timezone was not introduced until the 20th century. This is why timezone objects are usually passed in a date when determining the offset:
>>> tz.utcoffset(datetime(1883, 6, 28))
datetime.timedelta(-1, 68640)
>>> tz.utcoffset(datetime(1918, 6, 28))
datetime.timedelta(-1, 72000)
Hope it works for you,
import datetime
# Hello World program in Python
print "Hello World!\n"
time_str = '09:30'
time_zone_str = 'America/New_York'
s = "I am looking for a course in Paris!"
print(s)
print(datetime.datetime.strptime(time_str, '%H:%M').time())
print(datetime.time(3, 55))
Thanks

Assign timezone to a Python datetime [duplicate]

This question already has answers here:
pytz localize vs datetime replace
(4 answers)
Closed 8 years ago.
Assume that I have a timezone-less datetime object:
import datetime
import pytz
fmt = "%Y-%m-%d %H:%M:%S %Z%z"
dtUnaware = datetime.datetime(1979,2,20,6)
print(dtUnaware.strftime(fmt))
This yields:
1979-02-20 06:00:00
So far, so good. Now, I want to assign a timezone to this object. It seems like I could use either datetime.replace or pytz.localize.
First:
dtAware1 = dtUnaware.replace(tzinfo=pytz.timezone('Asia/Jerusalem'))
print(dtAware1.strftime(fmt))
returns: 1979-02-20 06:00:00 LMT+0221. Secondly:
dtAware2 = pytz.timezone('Asia/Jerusalem').localize(dtUnaware, is_dst=None)
print(dtAware2.strftime(fmt))
returns 1979-02-20 06:00:00 IST+0200.
What's wrong with the first method? It seems to assign a wrong timezone. Am I doing something wrong?
From Pytz documentation : This library differs from the documented Python API for
tzinfo implementations; if you want to create local wallclock
times you need to use the localize() method documented in this
document... Unfortunately these
issues cannot be resolved without modifying the Python datetime
implementation (see PEP-431)
My reading of that is that a pytz timezone is not exactly the same thing as a standard timezone. If you had a genuine timezone, first method should be good, but you have not.
There is a flaw in the datetime API: when you assign a timezone to it, the timezone is not given the opportunity to know the date and time it should be configured for. The historical database that pytz uses goes back a long way, often to periods when the timezone name or offset were different than the ones in use today. Without being given the chance to adjust to a specific date period, the details of the timezone may be wrong.
Using localize is the way around this problem, since both the date and timezone are available within the function at the same time.

How to add timezone into a naive datetime instance in python [duplicate]

This question already has answers here:
How to make a timezone aware datetime object
(15 answers)
Closed 6 years ago.
I've got a datetime which has no timezone information. I'm now getting the timezone info and would like to add the timezone into the existed datetime instance, how can I do?
d = datetime.datetime.now()
tz = pytz.timezone('Asia/Taipei')
How to add the timezone info tz into datetime a
Use tz.localize(d) to localize the instance. From the documentation:
The first is to use the localize() method provided by the pytz library. This is used to localize a naive datetime (datetime with no timezone information):
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
>>> print(loc_dt.strftime(fmt))
2002-10-27 06:00:00 EST-0500
If you don't use tz.localize(), but use datetime.replace(), chances are that a historical offset is used instead; tz.localize() will pick the right offset in effect for the given date. The US Eastern timezone DST start and end dates have changed over time, for example.
When you try to localize a datetime value that is ambiguous because it straddles the transition period from summer to winter time or vice-versa, the timezone will be consulted to see if the resulting datetime object should have .dst() return True or False. You can override the default for the timezone with the is_dst keyword argument for .localize():
dt = tz.localize(naive, is_dst=True)
or even switch off the choice altogether by setting is_dst=None. In that case, or in the rare cases there is no default set for a timezone, an ambiguous datetime value would lead to a AmbiguousTimeError exception being raised. The is_dst flag is only consulted for datetime values that are ambiguous and is ignored otherwise.
To go back the other way, turn a timezone-aware object back to a naive object, use .replace(tzinfo=None):
naivedt = awaredt.replace(tzinfo=None)
If you know that your original datetime was "measured" in the time zone you are trying to add to it, you could (but probably shouldn't) use replace rather than localize.
# d = datetime.datetime.now()
# tz = pytz.timezone('Asia/Taipei')
d = d.replace(tzinfo=tz)
I can imagine 2 times when this might make sense (the second one happened to me):
Your server locale is set to the incorrect time zone and you are trying to correct a datetime instance by making it aware of this incorrect timezone (and presumably later localizing it to the "correct" time zone so the values of now() match up to other times you are comparing it to (your watch, perhaps)
You want to "tag" a time instance (NOT a datetime) with a time zone (tzinfo) attribute so that attribute can be used later to form a full datetime instance.

Categories