3

I am working with pyExchange on windows 7 machine. I have a simple python v2.7 script that retrieves the Outlook calendar events from the exchange server. The script is provided below:

Code:

from pyexchange import Exchange2010Service, ExchangeNTLMAuthConnection
from datetime import datetime
import time
from pytz import timezone

def getEvents():
        URL = u'https://xxxxx.de/EWS/Exchange.asmx'
        USERNAME = u'MS.LOCAL\\xxxxx'
        PASSWORD = u"xxxxxx"

        connection = ExchangeNTLMAuthConnection(url=URL,
                                            username=USERNAME,
                                            password=PASSWORD)

        service = Exchange2010Service(connection)

        timestamp = datetime.now()
        print timestamp.strftime('%Y, %m, %d, %H, %M, %S')

        print time.timezone

        eventsList = service.calendar().list_events(
            start=timezone("Europe/Amsterdam").localize(datetime(2015, 1, 19, 0, 0, 0)),
            end=timezone("Europe/Amsterdam").localize(datetime(2015, 1, 19, 23, 59, 59)),
            details=True
        )

        for event in eventsList.events:
            print "{start} {stop} - {subject} - {room}".format(
                start=event.start,
                stop=event.end,
                subject=event.subject,
                room=event.location
            )

getEvents()

Problem:

The timestamp of the events doesn't match the timestamp of the events in Outlook. I created the events manually using the Outlook as well as using a pyExchange script.

For eg: If I create an event from 11:00 AM - 11:30 AM in Outlook, then the above script will return the timestamp of that event as 10:00 AM - 10:30 AM. The time is one hour less/back.

If I check my time.timezone it returns W. Europe Standard Time. I have specified my timezone in the script too ie. Europe/Amsterdam. But still the problem persists. Also I checked the timezone settings in Outlook. Shown below: enter image description here

I logged into the Exchange server and it is also in the same timezone as my client machine.

Any suggestion regarding why the time is not correct for the events? Is this a bug in pyExchange? I would really appreciate it, if some one can test this and report back here, just to be sure that its not only me who is facing this issue.

ρss
  • 5,115
  • 8
  • 43
  • 73
  • 1
    It looks like API usage issue (wrong parameters are passed e.g., it might expect naive datetime objects in the local timezone) or a bug in the Exchange stack. What does `tzlocal.get_localzone()` return? – jfs Jan 20 '15 at 10:13
  • It returns `Europe/Berlin` even I change this in my script instead of `Europe/Amsterdam`, nothing changes. The issue still persists. – ρss Jan 20 '15 at 10:31
  • Both `Europe/Berlin` and `Europe/Amsterdam` are `CET+0100` now. The timezones are the same for recent dates. I wouldn't expect a different result (it is just a sanity check). – jfs Jan 20 '15 at 10:47
  • Package maintainer here, I'll take a look. Timezones are tricky beasties, it might well be a bug in the package. Sorry for the problem! – Rachel Sanders Jan 20 '15 at 21:43

1 Answers1

1

I looked and it's probably not a bug in pyexchange, but how you're handling timezones. No shame, they're sadly extremely confusing in Python.

First, the package is returning event dates in UTC and not your local time. You're seeing an hour off the expected time because your timezone is +1 UTC. Here's an event I pulled from my calendar using your script (this is start/end/name/room):

2015-01-19 20:00:00+00:00 2015-01-19 21:00:00+00:00 - Lunch - Cafe

Note the +00:00 - that means it's in UTC. Noon here in California is 20:00 UTC.

Always, always, use UTC when handling datetimes. Here's some doc from the pytz folk on why localtimes are dangerous.

PyExchange tries to have your back and will convert localtime to UTC, but it always returns UTC. That's on purpose because see the previous link.

Now, to answer your question on getting this to work. First, convert your local time to UTC using these handy tips:

  1. Use datetime.now(pytz.utc) to get the current datetime
  2. Don't use datetime(…, tzinfo=timezone) to create a timezone aware datetime object, it's broken. Instead, create the datetime object and call timezone.localize on it.

For you, that means you have to do ugly stuff like:

start = timezone("Europe/Amsterdam").localize(datetime(2015, 1, 19, 0, 0, 0))
start = start.astimezone(pytz.utc)

Then, when you want to display UTC dates as your own time, do:

event.start.astimezone(timezone("Europe/Amsterdam"))

When I do that, I see this output from your script:

2015-01-19 21:00:00+01:00 2015-01-19 22:00:00+01:00 - Lunch - Cafe

which I would expect. Noon my time is 9pm your time.

Here's a gist of your script that I changed. Take a look and see if it fixes your problem. If not, I'm happy to look again!

Rachel Sanders
  • 5,734
  • 1
  • 27
  • 36
  • pytz talks about *naive* datetime objects representing local time. The package should accept *aware* datetime objects in any timezone e.g., `datetime.now(timezone('Europe/Amsterdam'))` -- an aware datetime object that represents the current time in `Europe/Amsterdam` timezone. (you can always convert them to UTC internally if needed (ignoring extreme datetime values). Prefer to specify `is_dst=None` to raise an exception for ambiguous or non-existent local times (errors shouldn't go unnoticed unless explicitly silenced) when calling `.localize()`. – jfs Jan 20 '15 at 23:25
  • Here's how you could [find `end - start`](http://stackoverflow.com/a/26313848/4279) depending on what `end`, `start` are. – jfs Jan 20 '15 at 23:30
  • @J.F.Sebastian - Yup. Pyexchange does accept any aware datetime objects in any timezone, it just doesn't *return* datetimes in the local timezone. Or it should. If it's not doing that, I consider that a bug. :) – Rachel Sanders Jan 20 '15 at 23:42
  • I've recently read about ["Unicode normalization insensitivity as a rule"](https://blogs.oracle.com/nico/entry/normalization_insensitivity_should_be_the) -- 1. don't modify input (preserve it whatever it is) 2. normalize on comparison. It translate to timezones: 1. return aware datetime objects exactly the same as you receive them 2. normalize (convert to utc for comparison, date arithmetics, etc). – jfs Jan 20 '15 at 23:48
  • That might be true for encodings, but it doesn't work for timezones. From UTC you can convert to anything, but the reverse is not true and it is dangerous to assume you can. Best practice is to always use UTC and only use localtime for display. Read through this: http://lucumr.pocoo.org/2011/7/15/eppur-si-muove/ – Rachel Sanders Jan 21 '15 at 00:29
  • **wrong**. *Any* aware datetime object (excluding values near `datetime.min`, `datetime.max`) can be converted to UTC e.g., `d.astimezone(pytz.utc)` to get aware datetime object or `d.replace(tzinfo=None) - d.utcoffset()` to get naive UTC datetime. The tz database changes regularly: what definition is more appropriate depends on the application (you could document that you convert to UTC at the call time. Also, Unicode normalization (NFC, NFD, etc forms) has nothing to do with character encodings. – jfs Jan 21 '15 at 00:50
  • Dude, I'm not really interested in arguing with you. – Rachel Sanders Jan 21 '15 at 01:02
  • @RachelSanders Thanks for having a look. I'll try this soon and report back if the issue got resolved. – ρss Jan 21 '15 at 09:24