Python / Mongoengine - Timezone missing when saved to database? - python

I'm having some trouble saving a date object to Mongo using MongoEngine. Here is my code:
print isodate
>>> 2014-07-01T20:00:00.000Z
import pytz
from dateutil import parser
tz = pytz.timezone('Europe/London')
start = parser.parse(isodate).replace(tzinfo=None)
start = tz.localize(start)
print start
>>> 2014-07-01 20:00:00+01:00
Localizing the date seems to work fine, but when saving to Mongo:
f = Fixture(
start=start
)
The following strangeness is happening when I look at the Mongo document created:
{
_id: ObjectId("53b1dfbde20b47102c824a8f"),
start: ISODate("2014-07-01T19:00:00Z")
}
Is there any reason why the time is off by two hours, and the timezone is no longer present?

I feel you misunderstood date time format. Refer to W3C Date and Time Formats:
Times are expressed in UTC (Coordinated Universal Time), with a special UTC designator ("Z").
Times are expressed in local time, together with a time zone offset in hours and minutes. A time zone offset of "+hh:mm" indicates that the date/time uses a local time zone which is "hh" hours and "mm" minutes ahead of UTC. A time zone offset of "-hh:mm" indicates that the date/time uses a local time zone which is "hh" hours and "mm" minutes behind UTC.
"2014-07-01T20:00:00.000Z" should equal to "2014-07-01 21:00:00+01:00". So it went wrong in localizing datetime, not in saving to Mongo.
If you want to convert "....T....Z" to local time, you can try this:
print isodate
>>> 2014-07-01T20:00:00.000Z
import pytz
from dateutil import parser
local_tz = pytz.timezone('Europe/London')
local_time = parser.parse(isodate).astimezone(local_tz)
print local_time
>>> 2014-07-01 21:00:00+01:00
If you need to perform date arithmetic on local times, do one more step (refer: pytz doc):
local_tz.normalize(local_time)
Actually you can directly save "....T....Z" ISODate into Mongo without converting to local time. Since it already contains timezone info, converting is unnecessary.

Related

Convert "PST" to "US/Pacific" for use with Pytz

I'm trying to create HTTP endpoints:
one that returns posts to a user that were created in a given month in the requestor's time zone.
another one that gets the months possible for post*.
Examples
(1) get posts in month of requestor's timezone
(2) get possible months for posts
For example if the user made posts in Sept-November but none in December then Jan onward it wouldn't return December.
But it takes the time zone in "PST" format, because it does a SQL query.
Problems
Unfortunately pytz, the library I'm using for getting all posts from a month, only accepts time zone in the format "US/Pacific".
Questions
What is the format or string representation "US/Pacific" called ?
How can I convert the string formats "PST", "UCT" or "CST" to their respective formats like "US/Pacific", etc. in Python ?
What's the name for this format like "US/Pacific" ?
Is there a sort of dictionary that maps "PST" to "US/Pacific" ?
Time zone terminology
How to define, represent or refer to a time zone? Some terms:
UTC time offset (e.g. "UTC-08:00")
in relation with ISO 8601 date/time format: time zone designator (e.g. "Z" or "-08")
time zone: canonical name
time zone: abbreviation
(Canonical) names
The spelled-out time zone names or (tz names) like "US/Pacific" or "Europe/Paris" are also called canonical names of time zone. They are used as key in the IANA time zone database. In RFC 6557 they are referred to as "time zone names". Wikipedia claims about them:
The primary, preferred zone name.
See also:
ECMA: 6.4Time Zone Names
Abbreviations
The alphabetic string literals like "UTC", "PST" are abbreviations of time zone.
Conversion between time zones
Usually the conversion between time zones is done by modifying the offsets of UTC which are represented in ISO 8601, time zone designators like "-0800" (PST) which is 8 hours subtracted from "+0000" (UTC).
See also:
Daylight saving time and time zone best practices
Converting using pytz timezone
To convert a given date-time from UTC to the target time zone (e.g. "US/Pacific") use astimezone(tz) on the source date-time instance:
import datetime
from pytz import timezone, utc
utc_time = datetime.datetime.utcnow()
pst_tz = timezone('US/Pacific')
pst_time = utc_time.replace(tzinfo=utc).astimezone(pst_tz)
Note:
the time-zone tz is built using pytz's tzinfo API, e.g. with timezone('PST8PDT') for PST or timezone('US/Central') for CST
the .replace() is optional and resets the time zone of given date-time to default UTC.
Surprisingly: The "PST" abbreviation is not found in pytz.all_timezones. Most similar are (evaluated in REPL):
>>> import pytz
>>> pytz.timezone('PST8PDT')
<DstTzInfo 'PST8PDT' PST-1 day, 16:00:00 STD>
>>> pytz.timezone('US/Pacific')
<DstTzInfo 'US/Pacific' LMT-1 day, 16:07:00 STD>
>>> pytz.timezone('US/Central')
<DstTzInfo 'US/Central' LMT-1 day, 18:09:00 STD>
See also:
pytz - Converting UTC and timezone to local time
Is there a list of Pytz Timezones?
Converting using zoneinfo (since 3.9)
Adjusted from MrFuppes answer to "How do I use timezones with a datetime object in python?":
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
utc_time = datetime(2012,11,10,9,0,0, tzinfo=timezone.utc)
cst_tz = ZoneInfo("US/Central")
cst_time = utc_time.astimezone(cst_tz)
# safely use `replace` to get the same wall time in a different tz:
pst_time = cst_time.replace(tzinfo=ZoneInfo("US/Pacific"))
print(utc_time.isoformat())
print(cst_time.isoformat())
print(pst_time.isoformat())
(above code is not tested!)
See also:
new Python module zoneinfo — IANA time zone support
Paul Ganssle (2021): Stop using utcnow and utcfromtimestamp, blog article from the maintainer of python-dateutil
import pytz
from datetime import datetime # timezone
print('The supported tz:', pytz.all_timezones, '\n')
# Show date-time for different timezone/country
# current date and time of NY
datetime_NY = datetime.now(pytz.timezone('America/New_York'))
print("NY:", datetime_NY.strftime("%m/%d/%Y, %H:%M:%S"))
# NY: 07/28/2021, 05:49:41
# Timezone Conversion
# Any timezone to UTC
NY_to_utc = datetime_NY.astimezone(pytz.utc)
print("NY_to_utc: ", NY_to_utc.strftime("%m/%d/%Y, %H:%M:%S"))
# NY_to_utc: 07/28/2021, 09:49:41
Naive and Aware datetime Refer this article for dealing with timezone
Show date-time for different timezone/country
Timezone Conversion
Timezone unaware/naive to Timezone aware
Issue with replace

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

How do you convert local time (PDT) to PST using python

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'

Python - convert naive datetime to UTC

I have date that I get in specific timezone time, but system deals with it as UTC and later it converts it back in that timezone, messing time.
For example like this:
I get this time: 2014-05-05 10:50:30. its datetime object. It has no timezone info, but I can get timezone info from user that uses that time. The thing is this time is showed as 'Europe/Vilnius' time, but system deals with it as UTC and when it outputs time to user it adds +3 hours showing wrong time. It does not matter if I change timezone to users timezone on that datetime object, it still outputs with +3 hours.
For example (snippet of code):
from datetime import datetime
import pytz
create_date = datetime.strptime(stage_log.create_date, "%Y-%m-%d %H:%M:%S")
tz = pytz.timezone(self.user_id.tz)
create_date = create_date.replace(tzinfo=pytz.utc)
This does not do anything and I still get wrong time.
Is there a way to move time to be correct UTC time(so then system correctly convert to users timezone) like this:
2014-05-05 10:50:30 -> convert to UTC. If timezone is 'Europe/Vilnius', it should convert that time to 2014-05-05 07:50:30. Then when system automatically does conversions it would correctly display 2014-05-05 10:50:30, because thats the time it should display.
Also if there is a way to just get number of hours that given timezone differs from UTC, then I could just do as simple as that:
create_date.replace(hour=create_date.hour-timezone_difference)
While this question does not specifically reference odoo, hopefully the following may help others:
Odoo - convert datetime to UTC:
(note: in this example self.start_date is a fields.Date)
start_date = fields.Datetime.to_string(pytz.timezone(self.env.context['tz']).localize(fields.Datetime.from_string(self.start_date), is_dst=None).astimezone(pytz.utc))
similar but with +24 hrs
end_date = fields.Datetime.to_string(pytz.timezone(self.env.context['tz']).localize(fields.Datetime.from_string(self.end_date), is_dst=None).astimezone(pytz.utc) + timedelta(hours=24))
This was used because the passed values (self.start_date) were field.Date and therefor did not get affected by timezones, while the target stored fields were fields.Datetime and therefor stored in UTC.
start_date/end_date which are now in UTC can then be used in a self.env[''].search([])

Categories