Python timezone '%z' directive for datetime.strptime() not available - python

Using '%z' pattern of datetime.strptime()
I have a string text that represent a date and I'm perfectly able to parse it and transform it into a clean datetime object:
date = "[24/Aug/2014:17:57:26"
dt = datetime.strptime(date, "[%d/%b/%Y:%H:%M:%S")
Except that I can't catch the entire date string with the timezone using the %z pattern as specified here
date_tz = 24/Aug/2014:17:57:26 +0200
dt = datetime.strptime(date, "[%d/%b/%Y:%H:%M:%S %z]")
>>> ValueError: 'z' is a bad directive in format '[%d/%b/%Y:%H:%M:%S %z]'
Because as this bug report says
strftime() is implemented per platform
I precise that there is no such a problem with the naive tzinfo directive '%Z'
Workaround : Casting tzinfo string into tzinfo object
I can perfectly make the following workaround by transforming the GST time format string into a tzinfo object [as suggested here][4] using dateutil module
and then insert tzinfo into datetime object
Question: Make %z available for my plateform?
But as I will obviously need %z pattern for further project I would like to find a solution to avoid this workaround and using external module for this simple task.
Can you suggest me some reading on it? I supposed that newer version of python (I'm on 2.7) can handle it but I'd rather not changing my version now for this little but crucial detail.
[EDIT]
Well, seeing comments make me reformulated my question how to parse Email time zone indicator using strptime() without being aware of locale time?

strptime() is implemented in pure Python. Unlike strftime(); it [which directives are supported] doesn't depend on platform. %z is supported since Python 3.2:
>>> from datetime import datetime
>>> datetime.strptime('24/Aug/2014:17:57:26 +0200', '%d/%b/%Y:%H:%M:%S %z')
datetime.datetime(2014, 8, 24, 17, 57, 26, tzinfo=datetime.timezone(datetime.timedelta(0, 7200)))
how to parse Email time zone indicator using strptime() without being aware of locale time?
There is no concrete timezone implementation in Python 2.7. You could easily implement the UTC offset parsing, see How to parse dates with -0400 timezone string in python?

In continue to #j-f-sebastians 's answer, here is a fix for python 2.7
Instead of using:
datetime.strptime(t,'%Y-%m-%dT%H:%M %z')
use the timedelta to account for the timezone, like this:
from datetime import datetime,timedelta
def dt_parse(t):
ret = datetime.strptime(t[0:16],'%Y-%m-%dT%H:%M')
if t[17]=='+':
ret-=timedelta(hours=int(t[18:20]),minutes=int(t[20:]))
elif t[17]=='-':
ret+=timedelta(hours=int(t[18:20]),minutes=int(t[20:]))
return ret
print(dt_parse('2017-01-12T14:12 -0530'))

The Answer of Uri is great, saved my life, but when you have
USE_TZ = True you need to be careful with the time, for avoid the warning "RuntimeWarning: DateTimeField" is better if you add the utc to the return.
import pytz
from datetime import datetime, timedelta
def dt_parse(t):
ret = datetime.strptime(t[0:19],'%Y-%m-%dT%H:%M:%S')
if t[23]=='+':
ret-=timedelta(hours=int(t[24:26]), minutes=int(t[27:]))
elif t[23]=='-':
ret+=timedelta(hours=int(t[24:26]), minutes=int(t[27:]))
return ret.replace(tzinfo=pytz.UTC)

Related

Convert an RFC 3339 nano time to Python datetime

Is there an easy way to convert an RFC 3339 nano time into a regular Python timestamp?
For example, time = '2022-07-14T12:01:25.225089838+08:00',
I found a way using datetime
from datetime import datetime
time = '2022-07-14T12:01:25.225089+08:00'
date = datetime.fromisoformat(time) # good
time = '2022-07-14T12:01:25.225089838+08:00'
date = datetime.fromisoformat(time) # error
It works with string like '2022-07-14T12:01:25.225089+08:00', but it doesn't work with the time above.
There are a few ways to do it.
Depends on what is the input format and how you define an easy way.
There are actually many post asking similar issues you have.
I'll post a few at the end for your reference if you are interested and please check next time before posting.
The main issue of datetime object is that it only holds 6 digit after second.
You will need a different data structure to save it if you want to preserve all of the digits.
If you are ok with cutting off at 6 digit, FObersteiner's answer is perfect.
Another methodology is vanilla datetime string parsing
from datetime import datetime
date = '2022-07-14T12:01:25.225089838+08:00'.removesuffix('+08:00')
x = datetime.strptime( date[:-3], '%Y-%m-%dT%H:%M:%S.%f')
If you would like to preserve all the digits. You may want to create your own class extending from the datetime class or create some function for it.
Convert an RFC 3339 time to a standard Python timestamp
Parsing datetime strings containing nanoseconds
from datetime.fromisoformat docs:
Caution: This does not support parsing arbitrary ISO 8601 strings - it is only intended as the inverse operation of datetime.isoformat(). A more full-featured ISO 8601 parser, dateutil.parser.isoparse is available in the third-party package dateutil.
dateutil's isoparse will do the job:
from dateutil.parser import isoparse
time = '2022-07-14T12:01:25.225089838+08:00'
date = isoparse(time)
print(date)
# 2022-07-14 12:01:25.225089+08:00
print(repr(date))
# datetime.datetime(2022, 7, 14, 12, 1, 25, 225089, tzinfo=tzoffset(None, 28800))
Note: it doesn't round to microseconds, it just slices off the last 3 decimal places. So basically, if you're dealing with a standardized format like RFC 3339, you can do the slicing yourself like
from datetime import datetime
time = '2022-07-14T12:01:25.225089838+08:00'
date = datetime.fromisoformat(time[:-9] + time[-6:])
print(date)
# 2022-07-14 12:01:25.225089+08:00

converting between timezones (format same instance in time) in python 3

I have a list of timestamps which look like so:
time_list=['2016-10-01T00:00:00+01:00','2016-10-01T23:00:00+00:00','2016-10-01T22:00:00+02:00',..]
I would like to apply a magic function to this list which gets them all in +00:00 timezone - this should result in (all timestamps should correctly adjusted to the +00:00 format):
ret_list=['2016-10-01T23:00:00+00:00','2016-10-01T23:00:00+00:00','2016-10-01T23:00:00+00:00',..]
You have to convert your isoformat strings to datetime objects first, change timezones to UTC and then stringify back.
If you are on python 3.7, according to this, you can use fromisoformat method of datetime, but if you don't, like me, I think the best option involves the use of dateutil module (you have to install it) and pytz:
import datetime as dt
from dateutil import parser
import pytz
time_list = ['2016-10-01T00:00:00+01:00','2016-10-01T23:00:00+00:00','2016-10-01T22:00:00+02:00']
utc_time_list = [parser.parse(x).astimezone(pytz.utc).isoformat() for x in time_list]
print(utc_time_list)
['2016-09-30T23:00:00+00:00', '2016-10-01T23:00:00+00:00', '2016-10-01T20:00:00+00:00']

Python Datetime object

I am currently having an issue converting an incoming datetime string to a datetime object using Python's built in strptime() method. Here is what I currently have:
def fixDateTimeField(datetimeString):
# Convert from 2012-08-07T00:00:00Z to 2012-08-07T00:00:00.000Z
datetime_object = datetime.strptime(datetimeString, "%y-%m-%dT%H:%M:%SZ")
return datetime_object
Like the comment says, I am trying to convert the string "2012-08-07T00:00:00Z" to a datetime object that looks like 2012-08-07T00:00:00.000Z but I am getting an error in my console that says: "ValueError: time data '2012-08-07T00:00:00Z' does not match format '%y-%m-%dT%H:%M:%SZ'". The format seems correct to me and i'm not seeing the issue.
Thanks in advance!
%y is for two-digit years. You have a four-digit year.
Try using %Y instead.
>>> from datetime import datetime
>>> datetimeString = "2012-08-07T00:00:00Z"
>>> print(datetime.strptime(datetimeString, "%Y-%m-%dT%H:%M:%SZ"))
2012-08-07 00:00:00
A nice way to parse your iso-8601 datetime string "2012-08-07T00:00:00Z" to a datetime object is using dateutil.
import dateutil.parser
yourdate = dateutil.parser.parse(datestring)
With strptime:
datetime.datetime.strptime( "2007-03-04T21:08:12", "%Y-%m-%dT%H:%M:%S")
Works. As an other answer said, the Y must be capitalized for strptime to work with 4 digit years.

Datetime localization with python/django

I am trying to parse an RSS feed. Entries in the feed have date elements like:
<dc:date>2016-09-21T16:00:00+02:00</dc:date>
Using feedparser, I try to do:
published_time = datetime.fromtimestamp(mktime(entry.published_parsed))
But the problem is that I seem to be getting the wrong time stored in the database. In this particular case, the datetime is stored as:
2016-09-21 13:00:00
... when I would expect 14:00 - the correct UTC time.
I assume the problem is in our django settings, where we have:
TIME_ZONE = 'Europe/Berlin'
Because when I switch to:
TIME_ZONE = 'UTC'
... the datatime is stored as correct UTC time:
2016-09-21 14:00:00
Is there any way to keep the django settings as they are, but to parse and store this datetime correctly, without the django timezone setting affecting it?
EDIT:
Maybe it's more clear like this...
print entry.published_parsed
published_time = datetime.fromtimestamp(mktime(entry.published_parsed))
print published_time
localized_time = pytz.timezone(settings.TIME_ZONE).localize(published_time, is_dst=None)
print localized_time
time.struct_time(tm_year=2016, tm_mon=9, tm_mday=21, tm_hour=14, tm_min=0, tm_sec=0, tm_wday=2, tm_yday=265, tm_isdst=0)
2016-09-21 15:00:00
2016-09-21 15:00:00+02:00
feedparser's entry.published_parsed is always a utc time tuple whatever the input time string is. To get timezone-aware datetime object:
from datetime import datetime
utc_time = datetime(*entry.published_parsed[:6], tzinfo=utc)
where utc is a tzinfo object such as datetime.timezone.utc, pytz.utc, or just your custom tzinfo (for older python versions).
You shouldn't pass utc time to mktime() that expects a local time. Same error: Have a correct datetime with correct timezone.
Make sure USE_TZ=True so that django uses aware datetime objects everywhere. Given a timezone-aware datetime object, django should save it to db correctly whatever your TIME_ZONE or timezone.get_current_timezone() are.
Have you tried using datetime.utcfromtimestamp() instead of datetime.fromtimestamp()?
As a secondary solution, you can get the unparsed data (I believe it's available as entry.published?) and just use python-dateutil to parse the string, then convert it to pytz.utc timezone like this.
>>> import pytz
>>> from dateutil import parser
>>> dt = parser.parse('2016-09-21T16:00:00+02:00')
>>> dt
datetime.datetime(2016, 9, 21, 16, 0, tzinfo=tzoffset(None, 7200))
>>> dt.astimezone(pytz.utc)
datetime.datetime(2016, 9, 21, 14, 0, tzinfo=<UTC>)
Use
published_time = pytz.utc.localize(datetime.utcfromtimestamp(calendar.timegm(parsed_entry.published_parsed)))
Feedparser can parse a large range of date formats, you can find them here.
As you can see in feedparser/feedparser/datetimes/__init__.py, the built-in function from Feedparser _parse_date does the following:
Parses a variety of date formats into a 9-tuple in GMT
This means that in parsed_entry.published_parsed you have a time.struct_time object in GMT timezone.
When you convert it to a datetime object using
published_time = datetime.fromtimestamp(mktime(parsed_entry.published_parsed))
the problem is that mktime assumes that the passed tuple is in local time, which is not, it's GMT/UTC! Other than that you don't properly localize the datetime object at the end of the conversion.
You need to replace that conversion with the following, remembering that Feedparser returns a GMT struct_time, and localize that with the timezone you like (UTC for the sake of simplicity).
You use calendar.timegm, which gives the number of seconds between epoch and the date passed as a parameter, assuming that the passed object is in UTC/GMT (we know from Feedparser it is)
You use utcfromtimestamp to obtain a naive datetime object (which we know represents a datetime in UTC, but Python does not at this moment)
With pytz.utc.localize you properly localize in UTC the datetime object.
Example:
import calendar
from datetime import datetime
import pytz
localized_dt = pytz.utc.localize(datetime.utcfromtimestamp(calendar.timegm(parsed_entry.published_parsed)))
As long as you are consistent, it doesn't matter if you use fromtimestamp or utcfromtimestamp. If you use fromtimestamp you need to tell Python that the datetime object you created has the local timezone. Supposing you are in Europe/Berlin, this is also fine:
pytz.timezone('Europe/Berlin').localize(datetime.fromtimestamp(calendar.timegm(parsed_entry.published_parsed)))
Were parsed_entry.published_parsed also in local timezone, mktime must be used in place of calendar.timegm.
As an alternative you can parse yourself the data string you get from Feedparser parsed_entry['published']
from dateutil import parser
localized_dt = parser.parse(parsed_entry['published'])
You can check that the following returns True:
parser.parse(parsed_entry['published']) == pytz.utc.localize(datetime.utcfromtimestamp(calendar.timegm(parsed_entry.published_parsed)))
The Django TIME_ZONE setting doesn't actually matter, because it's used only for visualization purposes or to automatically convert naive datetimes.
When USE_TZ is True, this is the default time zone that Django will use to display datetimes in templates and to interpret datetimes entered in forms.
What is important is to always use properly localized datetimes, no matter which time zone is used. As long as they are not in naive format, they will be properly handled by Django.

Python DateUtil Converting string to a date and time

I'm trying to convert a parameter of type string to a date time. I'm using the dateUtil library
from dateutil import parser
myDate_string="2001/9/1 12:00:03"
dt = parser.parse(myDate_string,dayfirst=True)
print dt
every time i run this i get
2001-09-01 12:00:03
regardless of whether i have dayfirst set as true or Year first set as false. Ideally I just want to have a date in the format DD-MM-YYYY HH:MM:SS. I don't want anything fancy. I am willing to use the datetime library but this doesn't seem to work at all for me. Can anyone give simple expamples of how to convert strings to date time with an explicit format, I'm a noob, so the most basic examples are all i require. I'm using Python 2.7
The problem you're having is that any arguments you pass to parser.parse only affect how the string is parsed, not how the subsequent object is printed.
parser.parse returns a datetime object - when you print it it will just use datetime's default __str__ method. If you replace your last line with
print dt.strftime("%d-%m-%Y %H:%M:%S")
it will work as you expect.
The standard lib (built-in) datetime lib can do this easily.
from datetime import datetime
my_date_string = "2001/9/1 12:00:03"
d = datetime.strptime(my_date_string, "%Y/%m/%d %H:%M:%S")
print d.strftime("%d-%m-%Y %H:%M:%S")

Categories

Resources