Monday, February 18, 2008

Free Web Hosting

Recently I researched the state of free web hosting. I came away pretty surprised at my findings. In the past, setting up a decent website without spending a cent was impossible. But now, it's not only possible, it's easy to create a very nice site using only free tools. Below is my highly-opinionated guide on the best free services.

Web Site

First of all, you need a place to store and manage your pages. There are so many choices in this area that I couldn't even begin to sort through them all. However, two sites that stood out to me were Google Page Creator and WordPress.Com.

Google Page Creator allows up to 100 MB of storage for pages and any attachments you might want to upload. Pages created through their web interface tend to look kind of weird, but you can create pages on your own computer and upload them. Unlike many other services, there doesn't seem to be any restrictions on the types of files that you can upload to Google Page Creator.

WordPress.Com is mainly a blogging site, but it has a feature called Pages that lets you create non-blog pages and edit them using HTML. You don't have a lot of flexibility in the design of your pages, since the pages get displayed using the same template used for your blog. But you get an unlimited number of pages, and you get a very generous allotment of 3 GB for image and document uploads (other files are not allowed unless you upgrade to the paid service).

Neither service has an API, which is disappointing. WordPress.Com does give you something they call an API key, but it's only for use with the Akismet spam filtering service.

I should mention that there are various hosted wiki services that you can use to build a web site, but I won't get into that in this post.

Blog

I have the most experience with Blogger, so I can't help but put that as my number one recommendation. One of the reasons that I chose Blogger was that it has a solid and well-documented API. I don't think the other free blogging services are quite as good in this area. Using the API, I can easily create and update all my blog posts on my own machine and then upload them to Blogger's servers at my leisure. Another advantage is that no ads ever appear on your blog (unless you want them to).

Images

Picasa is my number one pick here, but Flickr seems nice too. I like Picasa the best because it has an API similar to the Blogger API [1]. If I used Flickr, I'd have to learn a whole different API, which would be annoying. Picasa gives you 1 GB of storage, which I think is pretty decent. Some of the documentation claims that Picasa only supports JPEG images, but that seems to be out-of-date because I've uploaded PNG files without any issue (and they were not converted to JPEGs during the upload process).

Videos

Well, YouTube pretty much has this market cornered. Unless you have some specific needs for video hosting, it's best to go with YouTube. Since YouTube is owned by Google, you get a nice API for it as well. The YouTube API does not support uploading, but I don't think that's a big deal.

Rich Text Documents and Spreadsheets

I don't have any experience with it but it seems that Google Docs is the best choice in this area. The limits for the service are kind of complicated so I won't summarize them here but you can read them for yourself.

Presentations

Google Docs allows you to publish presentations as well, but there's also SlideShare, which also offers an API. From reading the online documentation, I think SlideShare edges out Google Docs because it allows you to import from PowerPoint, OpenOffice, and PDF, while Google Docs only allows you to import from PowerPoint. Also, SlideShare allows presentations up to 30 MB, while Google Docs only gives you up to 10 MB.

Miscellaneous Documents

I don't have a lot of experience with it yet, but Google Base seems to be a good choice for hosting documents in various formats. Basically, you can create an item on Google Base and attach different kinds of files to it. You can attach the following formats: Adobe PDF (.pdf), Microsoft Word (.doc), Microsoft PowerPoint (.ppt), Microsoft Excel (.xls), Text (.txt), HTML (.html), Rich Text Format (.rtf), ASCII, Unicode, XML, and Word Perfect documents (.wpd). Be careful, though: depending on the type of item you create, it may expire after a certain amount of time. And of course Google Base offers an API.

General File Hosting

For all your other file hosting needs, I recommend DivShare. This free service gives you 5 GB of disk space along with 50 GB of bandwidth, which I think is kind of amazing. It doesn't restrict the types of files you can upload, so this is the place to host your ZIP, EXE, TAR.GZ, and BZ2 files. One added benefit is that it treats media files specially. For example, you can use DivShare to embed an audio file directly into your web page. That means someone viewing your page can play a song from your page without needing to download it first. I personally don't see myself ever using this feature, but it is nifty. Even though there are plenty of other ad-supported file hosting services, DivShare is a cut above the rest because it has an API that supports uploading.

Conclusion

Besides the services I wrote about in this post, there are also hosted wikis and mashup creators, both of which are loaded topics, and no way am I going to try tackling them in this already-too-long post.

I think I've convincingly argued that you can create a fairly sophisticated web site using only free tools. Many of the best services out there offer APIs, meaning that if you're a programmer, you can combine them together to create your own custom web publishing solution. In a lot of cases, it may actually be preferable to publish your material on hosted services rather than affordable web hosts like DreamHost or WebFaction [2]. That's because these hosted services are run by organizations with a lot of resources at their disposal; by using them you don't have to worry about bandwidth allocations, outages, backups, spam filtering, comment moderation, and all those other annoying issues that keep you from Getting Things Done.

[1]Picasa and Blogger both use the Atom publication protocol. And since I'm a Python developer, I can use the same gdata module to access both services.
[2]I should mention that I'm a satisfied customer of both DreamHost and WebFaction. But after doing all this research, I think that the only thing I'll be hosting on them will be my own custom-built web applications.

Saturday, February 16, 2008

XPath: Getting All the Descendant Nodes

For some reason I can never remember the proper XPath for getting all the descendant nodes (both element and text nodes). I figure if I post it on my blog, I can just look it up whenever I forget (or maybe writing it down will force it permanently into my brain). Here's the XPath expression:

//*|//text()

Pretty simple, huh? At first, I thought it was //*|text(), but that doesn't actually work. Neither does //text()|*. Those two XPath expressions aren't even equivalent -- they actually give you different results.

Now for an example! Let's say that you have the following XML:

<html>
<head>
    <title>Converting from Local Time to UTC</title>
    <link rel="stylesheet" href="../preview.css" type="text/css" />
</head>
    <body>
        <div id="meta">
            <table>
                <tr>
                    <td><b>Title:</b></td>
                    <td>Converting from Local Time to UTC</td>
                </tr>
                <tr>
                    <td><b>Entry Id:</b></td>
                    <td>None</td>
                </tr>
                <tr>
                    <td><b>Labels:</b></td>
                    <td>python, utc, datetime</td>
                </tr>
            </table>
        </div>
    </body>
</html>

Using Python's lxml module, we can write a short script that prints out all the element tags and non-whitespace strings:

from lxml import etree

tree = etree.parse(open('temp.xml'))

for node in tree.xpath('//*|//text()'):
    if isinstance(node, basestring):
        if node.strip():
            print repr(node.strip())
    else:
        print '<%s>' % node.tag

Running the above code, we get the following output:

<html>
<head>
<title>
'Converting from Local Time to UTC'
<link>
<body>
<div>
<table>
<tr>
'Converting from Local Time to UTC'
<tr>
<td>
<b>
<tr>
'Title:'
<td>
<td>
<b>
'Entry Id:'
<td>
'None'
<td>
<b>
'Labels:'
<td>
'python, utc, datetime'

As you can see, the XPath expression gives you the element and text nodes in the exact order that they appear in the document.

Friday, February 15, 2008

Python.NET 2.0 for .NET SP1

In a recent post I showed how to compile and install Python.NET 2.0 alpha 2. Unfortunately, if you are using .NET 2.0 SP 1, the instructions won't produce a fully-functional build [1]. That's because some changes in SP1 broke the latest Python.NET code. However, Nicolas Lelong figured out the solution and posted a simple patch to the Python.NET mailing list. The patch is only necessary if you are using .NET 2.0 SP 1, but non-SP 1 systems can use it as well.

Note

I've recompiled the binaries myself and posted them here.

To verify that the patch works correctly, try running the following code.

import clr
clr.AddReference('System.Windows.Forms')
from System.Drawing import Color
from System.Windows.Forms import Form, Button, Label, BorderStyle, DockStyle

count = 0

def onclick(sender, args):
    global count
    count += 1
    l.Text = 'You clicked the {%s} button %d times' % (sender.Text, count)

if __name__ == '__main__':
    f = Form()
    f.Text = 'Hello WinForms!'
    f.Width = 500

    l = Label()
    l.Text = 'This is a label'

    b = Button()
    b.Text = 'Click Me!'
    b.Click += onclick

    # Layout:
    l.Dock = DockStyle.Top
    b.Dock = DockStyle.Top
    f.Controls.Add(l)
    f.Controls.Add(b)

    f.ShowDialog()

If you don't see any weird errors [2], then everything should be fine.

[1]Specifically, delegates won't work. That means, for example, that you won't be able to use Python functions to handle button clicks in a GUI.
[2]

If you are using an unpatched Python.NET with .NET SP1, you would see an error like this:

System.TypeInitializationException: The type initializer for 'Python.Runtime.CodeGenerator' threw an exception.

Thursday, February 14, 2008

Broken CSS

The Blogger template I'm using is called "Minima Lefty Stretch". Out of the many pre-made templates that Blogger offers to you, it seemed to be the one that best fit my needs [1]. One of the cool things about the pre-made templates is that they look very consistent across all the browsers. However, even Blogger (Google) can't get it right all the time.

Shortly after posting my previous entry, I realized that the items in my OL list didn't have numbers when viewed in IE 7. When I checked the page in Firefox, Safari, and Opera, they all looked fine. I do want to get my blog to render correctly in IE, so I guess I'll have to either tweak my existing template or else create a custom one from scratch.

[1]Since this is a blog about programming, I plan on posting a lot of code snippets. These look terrible in the majority of Blogger templates because they get truncated by the width of the main content DIV. Minima Lefty Stretch is one of the few exceptions, since it stretches to match the width of the browser viewport.
Broken CSS
==========
.. labels:: css, blogger template

The Blogger template I'm using is called "Minima Lefty Stretch". Out of the many pre-made templates that Blogger offers to you, it seemed to be the one that best fit my needs [#]_. One of the cool things about the pre-made templates is that they look very consistent across all the browsers. However, even Blogger (Google) can't get it right all the time. 

Shortly after posting my `previous entry`_, I realized that the items in my ``OL`` list didn't have numbers when viewed in IE 7. When I checked the page in Firefox, Safari, and Opera, they all looked fine. I do want to get my blog to render correctly in IE, so I guess I'll have to either tweak my existing template or else create a custom one from scratch.

.. _previous entry: http://feihonghsu.blogspot.com/2008/02/installing-pythonnet-20-alpha-2-on.html

.. [#] Since this is a blog about programming, I plan on posting a lot of code snippets. These look terrible in the majority of Blogger templates because they get truncated by the width of the main content DIV. Minima Lefty Stretch is one of the few exceptions, since it stretches to match the width of the browser viewport.

Wednesday, February 13, 2008

Installing Python.NET 2.0 Alpha 2 on Windows XP

Python.NET is a project that lets you use .NET libraries from within Python [1]. The latest version (2.0) is still alpha as of this writing, so the project owners do not provide binary downloads. Even though it's alpha, I still vastly prefer version 2.0 over 1.0 because it has fewer warts [2], and its API is compatible with IronPython. In practice, it does seem pretty stable too.

I'm going to explain how I compiled and installed Python.NET 2.0 using Visual C# 2008 Express Edition, a free IDE for C#. If you don't have it installed yet, see my previous post.

Note

If you don't want to go through all the hassle of following the steps below, you can download the binary files I created here.

Here are the steps:

  1. Download the source files from Sourceforge. You can choose either a tarball or a zip file.

  2. Extract the contents to your hard drive and open the pythonnet.sln solution file using Visual C#.

  3. Convert the solution file to the Visual C# 2008 format [3].

  4. There are several projects in the solution file. The main one is called Python.Runtime. Right-click on that project and select Properties.

  5. In the Application tab, change Target Framework to ".NET Framework 2.0".

  6. The default is to build binaries for Python 2.5 and UCS2. If you need to change this, you need to make some additional tweaks [4].

  7. From the menu, select Build -> Configuration Manager. In the dialog that opens, change the Active solution configuration to Release. Press the Close button.

  8. From the menu, select Build -> Build Solution.

  9. The binary files you want can be found under src\runtime\bin\Release:

    • Python.Runtime.dll
    • clr.pyd
  10. Copy the two binary files into your Python directory (e.g. C:\Python25).

  11. Test the installation by starting the shell and typing import clr. If there are no errors, it probably worked.

If you're going to PyCon this March and are interested in learning more about Python.NET, then come to my talk! [5]

[1]Python.NET works with CPython, the default implementation of Python. It's not to be confused with IronPython, which is implemented in .NET.
[2]For example, importing System.Data is a pain if you have both .NET 1.1 and .NET 2.0 installed.
[3]Because Python.NET was originally compiled using Visual C# 2005, you will be prompted with the Visual Studio Conversion Wizard dialog. You should go ahead and convert the project.
[4]Go to the Properties -> Build tab [4]. Change the Configuration combo box to Release. Then change the value inside the Conditional compilation symbols field. For example, PYTHON24,UCS4 will build a binary for Python 2.4 with UCS4. For more details about the difference between UCS2 and UCS4, see the readme.html file inside the doc folder.
[5]According to the official schedule, my talk will be held on Friday, March 14 at 2:45 pm.
.. entry_id:: tag:blogger.com,1999:blog-5424252364534723300.post-7884767366505308962

Installing Python.NET 2.0 Alpha 2 on Windows XP
===============================================
.. labels:: python, dotnet, python.net, visual studio

Python.NET_ is a project that lets you use .NET libraries from within Python [#]_. The latest version (2.0) is still alpha as of this writing, so the project owners do not provide binary downloads. Even though it's alpha, I still vastly prefer version 2.0 over 1.0 because it has fewer warts [#]_, and its API is compatible with IronPython_. In practice, it does seem pretty stable too.

.. _Python.NET: http://pythonnet.sourceforge.net
.. _IronPython: http://codeplex.com/ironpython
.. _Visual C# 2008 Express Edition: http://www.microsoft.com/express/vcsharp/

I'm going to explain how I compiled and installed Python.NET 2.0 using `Visual C# 2008 Express Edition`_, a free IDE for C#. If you don't have it installed yet, see my `previous post`_. 

.. _previous post: http://feihonghsu.blogspot.com/2008/02/installing-visual-studio-2008-express.html

.. note::
    If you don't want to go through all the hassle of following the steps below, you can download the binary files I created here_.
    
.. _here: http://feihong.hsu.googlepages.com/PythonNET2.0.zip

Here are the steps:

#. Download the `source files from Sourceforge`_. You can choose either a tarball or a zip file.
#. Extract the contents to your hard drive and open the ``pythonnet.sln`` solution file using Visual C#. 
#. Convert the solution file to the Visual C# 2008 format [#]_.
#. There are several projects in the solution file. The main one is called Python.Runtime. Right-click on that project and select ``Properties``. 
#. In the ``Application`` tab, change ``Target Framework`` to ".NET Framework 2.0".
#. The default is to build binaries for Python 2.5 and UCS2. If you need to change this, you need to make some additional tweaks [#]_.
#. From the menu, select ``Build -> Configuration Manager``. In the dialog that opens, change the ``Active solution configuration`` to ``Release``. Press the ``Close`` button.
#. From the menu, select ``Build -> Build Solution``.
#. The binary files you want can be found under ``src\runtime\bin\Release``:

    - Python.Runtime.dll
    - clr.pyd

#. Copy the two binary files into your Python directory (e.g. ``C:\Python25``).
#. Test the installation by starting the shell and typing ``import clr``. If there are no errors, it probably worked.

.. _source files from Sourceforge: http://sourceforge.net/project/showfiles.php?group_id=162464

If you're going to PyCon_ this March and are interested in learning more about Python.NET, then come to my talk!  [#]_

.. _PyCon: http://us.pycon.org/2008

.. [#] Python.NET works with CPython_, the default implementation of Python. It's not to be confused with IronPython_, which is implemented in .NET.

.. [#] For example, importing ``System.Data`` is a pain if you have both .NET 1.1 and .NET 2.0 installed.

.. [#] Because Python.NET was originally compiled using Visual C# 2005, you will be prompted with the Visual Studio Conversion Wizard dialog. You should go ahead and convert the project.

.. [#] Go to the ``Properties -> Build`` tab [4]_. Change the ``Configuration`` combo box to ``Release``. Then change the value inside the ``Conditional compilation symbols`` field. For example, ``PYTHON24,UCS4`` will build a binary for Python 2.4 with UCS4. For more details about the difference between UCS2 and UCS4, see the ``readme.html`` file inside the ``doc`` folder.

.. [#] According to the official schedule_, my talk will be held on Friday, March 14 at 2:45 pm.

.. _CPython: http://en.wikipedia.org/wiki/Cpython
.. _schedule: http://us.pycon.org/2008/conference/schedule/

Tuesday, February 12, 2008

Installing Visual Studio 2008 Express Edition

I recently installed Visual Studio 2008 Express Edition on my laptop running Windows XP. In the past, I've had problems using Microsoft's Express Edition installers because of my unstable internet connection [1]. So this time I ignored the installers and just downloaded the All-in-One DVD ISO file [2].

The instructions for installing from the ISO file aren't very good. The best and easiest way that I found to do it was to use a free tool from Microsoft called Virtual CD-ROM Control Panel. I used this tool to mount the ISO image to the Z:\ drive. When I went to My Computer and double-clicked on Z:\, the Visual Studio installation program ran automatically. After installation was done, I went back to Virtual CD-ROM Control Panel and clicked on the Remove Drive button to get rid of the Z:\ drive.

[1]The installer is a small executable that allows you select which options you want, then automatically downloades the files you need. But if your internet connection konks out somewhere during the download process, you won't get very far, even after internet access gets restored.
[2]Scroll down all the way to the bottom of the page and you'll see a section called "Offline Install".
Installing Visual Studio 2008 Express Edition
=============================================
.. labels:: visual studio, install

I recently installed `Visual Studio 2008 Express Edition`_ on my laptop running Windows XP. In the past, I've had problems using Microsoft's Express Edition installers because of my unstable internet connection [#]_. So this time I ignored the installers and just downloaded the All-in-One DVD ISO file [#]_.

.. _Visual Studio 2008 Express Edition: http://www.microsoft.com/express/download/default.aspx

The instructions_ for installing from the ISO file aren't very good. The best and easiest way that I found to do it was to use a free tool from Microsoft called `Virtual CD-ROM Control Panel`_. I used this tool to mount the ISO image to the ``Z:\`` drive. When I went to ``My Computer`` and double-clicked on ``Z:\``, the Visual Studio installation program ran automatically. After installation was done, I went back to Virtual CD-ROM Control Panel and clicked on the ``Remove Drive`` button to get rid of the ``Z:\`` drive.

.. _Virtual CD-ROM Control Panel: http://blogs.techrepublic.com.com/window-on-windows/?p=42
.. _instructions: http://www.microsoft.com/express/download/offline.aspx

.. [#] The installer is a small executable that allows you select which options you want, then automatically downloades the files you need. But if your internet connection konks out somewhere during the download process, you won't get very far, even after internet access gets restored.

.. [#] Scroll down all the way to the bottom of the page and you'll see a section called "Offline Install".


Monday, February 11, 2008

Converting from Local Time to UTC

OK, this problem took me way, WAY too long to figure out, so I'll post it here for future reference. I was struggling with following question: How do I convert back and forth between local time and Coordinated Universal Time (UTC)?

The solution is actually fairly simple:

def local_to_utc(t):
    """Make sure that the dst flag is -1 -- this tells mktime to take daylight
    savings into account"""
    secs = time.mktime(t)
    return time.gmtime(secs)

def utc_to_local(t):
    secs = calendar.timegm(t)
    return time.localtime(secs)

OK, simple enough, but I think it's kind of weird -- why the heck do I have to use a function from the calendar module to convert from UTC to local time? Why is the timegm function in calendar instead of time?

Even though my solution looked correct (as per my understanding from reading the Python docs), I decided I needed to do a sanity check. I compared the results of my local_to_utc function with .NET's DateTime.ToUniversalTime method. Meanwhile, I also compared my utc_to_local with .NET's DateTime.ToLocalTime method.

Frankly, I was surprised by the results of my first comparison:

   Local time           Python UTC            .NET UTC
----------------     ----------------     ----------------
2000-03-12 02:00     2000-03-12 07:00     2000-03-12 08:00
2001-03-11 02:00     2001-03-11 07:00     2001-03-11 08:00
2002-03-10 02:00     2002-03-10 07:00     2002-03-10 08:00
2003-03-09 02:00     2003-03-09 07:00     2003-03-09 08:00
2004-03-14 02:00     2004-03-14 07:00     2004-03-14 08:00
2005-03-13 02:00     2005-03-13 07:00     2005-03-13 08:00
2006-03-12 02:00     2006-03-12 07:00     2006-03-12 08:00
2007-03-11 02:00     2007-03-11 07:00     2007-03-11 08:00
2008-03-09 02:00     2008-03-09 07:00     2008-03-09 08:00
2009-03-08 02:00     2009-03-08 07:00     2009-03-08 08:00
2010-03-14 02:00     2010-03-14 07:00     2010-03-14 08:00

The table above shows that Python and .NET disagree on a single hour in March of every year. That particular hour is always 2 AM on the start of Daylight Savings Time [1].

I was surprised yet again when I ran the UTC to local comparison. Having already witnessed the one-day-per-year discrepancy, I figured I would see something similar in this comparison. But there were no discrepancies at all! So I won't bother showing the table.

When I thought about it, though, this all makes sense. That 2 AM on the start of DST is a magical date/time. From our perspective, it's 1:59 AM and then the minute hand crosses over the 12, and all of a sudden it's 3 AM. So it's like that 2 AM never existed at all. Since this date/time doesn't exist in local time, there shouldn't be a way to convert to UTC at all. Technically, the Python function should have returned None, and .NET's DateTime.ToUniversalTime method should have returned null. But instead of doing that, the designers decided to just fudge it and return a UTC value that was somewhere in the ballpark. And since it was two different designers, they chose two different values to fudge on.

There are no discrepancies in the conversion from UTC to local time because here, there are no values to fudge. UTC doesn't have magical date/time values. It just keeps ticking along, and never misses a beat.

I hope this helps other people out. I googled for this information and didn't find any results that clearly explained how to convert in both directions. As for the whole magic local time issue, I think it's not a big deal as long as you're aware of it. The designers of both libraries probably figured that most people would not bother to check if a date conversion function was returning None.

[1]Daylight Savings Time is set individually by each country. Here in the US, our daylight savings period begins at 2:00 am of the second Sunday of March, and ends at 2:00 am of the first Sunday of November. The length of DST was actually changed in 2007 in an attempt to reduce energy consumption.
Converting from Local Time to UTC
=================================
.. labels:: python, utc, datetime

OK, this problem took me way, WAY too long to figure out, so I'll post it here for future reference. I was struggling with following question: How do I convert back and forth between local time and `Coordinated Universal Time`_ (UTC)?

.. _Coordinated Universal Time: http://en.wikipedia.org/wiki/Utc

The solution is actually fairly simple:

.. code:: python

    def local_to_utc(t):
        """Make sure that the dst flag is -1 -- this tells mktime to take daylight
        savings into account"""
        secs = time.mktime(t)
        return time.gmtime(secs)

    def utc_to_local(t):
        secs = calendar.timegm(t)
        return time.localtime(secs)

OK, simple enough, but I think it's kind of weird -- why the heck do I have to use a function from the ``calendar`` module to convert from UTC to local time? Why is the ``timegm`` function in ``calendar`` instead of ``time``?

Even though my solution looked correct (as per my understanding from reading the Python docs), I decided I needed to do a sanity check. I compared the results of my ``local_to_utc`` function with .NET's ``DateTime.ToUniversalTime`` method. Meanwhile, I also compared my ``utc_to_local`` with .NET's ``DateTime.ToLocalTime`` method. 

Frankly, I was surprised by the results of my first comparison::

       Local time           Python UTC            .NET UTC
    ----------------     ----------------     ----------------
    2000-03-12 02:00     2000-03-12 07:00     2000-03-12 08:00
    2001-03-11 02:00     2001-03-11 07:00     2001-03-11 08:00
    2002-03-10 02:00     2002-03-10 07:00     2002-03-10 08:00
    2003-03-09 02:00     2003-03-09 07:00     2003-03-09 08:00
    2004-03-14 02:00     2004-03-14 07:00     2004-03-14 08:00
    2005-03-13 02:00     2005-03-13 07:00     2005-03-13 08:00
    2006-03-12 02:00     2006-03-12 07:00     2006-03-12 08:00
    2007-03-11 02:00     2007-03-11 07:00     2007-03-11 08:00
    2008-03-09 02:00     2008-03-09 07:00     2008-03-09 08:00
    2009-03-08 02:00     2009-03-08 07:00     2009-03-08 08:00
    2010-03-14 02:00     2010-03-14 07:00     2010-03-14 08:00

The table above shows that Python and .NET disagree on a single hour in March of every year. That particular hour is always 2 AM on the start of Daylight Savings Time [#]_. 

I was surprised yet again when I ran the UTC to local comparison. Having already witnessed the one-day-per-year discrepancy, I figured I would see something similar in this comparison. But there were no discrepancies at all! So I won't bother showing the table.

When I thought about it, though, this all makes sense. That 2 AM on the start of DST is a magical date/time. From our perspective, it's 1:59 AM and then the minute hand crosses over the 12, and all of a sudden it's 3 AM. So it's like that 2 AM never existed at all. Since this date/time doesn't exist in local time, there shouldn't be a way to convert to UTC at all. Technically, the Python function should have returned ``None``, and .NET's ``DateTime.ToUniversalTime`` method should have returned ``null``. But instead of doing that, the designers decided to just fudge it and return a UTC value that was somewhere in the ballpark. And since it was two different designers, they chose two different values to fudge on. 

There are no discrepancies in the conversion from UTC to local time because here, there are no values to fudge. UTC doesn't have magical date/time values. It just keeps ticking along, and never misses a beat.

I hope this helps other people out. I googled for this information and didn't find any results that clearly explained how to convert in both directions. As for the whole magic local time issue, I think it's not a big deal as long as you're aware of it. The designers of both libraries probably figured that most people would not bother to check if a date conversion function was returning ``None``.

.. [#] Daylight Savings Time is set individually by each country. Here in the US, our daylight savings period begins at 2:00 am of the second Sunday of March, and ends at 2:00 am of the first Sunday of November. The length of DST was actually `changed in 2007`_ in an attempt to reduce energy consumption.

.. _changed in 2007: http://tf.nist.gov/general/dst.htm