Python UTC timestamp with ISO format - python

I know this has been asked but there was no solution provided there.
Python UTC datetime object's ISO format doesn't include Z (Zulu or Zero offset)
I am looking for a clean way of generating UTC time stamp with this format in Python. The format I need is
2013-10-29T09:38:41.341Z.
Specifically, I need to include "Z" at the end. Python's datetime.utcnow().isoformat() does not append "Z" at the end.
Note that manually appending "Z" is not a solution I can accept. I am looking for a clean way to do this.
What is the clean way to generate UTC timestamp in ISO format with the suffix Z?

How about something like
datetime.utcnow().isoformat()[:-3] + 'Z'

You can use the arrow library.
Arrow doesn't cover it yet, see github issue. And I don't think any python library does it yet. But It is pretty simple to hack for now on.
Need to be installed with pip though:
$ pip install arrow
Then get your iso format, but without Zulu format
import arrow
arrow.utcnow().isoformat()
#'2017-02-10T08:44:38.954159+00:00'
Or you make your own.
arrow.utcnow().format('YYYY-MM-DDTHH:mm:ss.SSS') + 'Z'
# 2017-02-11T12:34:30.483Z

Due to lack of reputation, I add this as new Answer.
Tomasz Swider Solution is a good start, however [:-3] will cut off seconds if the time provided does not have microseconds:
I will demonstrate this using utcfromtimestamp:
In [10]: datetime.utcfromtimestamp(0.1).isoformat()[:-3] + 'Z'
Out[10]: '1970-01-01T00:00:00.100Z'
In [10]: datetime.utcfromtimestamp(0).isoformat()[:-3] + 'Z'
Out[11]: '1970-01-01T00:00Z'
I think this is a cleaner solution to get an ISO Date with Milliseconds and 'Z' for Zulu time:
datetime.utcnow().isoformat(timespec='milliseconds')+ 'Z'
Again demonstrating using utcfromtimestamp:
In [25]: datetime.utcfromtimestamp(0.1).isoformat(timespec='milliseconds')+ 'Z'
Out[25]: '1970-01-01T00:00:00.100Z'
In [25]: datetime.utcfromtimestamp(0).isoformat(timespec='milliseconds')+ 'Z'
Out[25]: '1970-01-01T00:00:00.000Z'

This is how DJango does it [1], [2]:
DjangoJSONEncoder
class django.core.serializers.json.DjangoJSONEncoder¶
The JSON serializer uses DjangoJSONEncoder for encoding. A subclass of
JSONEncoder, it handles these additional types:
datetime
A string of the form YYYY-MM-DDTHH:mm:ss.sssZ or YYYY-MM-DDTHH:mm:ss.sss+HH:MM as defined in ECMA-262.
def default(self, o):
# See "Date Time String Format" in the ECMA-262 specification.
if isinstance(o, datetime.datetime):
r = o.isoformat()
if o.microsecond:
r = r[:23] + r[26:]
if r.endswith('+00:00'):
r = r[:-6] + 'Z'
return r
print(f"aaa{naive_utcnow.isoformat()[:23] = }")
Please note date datetime objects may or may not contain timezone information (a distinction called naive and aware datetime objects).
In your example, datetime.utcnow() will produce a naive object, which will not work properly with django code.
In case you want to always have a Z in the end (e.g. for interoperability with other systems, such as client browsers and node), take a look at the script below where I explain how to get there as well as how to handle some common pitfalls in handling datetimes with python:
from datetime import datetime, timezone
utc = timezone.utc
naive_utcnow = datetime.utcnow()
aware_utcnow = datetime.now(utc)
# there is no timezone info for naive objects here:
print(f"{naive_utcnow.isoformat() = }")
# with "+00:00":
print(f"{aware_utcnow.isoformat() = }")
# copy & paste from django implementation:
def toECMA262_django(dt: datetime):
s = dt.isoformat()
if dt.microsecond:
s = s[:23] + s[26:]
if s.endswith('+00:00'):
s = s[:-6] + 'Z'
return s
# note: django's version won't add Z for naive objects:
print(f"{toECMA262_django(naive_utcnow) = }")
# djanto's output is perfecly compatible with javacript
# for aware datetime objects:
print(f"{toECMA262_django(aware_utcnow) = }")
# improved version to treat naive objects as utc by default
def toECMA262_v2(dt: datetime, default_tz=utc):
if not dt.tzinfo:
dt = dt.replace(tzinfo=default_tz)
s = dt.isoformat()
if dt.microsecond:
s = s[:23] + s[26:]
if s.endswith('+00:00'):
s = s[:-6] + 'Z'
return s
# now has Z too:
print(f"{toECMA262_v2(naive_utcnow) = }")
print(f"{toECMA262_v2(aware_utcnow) = }")
# now works even with the misleading utcnow():
print(f"{toECMA262_v2(datetime.utcnow()) = }")
# CAREFUL: wrong result here, there is no distinction between
# naive objects returned from now() and utcnow(), the calling
# code is responsible for knowing if naive objects are in utc or not.
print(f"{toECMA262_v2(datetime.now()) = }")
# safer version, no default assumptions made
def toECMA262_v3(dt: datetime, naive_as_tz=None):
if not dt.tzinfo and naive_as_tz:
dt = dt.replace(tzinfo=naive_as_tz)
s = dt.isoformat()
if dt.microsecond:
s = s[:23] + s[26:]
if s.endswith('+00:00'):
s = s[:-6] + 'Z'
return s
# no tz offset for naive objects, unless explicitly specified:
print(f"{toECMA262_v3(naive_utcnow) = }")
print(f"{toECMA262_v3(naive_utcnow, utc) = }")
print(f"{toECMA262_v3(aware_utcnow) = }")
# no tz offset for naive objects, unless explicitly specified:
print(f"{toECMA262_v3(datetime.utcnow()) = }")
print(f"{toECMA262_v3(datetime.utcnow(), utc) = }")
# this is not wrong anymore, but no tz offset either
print(f"{toECMA262_v3(datetime.now()) = }")
# even safer, guarantees there will be a timezone or an exception is raised
def toECMA262_v4(dt: datetime, naive_as_tz=None):
if not dt.tzinfo:
if not naive_as_tz:
raise ValueError('Aware object or naive_as_tz required')
dt = dt.replace(tzinfo=naive_as_tz)
s = dt.isoformat()
if dt.microsecond:
s = s[:23] + s[26:]
if s.endswith('+00:00'):
s = s[:-6] + 'Z'
return s
def try_print(expr):
'''little helper function to print exceptions in place'''
try:
print(f"{expr} = ", end='')
print(repr(eval(expr)))
except ValueError as exc:
print(repr(exc))
# works with naive when tz is explicitly passed, otherwise raise:
try_print("toECMA262_v4(naive_utcnow, utc)")
try_print("toECMA262_v4(naive_utcnow)") # raises
try_print("toECMA262_v4(aware_utcnow)")
try_print("toECMA262_v4(datetime.utcnow(), utc)")
try_print("toECMA262_v4(datetime.utcnow())") # raises
try_print("toECMA262_v4(datetime.now())") # raises
# Please note that if have an aware object that is not in utc,
# you will not get a string ending in Z, but the proper offset
# For example:
import dateutil.tz
tzlocal = dateutil.tz.tzlocal()
aware_now = datetime.now(tzlocal)
print(f"{toECMA262_v4(aware_now) = }")
# output '2021-05-25T04:15:44.848-03:00'
# version that always output Z ended strings:
def toECMA262_v5(dt: datetime, naive_as_tz=None):
if not dt.tzinfo:
if not naive_as_tz:
raise ValueError('Aware object or naive_as_tz required')
dt = dt.replace(tzinfo=naive_as_tz)
dt = dt.astimezone(utc)
s = dt.isoformat()
if dt.microsecond:
s = s[:23] + s[26:]
if s.endswith('+00:00'):
s = s[:-6] + 'Z'
return s
# all possible cases supported and correct now, all returned with Z:
try_print("toECMA262_v5(naive_utcnow, utc)")
try_print("toECMA262_v5(naive_utcnow)") # raises
try_print("toECMA262_v5(aware_utcnow)")
try_print("toECMA262_v5(aware_now)")
try_print("toECMA262_v5(datetime.utcnow(), utc)")
try_print("toECMA262_v5(datetime.utcnow())") # raises
try_print("toECMA262_v5(datetime.now())") # raises
try_print("toECMA262_v5(datetime.now(), tzlocal)") # works fine now ;)
The output of the script:
naive_utcnow.isoformat() = '2021-05-25T07:45:22.774853'
aware_utcnow.isoformat() = '2021-05-25T07:45:22.774856+00:00'
toECMA262_django(naive_utcnow) = '2021-05-25T07:45:22.774'
toECMA262_django(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(naive_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(datetime.utcnow()) = '2021-05-25T07:45:22.774Z'
toECMA262_v2(datetime.now()) = '2021-05-25T04:45:22.774Z'
toECMA262_v3(naive_utcnow) = '2021-05-25T07:45:22.774'
toECMA262_v3(naive_utcnow, utc) = '2021-05-25T07:45:22.774Z'
toECMA262_v3(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v3(datetime.utcnow()) = '2021-05-25T07:45:22.775'
toECMA262_v3(datetime.utcnow(), utc) = '2021-05-25T07:45:22.775Z'
toECMA262_v3(datetime.now()) = '2021-05-25T04:45:22.775'
toECMA262_v4(naive_utcnow, utc) = '2021-05-25T07:45:22.774Z'
toECMA262_v4(naive_utcnow) = ValueError('Aware object or naive_as_tz required')
toECMA262_v4(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v4(datetime.utcnow(), utc) = '2021-05-25T07:45:22.775Z'
toECMA262_v4(datetime.utcnow()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v4(datetime.now()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v4(aware_now) = '2021-05-25T04:45:22.788-03:00'
toECMA262_v5(naive_utcnow, utc) = '2021-05-25T07:45:22.774Z'
toECMA262_v5(naive_utcnow) = ValueError('Aware object or naive_as_tz required')
toECMA262_v5(aware_utcnow) = '2021-05-25T07:45:22.774Z'
toECMA262_v5(aware_now) = '2021-05-25T07:45:22.788Z'
toECMA262_v5(datetime.utcnow(), utc) = '2021-05-25T07:45:22.788Z'
toECMA262_v5(datetime.utcnow()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v5(datetime.now()) = ValueError('Aware object or naive_as_tz required')
toECMA262_v5(datetime.now(), tzlocal) = '2021-05-25T07:45:22.788Z'
Version 5 above always output Z ended ECMA-262 compatible strings, accepting datetime objects in any timezone. If naive datetimes are passed, the caller code must specify if the object is in utc, local or any other timezone and it will be converted to utc automatically.
PS: I used python >= 3.8's newers fstring debug syntax with = for printing the output in a more friendly/conscise way, besides that the code should run fine with python >= 3.2

zulu = "{}Z".format(arrow.utcnow().format('YYYY-MM-DDTHH:mm:ss.SSS'))
#'2018-11-28T21:54:49.639Z'

Related

How do I convert a string to a datetime without many nested try...excepts?

I'm trying to check user input of a date/time in several allowable formats. (I know about the dateutil library. It's not what I'm looking for in this case.)
If some user input was accepted, the function must return a datetime object.
If ALL "try...except" fail — the function must return NONE. But I have 30-50 different date/time formats that I need to check.
I'm confused by the huge indentation in my code! How do I organize this format checking in a good style with GOOD performance?
# Test format check program
import datetime
def datetime_format_check(str):
try:
dt = datetime.datetime.strptime(str, "%y-%m-%d %H:%M")
return dt
except:
try:
dt = datetime.datetime.strptime(str, "%Y-%m-%d %H:%M")
return dt
except:
try:
dt = datetime.datetime.strptime(str, "%y-%m-%d")
return dt
except:
try:
dt = datetime.datetime.strptime(str, "%Y-%m-%d")
return dt
except:
try:
dt = datetime.datetime.strptime(str, "%H:%M")
return dt
except:
try:
# . . .
# many many try...except blocks )))
# . . .
return None # last except far far away from a screen border. ))))
while True:
str = input("Input date: ")
print("Result: ", datetime_format_check(str))
Repetitive code? Well, that just begs to be replaced with a loop.
Put all of the formats in a list and iterate over it, checking each format:
def datetime_format_check(s):
formats = ["%y-%m-%d %H:%M", "%Y-%m-%d %H:%M", "%y-%m-%d"] # etc
for format in formats:
try:
dt = datetime.datetime.strptime(s, format)
return dt
except ValueError:
pass
return None
Some minor corrections I made to your code:
Don't name your argument str; it shadows the builtin.
Don't use a bare except:, always catch the specific exception.

Python 3.8 datetime date comparison not work between "internal generated date" and imported date

I'm trying to compare the actual date with externally generated date, always generated from datetime but in another script and saved in a txt file.
This is the code:
import datetime
datin = datetime.datetime.today()
with open('date.txt', 'r') as mydate:
mdate = mydate.read()
datex = datetime.datetime.strptime(mdate, '%d-%m-%Y')
if datin.date == datex.date:
print('=')
else:
print('!=')
print(datin.strftime('%d-%m-%Y'))
print(datex.strftime('%d-%m-%Y'))
this is the txt file:
03-07-2020
(the same date I'm testing the script)
should return = but return !=
What am I doing wrong?
You have a slight error in that you are accessing the method of the date objects instead of calling the method.
You can find this out by trying to print
datin.date versus datin.date()
Here is the corrected code that runs as expected:
import datetime
datin = datetime.datetime.today()
mdate = '03-07-2020'
datex = datetime.datetime.strptime(mdate,"%d-%m-%Y")
print(datin.date())
print(datex.date())
if datin.date() == datex.date():
print("=")
else:
print("!=")
print (datin.strftime("%d-%m-%Y"))
print(datex.strftime("%d-%m-%Y"))

Adding seconds to ISO 8601 datestamp string

I am trying to add seconds to a datestamp string that is received from a json object but the datetime function I am trying to use does not allow strings and wants the date to be separated like: datetime.strftime(2011,11,18). Here is what I have:
import requests
from datetime import datetime
def call():
pay = {'token' : "802ba928cd3ce9acd90595df2853ee2b"}
r = requests.post('http://challenge.code2040.org/api/dating',
params=pay)
response = r.json()
time = response['datestamp']
interval = response['interval']
utc = datetime.strftime(time, '%Y-%m-%dT&H:%M:%S.%fZ')
timestamp = (utc-time).total_seconds()
utc_dt = datetime(time) + timedelta(seconds=timestamp)
print(utc_dt.strftime('%Y-%m-%dT%H:%M:%S.%fZ'))
Is there another way I can add time to a ISO8601 datestamp?

Jira python calculate time

I am trying to calculate the time from the issue is created and until it is resolved. With these fields:
creation_time = issue.fields.created
resolved_time = issue.fields.resolutiondate
Output when I print:
Creation: 2016-06-09T14:37:05.000+0200 Resolved: 2016-06-10T10:53:12.000+0200
Is there anyway I can minus the resolution date and time with the creation date and time to find how much time is spent on a issue?
Parse the date/time strings into a suitable datetime object and then you can use those to do calculations.
This post explains how to parse a date/time string or you can just take a look at the documentation for the strptime() method.
For calculations, there are examples in this post and there's detailed documentation here.
As an example, something like this should be close to a solution:
from datetime import datetime
from datetime import timedelta
createdTime = datetime.strptime('2016-06-09T14:37:05.000+0200', '%Y-%m-%dT%H:%M:%S.%f')
resolvedTime = datetime.strptime('2016-06-10T10:53:12.000+0200', '%Y-%m-%dT%H:%M:%S.%f')
duration = resolvedTime - createdTime
duration will be a timedelta object and you can access duration.days, duration.seconds and duration.microseconds to get its info.
strptime does have as a drawback that it does not support parsing timezones, so you'll have to cut that part of your input first. Alternatively, see this post.
strptime does not support parsing timezones.
This code is working for me
from datetime import datetime
createdTime = datetime.strptime(issue.fields.created.split(".")[0], '%Y-%m-%dT%H:%M:%S')
resolvedTime = datetime.strptime(issue.fields.resolutiondate.split(".")[0], '%Y-%m-%dT%H:%M:%S')
duration = resolvedTime - createdTime
I've write a function which calculates mean, median and variance of the respond times in days. Hope that helps;
import datetime as d
import numpy as np
ymd_create = []
ymd_resdate = []
delta_t = []
class calculate:
def __init__(self):
self.result = 0
def meantime(self, issueobject):
for i in range(0, len(issueobject)):
ymd_create.append(d.datetime(int(issueobject[i].raw[u'fields'][u'created'].split('T')[0].split('-')[0]), int(issueobject[i].raw[u'fields'][u'created'].split('T')[0].split('-')[1]), int(issueobject[i].raw[u'fields']
[u'created'].split('T')[0].split('-')[2]), int(issueobject[i].raw[u'fields'][u'created'].split('T')[1].split(':')[0]), int(issueobject[i].raw[u'fields'][u'created'].split('T')[1].split(':')[1])))
ymd_resdate.append(d.datetime(int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[0].split('-')[0]), int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[0].split('-')[1]), int(issueobject[i].raw[u'fields']
[u'resolutiondate'].split('T')[0].split('-')[2]), int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[1].split(':')[0]), int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[1].split(':')[1])))
delta_t.append((ymd_resdate[i] - ymd_create[i]).days)
self.result = np.mean(np.array(delta_t))
return self.result
def mediantime(self, issueobject):
for i in range(0, len(issueobject)):
ymd_create.append(d.datetime(int(issueobject[i].raw[u'fields'][u'created'].split('T')[0].split('-')[0]), int(issueobject[i].raw[u'fields'][u'created'].split('T')[0].split('-')[1]), int(issueobject[i].raw[u'fields']
[u'created'].split('T')[0].split('-')[2]), int(issueobject[i].raw[u'fields'][u'created'].split('T')[1].split(':')[0]), int(issueobject[i].raw[u'fields'][u'created'].split('T')[1].split(':')[1])))
ymd_resdate.append(d.datetime(int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[0].split('-')[0]), int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[0].split('-')[1]), int(issueobject[i].raw[u'fields']
[u'resolutiondate'].split('T')[0].split('-')[2]), int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[1].split(':')[0]), int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[1].split(':')[1])))
delta_t.append((ymd_resdate[i] - ymd_create[i]).days)
self.result = np.median(np.array(delta_t))
return self.result
def variancetime(self, issueobject):
for i in range(0, len(issueobject)):
ymd_create.append(d.datetime(int(issueobject[i].raw[u'fields'][u'created'].split('T')[0].split('-')[0]), int(issueobject[i].raw[u'fields'][u'created'].split('T')[0].split('-')[1]), int(issueobject[i].raw[u'fields']
[u'created'].split('T')[0].split('-')[2]), int(issueobject[i].raw[u'fields'][u'created'].split('T')[1].split(':')[0]), int(issueobject[i].raw[u'fields'][u'created'].split('T')[1].split(':')[1])))
ymd_resdate.append(d.datetime(int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[0].split('-')[0]), int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[0].split('-')[1]), int(issueobject[i].raw[u'fields']
[u'resolutiondate'].split('T')[0].split('-')[2]), int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[1].split(':')[0]), int(issueobject[i].raw[u'fields'][u'resolutiondate'].split('T')[1].split(':')[1])))
delta_t.append((ymd_resdate[i] - ymd_create[i]).days)
self.result = np.var(np.array(delta_t))
return self.result

parse dates with icalendar and compare to python datetime

I have an .ics file from which I would like to extract all of the events that occur on today's day. I think I'm having trouble converting the icalendar DTSTART and DTEND to python datetimes. I've tried to follow the documentation at icalendar.readthedocs.org. The list I'm getting is empty, which should not be the case.
This is my code:
import urllib2
import json
from datetime import datetime
from icalendar import Calendar, Event, vDatetime
def getTodayEvents(icsFile):
cal = Calendar.from_ical(icsFile)
today = datetime.now().date()
entries = []
for event in cal.walk('VEVENT'):
dtstart = event['DTSTART']
dtend = event['DTEND']
start = vDatetime.from_ical(dtstart) //Trouble here?
end = vDatetime.from_ical(dtend)
if start <= today <= end:
entry = {'summary' : event['SUMMARY'] }
entries.append(entry)
output = json.dumps(entries)
return output //This list is empty
And this is what the and ics entry looks like:
BEGIN:VEVENT
SUMMARY:Jonny Smith
DTSTART;VALUE=DATE:20140731
DTEND;VALUE=DATE:20150802
UID: 12345
CLASS:PUBLIC
PRIORITY:5
DTSTAMP:20141006T160145Z
TRANSP:OPAQUE
STATUS:CONFIRMED
SEQUENCE:0
LOCATION:Mansfield\, GA
X-MICROSOFT-CDO-APPT-SEQUENCE:0
X-MICROSOFT-CDO-BUSYSTATUS:FREE
X-MICROSOFT-CDO-INTENDEDSTATUS:BUSY
X-MICROSOFT-CDO-ALLDAYEVENT:TRUE
X-MICROSOFT-CDO-IMPORTANCE:1
X-MICROSOFT-CDO-INSTTYPE:0
X-MICROSOFT-DISALLOW-COUNTER:FALSE
END:VEVENT
DTSTART, DTEND properties have .dt attribute:
#!/usr/bin/env python
import json
from datetime import date
import icalendar # $ pip install icalendar
today = date.today()
calendar = icalendar.Calendar.from_ical(ics_file)
entries = [dict(summary=event['SUMMARY'])
for event in calendar.walk('VEVENT')
if event['DTSTART'].dt <= today <= event['DTEND'].dt]
print(json.dumps(entries, indent=2, sort_keys=True))
Output
[
{
"summary": "Jonny Smith"
}
]
The event object has a method .decoded(), which gives you either a datetime.date object (as in your case, the .ics only has a date) or a datetime.datetime object. For the datetime.datetime object, you additionally need to convert the correct timezone.
In order to make a unified comparison, I convert everything to a string and then compare the string. This ended up, that I wrote an isEventToday method:
from datetime import datetime, timezone, timedelta
def isEventToday(event):
if event.get('dtstart') == None:
dtstart = ""
else:
temp = event.decoded('dtstart')
if isinstance(temp, datetime):
dtstart = temp.astimezone().strftime("%Y-%m-%d")
else:
dtstart = temp.strftime("%Y-%m-%d")
if event.get('dtend') == None:
dtend = ""
else:
temp = event.decoded('dtend')
if isinstance(temp, datetime):
dtend = temp.astimezone().strftime("%Y-%m-%d")
else:
# dtend for day events is the day AFTER the event, so we
# need to substract one!
dtend = (temp - timedelta(days=1)).strftime("%Y-%m-%d")
today = datetime.today().date().strftime("%Y-%m-%d")
if dtstart != "" and dtstart == today:
return True
if dtend != "" and dtend == today:
return True
if dtstart != "" and dtend != "" and dtstart <= today and today <= dtend:
return True
return False
The code does not look nice to me, but it is working.
Check to see if you've got a discrepancy between data types or content in your if start <= today <= end: comparison. Take a look (in debugger or so) at what are the types and content of those three variables. I think you'll find that the comparison is comparing things that are legal to compare, but not compatible enough to give you the answer you expect (i.e., do the start and end times of this event overlap todays date?)
Your today is a datetime structure, which can be compared to other datetimes as you intend. Perhaps your vDatetime.from_ical(dtstart) is returning something other than a datetime. A quick glance at the source looks like it should be returning a datetime though. Maybe you've got a time zone issue? Look at the content of all three and check which is < or == or > others.
If that's the case, add a time zone to your calls to vDatetime.from_ical() calls;
start = vDatetime.from_ical(dtstart,'Europe/Amsterdam') #or wherever you are
Your time in the .ics indicates Z -- i.e., GMT.
If you need to do more with dates, see working with time.

Categories

Resources