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.
Related
I have the following code:
import datetime
dt = 1546955400
print(datetime.datetime.fromtimestamp(dt))
When I run this code on my local machine, I get the correct (expected) time which is
2019-01-08 15:50:00.
However I tried running this exact same code on a VM and the result was
2019-01-08 13:50:00 (two hours earlier). Why is this is happening and how can I fix it so that I always get the first one regardless of where the code is running?
datetime.datetime.fromtimestamp() returns local time. From the documentation:
Return the local date and time corresponding to the POSIX timestamp, such as is returned by time.time(). If optional argument tz is None or not specified, the timestamp is converted to the platform’s local date and time, and the returned datetime object is naive.
The timestamp value is an offset in seconds from the UNIX epoch value, midnight 1 January 1970, in the UTC timezone. The local time is a system-wide configured offset from UTC, the local timezone.
If your VM is producing unexpected results, you need to configure the timezone of the OS.
Alternatively, ignore timezones and only deal with time in the UTC timezone. For timestamps, that means using the datetime.datetime.utcfromtimestamp() function.
Your specific timestamp is 13:50 UTC:
>>> dt = 1546955400
>>> from datetime import datetime
>>> datetime.utcfromtimestamp(dt)
datetime.datetime(2019, 1, 8, 13, 50)
>>> print(_)
2019-01-08 13:50:00
so your VM is either set to the UTC or the GMT timezone (the latter is currently at UTC+0, until the switch to the UK daylight saving timezone BST). Your local system is in a UTC+2 timezone, given your stated location from your profile that'd be EEE, Easter European Time.
Another option is to create a timezone-aware timestamp by passing in a tz argument. If you have a specific UTC offset, just create a datetime.timezone() instance for that offset:
utcplus2 = datetime.timezone(datetime.timedelta(hours=2))
datetime.datetime.fromtimestamp(dt, utcplus2)
However, it is usually better to store and operate on UTC datetime instances everywhere, and only convert to specific timezones when displaying information to users. This simplifies datetime handling as it lets you avoid a number of timezone corner cases and problems, such as mixing datetime information from different timezones and timezones with a summer and winter time distinction.
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.
I have a csv file with the datetime in unixtimestamp format with milliseconds and timezone information in milliseconds as well. I want to convert this into a more usable datetime format for further processing.
For example, the time is 1437323953822 and timezone is -14400000.
I can convert the timestamp into a datetime by using
datetime.datetime.fromtimestamp(1437323953822/1000)
But how do I now incorporate the timezone which is -4 UTC time from what I know.
(-14400000 / 1000 / 60 / 60) = -4
How do I use this timezone to get the actual time?
fromtimestamp can also take another parameter for the timezone, a subclass of tzinfo:
classmethod datetime.fromtimestamp(timestamp[, tz])
Return the local date and time corresponding to the POSIX timestamp,
such as is returned by time.time(). If optional argument tz is
None or not specified, the timestamp is converted to the platform’s
local date and time, and the returned datetime object is naive.
Else tz must be an instance of a class tzinfo subclass, and the
timestamp is converted to tz‘s time zone. In this case the result is
equivalent to
tz.fromutc(datetime.utcfromtimestamp(timestamp).replace(tzinfo=tz)).
fromtimestamp() already returns your local time i.e., you don't need to attach the utc offset if fromtimestamp() determines it correctly automatically:
#!/usr/bin/env python
from datetime import datetime
local_time = datetime.fromtimestamp(1437323953822 * 1e-3)
# -> datetime.datetime(2015, 7, 19, 12, 39, 13, 822000)
fromtimestamp() may fail in some cases e.g., if the local timezone had a different utc offset in the past and fromtimestamp() does not use a historical timezone database on a given platform (notably, Windows). In that case, construct the local time explicitly from utc time and the given utc offset:
#!/usr/bin/env python
from datetime import datetime, timedelta
utc_time = datetime(1970, 1, 1) + timedelta(milliseconds=1437323953822)
utc_offset = timedelta(milliseconds=-14400000)
local_time = utc_time + utc_offset
# -> datetime.datetime(2015, 7, 19, 12, 39, 13, 822000)
Python always expects POSIX Epoch and therefore it is ok to hardcode it. The explicit formula may be more precise (no rounding error) and it may accept a wider range of input timestamps (fromtimestamp() range depends on platform and may be narrower than the corresponding datetime range).
This question is old but I want to give a slightly more comprehensive answer.
About the unix timestamp:
The timestamp is the number of seconds (or milliseconds) elapsed since an absolute point in time, midnight of Jan 1 1970 in UTC time. (UTC is Greenwich Mean Time without Daylight Savings time adjustments.)
fromtimestamp does convert the unix timestamp to your platform's time. If you are working across different platforms, it is important to set the platform's timezone correctly. If you want it to be in UTC instead, then utcfromtimestamp should be used instead.
To answer OP's question directly, the following code will create a timezone based on the offset.
from datetime import datetime, timezone, timedelta
ts = int('1604750712')
tz = timezone(-timedelta(hours=4))
print(datetime.fromtimestamp(ts, tz).strftime('%Y-%m-%d %H:%M:%S'))
timezone object is an concrete class of tzinfo, I have initiated it with a negative offset of 4 hours from UTC.
from datetime import datetime
import pytz # pip install pytz
tz = pytz.timezone('Asia/Dubai')
ts = int('1604750712')
print(datetime.fromtimestamp(ts,tz).strftime('%d-%m-%Y %H:%M:%S'))
Usually what I do if I want to convert time zone is the following
# local interpreted as pst to utc
utc = pytz.utc
pst = pytz.timezone('America/Los_Angeles')
start_time_str = time.strftime('%Y-%m-%d %H:%M:%S', \
time.localtime(start_time))
start_time_datetime = \
datetime.datetime.strptime(start_time_str, "%Y-%m-%d %H:%M:%S")
start_time_datetime = \
start_time_datetime.replace(tzinfo=pst).astimezone(utc)
Now I want to do similar stuff such that I want localtime convert to pst
localtime = datetime.datetime.fromtimestamp(time.mktime(
time.localtime()))
I am not exactly sure how would you achieve this
Any help would be appreciated
Two things:
The "America/Los_Angeles" identifier represents the entire Pacific time zone, including both PST and PDT. It will use the either -8 or -7 as the time zone offset depending on the specific date and time it is used on.
Calling replace(tzinfo=...) is a mistake. Use localize instead. See the pytz documentation. This is discussed in the introduction and in the very first example.
Your code contains unnecessary (calling strptime after strftime) or just wrong (.replace()) conversions.
To create an aware datetime object given "seconds since epoch" start_time (a value returned by time.time()):
#!/usr/bin/env python
from datetime import datetime
import pytz # $ pip install pytz
tz = pytz.timezone('America/Los_Angeles')
start_time_datetime = datetime.fromtimestamp(start_time, tz)
As #Matt Johnson mentions, 'America/Los_Angeles' timezone id produces time with PST or PDT tzname depending on the date. No conversion is necessary.
The last code example in your question has both unnecessary conversions and may fail in some cases. If the intent is to get the current time in the local timezone as an aware datetime object then you could use tzlocal module:
#!/usr/bin/env python
from datetime import datetime
from tzlocal import get_localzone # $ pip install tzlocal
local_timezone = get_localzone() # pytz timezone corresponding to the local time
current_localtime = datetime.now(local_timezone)
First, as a note, local time is either Standard Time or Daylight Savings Time -- it cannot be both simultaneously. I believe what you are really getting at is asking whether there is a way to tell if a time is either a Daylight Savings Time or not. In other words, given a specific day, is a particular time in Daylight Savings Time? Another common use case is converting tabular data pegged at a specific time offset to the current correct time.
Okay, I can think of three ways to do this.
(1) You can convert the datetime into a time object and look at the .tm_isdst property, similar to this answer. You can do a test on it to see if that property equals one and then subtract a timedelta of one hour.
>>> time.localtime()
time.struct_time(tm_year=2019, tm_mon=11, tm_mday=16, tm_hour=12,
tm_min=36, tm_sec=32, tm_wday=5, tm_yday=320, tm_isdst=0)
>>> time.localtime().tm_isdst
0
(2) Second way is to crawl month by month over noon on the fifteenth of each month and check if the offset varies. (Note that some areas do not implement DST.) The greater value indicates a skip ahead for Daylight Savings Time. (For instance, PDT is UTC -7 while PST is UTC -8.) From those values you can calculate the time difference. Then you can test your time by checking the offset to see if it is in Standard Time or Daylight Savings Time. Offest is checked this way:
>>> my_special_pdt_datetime.strftime('%z')
'-0700'
(3) And, maybe the easiest, is to calculate the existing offset by getting offset = datetime.datetime.utcnow() - datetime.datetime.now() and then comparing that to the known offset from pytz:
>>> pytz.timezone('US/Pacific').localize(datetime.datetime(2019,1,1)).strftime('%z')
'-0800'
I have a datetime in utc time zone, for example:
utc_time = datetime.datetime.utcnow()
And a pytz timezone object:
tz = timezone('America/St_Johns')
What is the proper way to convert utc_time to the given timezone?
I think I got it:
pytz.utc.localize(utc_time, is_dst=None).astimezone(tz)
This line first converts the naive (time zone unaware) utc_time datetime object to a datetime object that contains a timezone (UTC). Then it uses the astimezone function to adjust the time according to the requested time zone.
It's the exact purpose of fromutc function:
tz.fromutc(utc_time)
(astimezone function calls fromutc under the hood, but tries to convert to UTC first, which is unneeded in your case)
I agree with Tzach's answer. Just wanted to include that the is_dst parameter is not required:
pytz.utc.localize(datetime.utcnow()).astimezone(tz)
That code converts the current UTC time to a timezone aware current datetime.
Whereas the code below converts the current UTC time to a timezone aware datetime which is not necessarily current. The timezone is just appended into the UTC time value.
tz.localize(datetime.utcnow())
May I recommend to use arrow? If I understood the question:
>>> import arrow
>>> utc = arrow.utcnow()
>>> utc
<Arrow [2014-08-12T13:01:28.071624+00:00]>
>>> local = utc.to("America/St_Johns")
>>> local
<Arrow [2014-08-12T10:31:28.071624-02:30]>
You can also use
tz.fromutc(utc_time)
Another very easy way:
Because utcnow method returns a naive object, so you have to convert the naive object into aware object. Using replace method you can convert a naive object into aware object. Then you can use the astimezone method to create new datetime object in a different time zone.
from datetime import datetime
import pytz
utc_time = datetime.utcnow()
tz = pytz.timezone('America/St_Johns')
utc_time =utc_time.replace(tzinfo=pytz.UTC) #replace method
st_john_time=utc_time.astimezone(tz) #astimezone method
print(st_john_time)
You can also use the sample below, I use it for similar task
tz = pytz.timezone('America/St_Johns')
time_difference=tz.utcoffset(utc_time).total_seconds() #time difference between UTC and local timezones in 5:30:00 format
utc_time = date + timedelta(0,time_difference)
It works fast and you don't need to import additional libraries.