bug 1347212: improve release automation publishing of releases - add support for scheduling publishing of releases. r=rail
authorBen Hearsum <bhearsum@mozilla.com>
Fri, 31 Mar 2017 10:01:20 -0400
changeset 7413 0a0b27078a18
parent 7412 451aa5b9b730
child 7414 0167a5d06a92
push id5518
push userbhearsum@mozilla.com
push dateFri, 31 Mar 2017 14:08:22 +0000
reviewersrail
bugs1347212
bug 1347212: improve release automation publishing of releases - add support for scheduling publishing of releases. r=rail
lib/python/balrog/submitter/api.py
lib/python/balrog/submitter/cli.py
lib/python/vendor/arrow-0.10.0/HISTORY.md
lib/python/vendor/arrow-0.10.0/LICENSE
lib/python/vendor/arrow-0.10.0/MANIFEST.in
lib/python/vendor/arrow-0.10.0/PKG-INFO
lib/python/vendor/arrow-0.10.0/README.rst
lib/python/vendor/arrow-0.10.0/arrow.egg-info/PKG-INFO
lib/python/vendor/arrow-0.10.0/arrow.egg-info/SOURCES.txt
lib/python/vendor/arrow-0.10.0/arrow.egg-info/dependency_links.txt
lib/python/vendor/arrow-0.10.0/arrow.egg-info/not-zip-safe
lib/python/vendor/arrow-0.10.0/arrow.egg-info/requires.txt
lib/python/vendor/arrow-0.10.0/arrow.egg-info/top_level.txt
lib/python/vendor/arrow-0.10.0/arrow/__init__.py
lib/python/vendor/arrow-0.10.0/arrow/api.py
lib/python/vendor/arrow-0.10.0/arrow/arrow.py
lib/python/vendor/arrow-0.10.0/arrow/factory.py
lib/python/vendor/arrow-0.10.0/arrow/formatter.py
lib/python/vendor/arrow-0.10.0/arrow/locales.py
lib/python/vendor/arrow-0.10.0/arrow/parser.py
lib/python/vendor/arrow-0.10.0/arrow/util.py
lib/python/vendor/arrow-0.10.0/docs/Makefile
lib/python/vendor/arrow-0.10.0/docs/_themes/COPYING.txt
lib/python/vendor/arrow-0.10.0/docs/_themes/README.rst
lib/python/vendor/arrow-0.10.0/docs/_themes/f6/NEWS.txt
lib/python/vendor/arrow-0.10.0/docs/_themes/f6/README.rst
lib/python/vendor/arrow-0.10.0/docs/_themes/f6/layout.html
lib/python/vendor/arrow-0.10.0/docs/_themes/f6/static/brillant.png
lib/python/vendor/arrow-0.10.0/docs/_themes/f6/static/f6.css
lib/python/vendor/arrow-0.10.0/docs/_themes/f6/theme.conf
lib/python/vendor/arrow-0.10.0/docs/conf.py
lib/python/vendor/arrow-0.10.0/docs/index.rst
lib/python/vendor/arrow-0.10.0/setup.cfg
lib/python/vendor/arrow-0.10.0/setup.py
lib/python/vendor/balrogclient-0.0.1/balrogclient/__init__.py
lib/python/vendor/balrogclient-0.0.1/balrogclient/api.py
lib/python/vendorlibs.pth
scripts/build-promotion/balrog-release-shipper.py
--- a/lib/python/balrog/submitter/api.py
+++ b/lib/python/balrog/submitter/api.py
@@ -1,8 +1,8 @@
 import os
 import site
 
 site.addsitedir(os.path.join(os.path.dirname(__file__), "../.."))
 
-from balrogclient import is_csrf_token_expired, SingleLocale, Release, Rule
+from balrogclient import is_csrf_token_expired, SingleLocale, Release, Rule, ScheduledRuleChange
 
-__all__ = [ 'is_csrf_token_expired', 'SingleLocale', 'Release', 'Rule' ]
+__all__ = [ 'is_csrf_token_expired', 'SingleLocale', 'Release', 'Rule', 'ScheduledRuleChange' ]
--- a/lib/python/balrog/submitter/cli.py
+++ b/lib/python/balrog/submitter/cli.py
@@ -1,19 +1,20 @@
+import arrow
 try:
     import simplejson as json
 except ImportError:
     import json
 
 from release.info import getProductDetails
 from release.paths import makeCandidatesDir
 from release.platforms import buildbot2updatePlatforms, buildbot2bouncer, \
   buildbot2ftp
 from release.versions import getPrettyVersion
-from balrog.submitter.api import Release, SingleLocale, Rule
+from balrog.submitter.api import Release, SingleLocale, Rule, ScheduledRuleChange
 from balrog.submitter.updates import merge_partial_updates
 from util.algorithms import recursive_update
 from util.retry import retry
 import logging
 from requests.exceptions import HTTPError
 
 log = logging.getLogger(__name__)
 
@@ -469,22 +470,50 @@ class ReleaseSubmitterV4(ReleaseSubmitte
 
 
 class ReleasePusher(object):
     def __init__(self, api_root, auth, dummy=False):
         self.api_root = api_root
         self.auth = auth
         self.dummy = dummy
 
-    def run(self, productName, version, build_number, rule_ids):
+    def run(self, productName, version, build_number, rule_ids, backgroundRate=None):
         name = get_release_blob_name(productName, version, build_number,
                                      self.dummy)
         for rule_id in rule_ids:
+            data = {"mapping": name}
+            if backgroundRate:
+                data["backgroundRate"] = backgroundRate
             Rule(api_root=self.api_root, auth=self.auth, rule_id=rule_id
-                 ).update_rule(mapping=name)
+                 ).update_rule(**data)
+
+
+class ReleaseScheduler(object):
+    def __init__(self, api_root, auth, dummy=False):
+        self.api_root = api_root
+        self.auth = auth
+        self.dummy = dummy
+
+    def run(self, productName, version, build_number, rule_ids, when, backgroundRate=None):
+        name = get_release_blob_name(productName, version, build_number,
+                                     self.dummy)
+        for rule_id in rule_ids:
+            data, data_version = Rule(api_root=self.api_root, auth=self.auth, rule_id=rule_id).get_data()
+            data["fallbackMapping"] = data["mapping"]
+            data["mapping"] = name
+            data["data_verison"] = data_version
+            data["rule_id"] = rule_id
+            data["change_type"] = "update"
+            # We receive an iso8601 datetime, but what Balrog needs is a to-the-millisecond epoch timestamp
+            data["when"] = arrow.get(when).timestamp * 1000
+            if backgroundRate:
+                data["backgroundRate"] = backgroundRate
+
+            ScheduledRuleChange(api_root=self.api_root, auth=self.auth, rule_id=rule_id
+                               ).add_scheduled_rule_change(**data)
 
 
 class BlobTweaker(object):
     def __init__(self, api_root, auth):
         self.api_root = api_root
         self.auth = auth
 
     def run(self, name, data):
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/HISTORY.md
@@ -0,0 +1,231 @@
+## History
+
+### 0.10.0
+
+- [FIX] Fix getattr off by one for quarter
+- [FIX] Fix negative offset for UTC
+- [FIX] Update arrow.py
+
+### 0.9.0
+
+- [NEW] Remove duplicate code
+- [NEW] Support gnu date iso 8601
+- [NEW] Add support for universal wheels
+- [NEW] Slovenian locale
+- [NEW] Slovak locale
+- [NEW] Romanian locale
+- [FIX] respect limit even if end is defined range
+- [FIX] Separate replace & shift functions
+- [NEW] Added tox
+- [FIX] Fix supported Python versions in documentation 
+- [NEW] Azerbaijani locale added, locale issue fixed in Turkish.
+- [FIX] Format ParserError's raise message 
+
+### 0.8.0
+
+- []
+
+### 0.7.1
+
+- [NEW] Esperanto locale (batisteo)
+
+
+### 0.7.0 
+- [FIX] Parse localized strings #228 (swistakm)
+- [FIX] Modify tzinfo parameter in `get` api #221 (bottleimp)
+- [FIX] Fix Czech locale (PrehistoricTeam)
+- [FIX] Raise TypeError when adding/subtracting non-dates (itsmeolivia)
+- [FIX] Fix pytz conversion error (Kudo)
+- [FIX] Fix overzealous time truncation in span_range (kdeldycke) 
+- [NEW] Humanize for time duration #232 (ybrs)
+- [NEW] Add Thai locale (sipp11)
+- [NEW] Adding Belarusian (be) locale (oire)
+- [NEW] Search date in strings (beenje)
+- [NEW] Note that arrow's tokens differ from strptime's. (offby1)
+
+### 0.6.0
+
+- [FIX] Added support for Python 3
+- [FIX] Avoid truncating oversized epoch timestamps. Fixes #216.
+- [FIX] Fixed month abbreviations for Ukrainian
+- [FIX] Fix typo timezone
+- [FIX] A couple of dialect fixes and two new languages
+- [FIX] Spanish locale: `Miercoles` should have acute accent
+- [Fix] Fix Finnish grammar
+- [FIX] Fix typo in 'Arrow.floor' docstring
+- [FIX] Use read() utility to open README
+- [FIX] span_range for week frame
+- [NEW] Add minimal support for fractional seconds longer than six digits.
+- [NEW] Adding locale support for Marathi (mr)
+- [NEW] Add count argument to span method
+- [NEW] Improved docs
+
+
+### 0.5.1 - 0.5.4
+
+- [FIX] test the behavior of simplejson instead of calling for_json directly (tonyseek)
+- [FIX] Add Hebrew Locale (doodyparizada)
+- [FIX] Update documentation location (andrewelkins)
+- [FIX] Update setup.py Development Status level (andrewelkins)
+- [FIX] Case insensitive month match (cshowe)
+
+### 0.5.0
+
+- [NEW] struct_time addition. (mhworth)
+- [NEW] Version grep (eirnym)
+- [NEW] Default to ISO-8601 format (emonty)
+- [NEW] Raise TypeError on comparison (sniekamp)
+- [NEW] Adding Macedonian(mk) locale (krisfremen)
+- [FIX] Fix for ISO seconds and fractional seconds (sdispater) (andrewelkins)
+- [FIX] Use correct Dutch wording for "hours" (wbolster)
+- [FIX] Complete the list of english locales (indorilftw)
+- [FIX] Change README to reStructuredText (nyuszika7h)
+- [FIX] Parse lower-cased 'h' (tamentis)
+- [FIX] Slight modifications to Dutch locale (nvie)
+
+### 0.4.4
+
+- [NEW] Include the docs in the released tarball
+- [NEW] Czech localization Czech localization for Arrow
+- [NEW] Add fa_ir to locales
+- [FIX] Fixes parsing of time strings with a final Z
+- [FIX] Fixes ISO parsing and formatting for fractional seconds
+- [FIX] test_fromtimestamp sp
+- [FIX] some typos fixed
+- [FIX] removed an unused import statement
+- [FIX] docs table fix
+- [FIX] Issue with specify 'X' template and no template at all to arrow.get
+- [FIX] Fix "import" typo in docs/index.rst
+- [FIX] Fix unit tests for zero passed
+- [FIX] Update layout.html
+- [FIX] In Norwegian and new Norwegian months and weekdays should not be capitalized
+- [FIX] Fixed discrepancy between specifying 'X' to arrow.get and specifying no template
+
+
+### 0.4.3
+
+- [NEW] Turkish locale (Emre)
+- [NEW] Arabic locale (Mosab Ahmad)
+- [NEW] Danish locale (Holmars)
+- [NEW] Icelandic locale (Holmars)
+- [NEW] Hindi locale (Atmb4u)
+- [NEW] Malayalam locale (Atmb4u)
+- [NEW] Finnish locale (Stormpat)
+- [NEW] Portuguese locale (Danielcorreia)
+- [NEW] ``h`` and ``hh`` strings are now supported (Averyonghub)
+- [FIX] An incorrect inflection in the Polish locale has been fixed (Avalanchy)
+- [FIX] ``arrow.get`` now properly handles ``Date``s (Jaapz)
+- [FIX] Tests are now declared in ``setup.py`` and the manifest (Pypingou)
+- [FIX] ``__version__`` has been added to ``__init__.py`` (Sametmax)
+- [FIX] ISO 8601 strings can be parsed without a separator (Ivandiguisto / Root)
+- [FIX] Documentation is now more clear regarding some inputs on ``arrow.get`` (Eriktaubeneck)
+- [FIX] Some documentation links have been fixed (Vrutsky)
+- [FIX] Error messages for parse errors are now more descriptive (Maciej Albin)
+- [FIX] The parser now correctly checks for separators in strings (Mschwager)
+
+
+### 0.4.2
+
+- [NEW] Factory ``get`` method now accepts a single ``Arrow`` argument.
+- [NEW] Tokens SSSS, SSSSS and SSSSSS are supported in parsing.
+- [NEW] ``Arrow`` objects have a ``float_timestamp`` property.
+- [NEW] Vietnamese locale (Iu1nguoi)
+- [NEW] Factory ``get`` method now accepts a list of format strings (Dgilland)
+- [NEW] A MANIFEST.in file has been added (Pypingou)
+- [NEW] Tests can be run directly from ``setup.py`` (Pypingou)
+- [FIX] Arrow docs now list 'day of week' format tokens correctly (Rudolphfroger)
+- [FIX] Several issues with the Korean locale have been resolved (Yoloseem)
+- [FIX] ``humanize`` now correctly returns unicode (Shvechikov)
+- [FIX] ``Arrow`` objects now pickle / unpickle correctly (Yoloseem)
+
+### 0.4.1
+
+- [NEW] Table / explanation of formatting & parsing tokens in docs
+- [NEW] Brazilian locale (Augusto2112)
+- [NEW] Dutch locale (OrangeTux)
+- [NEW] Italian locale (Pertux)
+- [NEW] Austrain locale (LeChewbacca)
+- [NEW] Tagalog locale (Marksteve)
+- [FIX] Corrected spelling and day numbers in German locale (LeChewbacca)
+- [FIX] Factory ``get`` method should now handle unicode strings correctly (Bwells)
+- [FIX] Midnight and noon should now parse and format correctly (Bwells)
+
+### 0.4.0
+
+- [NEW] Format-free ISO-8601 parsing in factory ``get`` method
+- [NEW] Support for 'week' / 'weeks' in ``span``, ``range``, ``span_range``, ``floor`` and ``ceil``
+- [NEW] Support for 'weeks' in ``replace``
+- [NEW] Norwegian locale (Martinp)
+- [NEW] Japanese locale (CortYuming)
+- [FIX] Timezones no longer show the wrong sign when formatted (Bean)
+- [FIX] Microseconds are parsed correctly from strings (Bsidhom)
+- [FIX] Locale day-of-week is no longer off by one (Cynddl)
+- [FIX] Corrected plurals of Ukrainian and Russian nouns (Catchagain)
+- [CHANGE] Old 0.1 ``arrow`` module method removed
+- [CHANGE] Dropped timestamp support in ``range`` and ``span_range`` (never worked correctly) 
+- [CHANGE] Dropped parsing of single string as tz string in factory ``get`` method (replaced by ISO-8601) 
+
+### 0.3.5
+
+- [NEW] French locale (Cynddl)
+- [NEW] Spanish locale (Slapresta)
+- [FIX] Ranges handle multiple timezones correctly (Ftobia)
+ 
+### 0.3.4
+
+- [FIX] Humanize no longer sometimes returns the wrong month delta
+- [FIX] ``__format__`` works correctly with no format string
+
+### 0.3.3
+
+- [NEW] Python 2.6 support
+- [NEW] Initial support for locale-based parsing and formatting
+- [NEW] ArrowFactory class, now proxied as the module API
+- [NEW] ``factory`` api method to obtain a factory for a custom type
+- [FIX] Python 3 support and tests completely ironed out 
+
+### 0.3.2
+
+- [NEW] Python 3+ support
+
+### 0.3.1
+
+- [FIX] The old ``arrow`` module function handles timestamps correctly as it used to
+
+### 0.3.0
+
+- [NEW] ``Arrow.replace`` method
+- [NEW] Accept timestamps, datetimes and Arrows for datetime inputs, where reasonable
+- [FIX] ``range`` and ``span_range`` respect end and limit parameters correctly
+- [CHANGE] Arrow objects are no longer mutable
+- [CHANGE] Plural attribute name semantics altered: single -> absolute, plural -> relative
+- [CHANGE] Plural names no longer supported as properties (e.g. ``arrow.utcnow().years``)
+
+### 0.2.1
+
+- [NEW] Support for localized humanization
+- [NEW] English, Russian, Greek, Korean, Chinese locales
+
+### 0.2.0
+
+- **REWRITE**
+- [NEW] Date parsing
+- [NEW] Date formatting
+- [NEW] ``floor``, ``ceil`` and ``span`` methods
+- [NEW] ``datetime`` interface implementation 
+- [NEW] ``clone`` method
+- [NEW] ``get``, ``now`` and ``utcnow`` API methods
+
+### 0.1.6
+
+- [NEW] Humanized time deltas
+- [NEW] ``__eq__`` implemented
+- [FIX] Issues with conversions related to daylight savings time resolved
+- [CHANGE] ``__str__`` uses ISO formatting
+
+### 0.1.5
+
+- **Started tracking changes**
+- [NEW] Parsing of ISO-formatted time zone offsets (e.g. '+02:30', '-05:00')
+- [NEW] Resolved some issues with timestamps and delta / Olson time zones
+
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2013 Chris Smith
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/MANIFEST.in
@@ -0,0 +1,3 @@
+include LICENSE HISTORY.md README.rst
+recursive-include tests *
+recursive-include docs *
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/PKG-INFO
@@ -0,0 +1,121 @@
+Metadata-Version: 1.1
+Name: arrow
+Version: 0.10.0
+Summary: Better dates and times for Python
+Home-page: https://github.com/crsmithdev/arrow/
+Author: Chris Smith
+Author-email: crsmithdev@gmail.com
+License: Apache 2.0
+Description: Arrow - Better dates & times for Python
+        =======================================
+        
+        .. image:: https://travis-ci.org/crsmithdev/arrow.svg
+           :alt: build status
+           :target: https://travis-ci.org/crsmithdev/arrow
+        
+        .. image:: https://codecov.io/github/crsmithdev/arrow/coverage.svg?branch=master
+           :target: https://codecov.io/github/crsmithdev/arrow
+           :alt: Codecov
+        
+        .. image:: https://img.shields.io/pypi/v/arrow.svg
+           :target: https://pypi.python.org/pypi/arrow
+           :alt: downloads
+                
+        Documentation: `arrow.readthedocs.org <http://arrow.readthedocs.org/en/latest/>`_
+        ---------------------------------------------------------------------------------
+        
+        What?
+        -----
+        
+        Arrow is a Python library that offers a sensible, human-friendly approach to creating, manipulating, formatting and converting dates, times, and timestamps.  It implements and updates the datetime type, plugging gaps in functionality, and provides an intelligent module API that supports many common creation scenarios.  Simply put, it helps you work with dates and times with fewer imports and a lot less code.
+        
+        Arrow is heavily inspired by `moment.js <https://github.com/timrwood/moment>`_ and `requests <https://github.com/kennethreitz/requests>`_
+        
+        Why?
+        ----
+        
+        Python's standard library and some other low-level modules have near-complete date, time and time zone functionality but don't work very well from a usability perspective:
+        
+        - Too many modules:  datetime, time, calendar, dateutil, pytz and more
+        - Too many types:  date, time, datetime, tzinfo, timedelta, relativedelta, etc.
+        - Time zones and timestamp conversions are verbose and unpleasant 
+        - Time zone naievety is the norm
+        - Gaps in functionality:  ISO-8601 parsing, timespans, humanization
+        
+        Features 
+        --------
+        
+        - Fully implemented, drop-in replacement for datetime 
+        - Supports Python 2.6, 2.7, 3.3, 3.4 and 3.5
+        - Time zone-aware & UTC by default
+        - Provides super-simple creation options for many common input scenarios
+        - Updated .replace method with support for relative offsets, including weeks
+        - Formats and parses strings automatically
+        - Partial support for ISO-8601
+        - Timezone conversion
+        - Timestamp available as a property
+        - Generates time spans, ranges, floors and ceilings in timeframes from year to microsecond
+        - Humanizes and supports a growing list of contributed locales
+        - Extensible for your own Arrow-derived types
+        
+        Quick start
+        -----------
+        
+        First:
+        
+        .. code-block:: console
+        
+            $ pip install arrow
+        
+        And then:
+        
+        .. code-block:: pycon
+        
+            >>> import arrow
+            >>> utc = arrow.utcnow()
+            >>> utc
+            <Arrow [2013-05-11T21:23:58.970460+00:00]>
+        
+            >>> utc = utc.replace(hours=-1)
+            >>> utc
+            <Arrow [2013-05-11T20:23:58.970460+00:00]>
+        
+            >>> local = utc.to('US/Pacific')
+            >>> local
+            <Arrow [2013-05-11T13:23:58.970460-07:00]>
+        
+            >>> arrow.get('2013-05-11T21:23:58.970460+00:00')
+            <Arrow [2013-05-11T21:23:58.970460+00:00]>
+        
+            >>> local.timestamp
+            1368303838
+        
+            >>> local.format()
+            '2013-05-11 13:23:58 -07:00'
+        
+            >>> local.format('YYYY-MM-DD HH:mm:ss ZZ')
+            '2013-05-11 13:23:58 -07:00'
+        
+            >>> local.humanize()
+            'an hour ago'
+        
+            >>> local.humanize(locale='ko_kr')
+            '1시간 전'
+            
+        Further documentation can be found at `arrow.readthedocs.org <http://arrow.readthedocs.org/en/latest/>`_
+        
+        Contributing
+        ------------
+        
+        Contributions are welcome, especially with localization.  See `locales.py <https://github.com/crsmithdev/arrow/blob/master/arrow/locales.py>`_ for what's currently supported.
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/README.rst
@@ -0,0 +1,102 @@
+Arrow - Better dates & times for Python
+=======================================
+
+.. image:: https://travis-ci.org/crsmithdev/arrow.svg
+   :alt: build status
+   :target: https://travis-ci.org/crsmithdev/arrow
+
+.. image:: https://codecov.io/github/crsmithdev/arrow/coverage.svg?branch=master
+   :target: https://codecov.io/github/crsmithdev/arrow
+   :alt: Codecov
+
+.. image:: https://img.shields.io/pypi/v/arrow.svg
+   :target: https://pypi.python.org/pypi/arrow
+   :alt: downloads
+        
+Documentation: `arrow.readthedocs.org <http://arrow.readthedocs.org/en/latest/>`_
+---------------------------------------------------------------------------------
+
+What?
+-----
+
+Arrow is a Python library that offers a sensible, human-friendly approach to creating, manipulating, formatting and converting dates, times, and timestamps.  It implements and updates the datetime type, plugging gaps in functionality, and provides an intelligent module API that supports many common creation scenarios.  Simply put, it helps you work with dates and times with fewer imports and a lot less code.
+
+Arrow is heavily inspired by `moment.js <https://github.com/timrwood/moment>`_ and `requests <https://github.com/kennethreitz/requests>`_
+
+Why?
+----
+
+Python's standard library and some other low-level modules have near-complete date, time and time zone functionality but don't work very well from a usability perspective:
+
+- Too many modules:  datetime, time, calendar, dateutil, pytz and more
+- Too many types:  date, time, datetime, tzinfo, timedelta, relativedelta, etc.
+- Time zones and timestamp conversions are verbose and unpleasant 
+- Time zone naievety is the norm
+- Gaps in functionality:  ISO-8601 parsing, timespans, humanization
+
+Features 
+--------
+
+- Fully implemented, drop-in replacement for datetime 
+- Supports Python 2.6, 2.7, 3.3, 3.4 and 3.5
+- Time zone-aware & UTC by default
+- Provides super-simple creation options for many common input scenarios
+- Updated .replace method with support for relative offsets, including weeks
+- Formats and parses strings automatically
+- Partial support for ISO-8601
+- Timezone conversion
+- Timestamp available as a property
+- Generates time spans, ranges, floors and ceilings in timeframes from year to microsecond
+- Humanizes and supports a growing list of contributed locales
+- Extensible for your own Arrow-derived types
+
+Quick start
+-----------
+
+First:
+
+.. code-block:: console
+
+    $ pip install arrow
+
+And then:
+
+.. code-block:: pycon
+
+    >>> import arrow
+    >>> utc = arrow.utcnow()
+    >>> utc
+    <Arrow [2013-05-11T21:23:58.970460+00:00]>
+
+    >>> utc = utc.replace(hours=-1)
+    >>> utc
+    <Arrow [2013-05-11T20:23:58.970460+00:00]>
+
+    >>> local = utc.to('US/Pacific')
+    >>> local
+    <Arrow [2013-05-11T13:23:58.970460-07:00]>
+
+    >>> arrow.get('2013-05-11T21:23:58.970460+00:00')
+    <Arrow [2013-05-11T21:23:58.970460+00:00]>
+
+    >>> local.timestamp
+    1368303838
+
+    >>> local.format()
+    '2013-05-11 13:23:58 -07:00'
+
+    >>> local.format('YYYY-MM-DD HH:mm:ss ZZ')
+    '2013-05-11 13:23:58 -07:00'
+
+    >>> local.humanize()
+    'an hour ago'
+
+    >>> local.humanize(locale='ko_kr')
+    '1시간 전'
+    
+Further documentation can be found at `arrow.readthedocs.org <http://arrow.readthedocs.org/en/latest/>`_
+
+Contributing
+------------
+
+Contributions are welcome, especially with localization.  See `locales.py <https://github.com/crsmithdev/arrow/blob/master/arrow/locales.py>`_ for what's currently supported.
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow.egg-info/PKG-INFO
@@ -0,0 +1,121 @@
+Metadata-Version: 1.1
+Name: arrow
+Version: 0.10.0
+Summary: Better dates and times for Python
+Home-page: https://github.com/crsmithdev/arrow/
+Author: Chris Smith
+Author-email: crsmithdev@gmail.com
+License: Apache 2.0
+Description: Arrow - Better dates & times for Python
+        =======================================
+        
+        .. image:: https://travis-ci.org/crsmithdev/arrow.svg
+           :alt: build status
+           :target: https://travis-ci.org/crsmithdev/arrow
+        
+        .. image:: https://codecov.io/github/crsmithdev/arrow/coverage.svg?branch=master
+           :target: https://codecov.io/github/crsmithdev/arrow
+           :alt: Codecov
+        
+        .. image:: https://img.shields.io/pypi/v/arrow.svg
+           :target: https://pypi.python.org/pypi/arrow
+           :alt: downloads
+                
+        Documentation: `arrow.readthedocs.org <http://arrow.readthedocs.org/en/latest/>`_
+        ---------------------------------------------------------------------------------
+        
+        What?
+        -----
+        
+        Arrow is a Python library that offers a sensible, human-friendly approach to creating, manipulating, formatting and converting dates, times, and timestamps.  It implements and updates the datetime type, plugging gaps in functionality, and provides an intelligent module API that supports many common creation scenarios.  Simply put, it helps you work with dates and times with fewer imports and a lot less code.
+        
+        Arrow is heavily inspired by `moment.js <https://github.com/timrwood/moment>`_ and `requests <https://github.com/kennethreitz/requests>`_
+        
+        Why?
+        ----
+        
+        Python's standard library and some other low-level modules have near-complete date, time and time zone functionality but don't work very well from a usability perspective:
+        
+        - Too many modules:  datetime, time, calendar, dateutil, pytz and more
+        - Too many types:  date, time, datetime, tzinfo, timedelta, relativedelta, etc.
+        - Time zones and timestamp conversions are verbose and unpleasant 
+        - Time zone naievety is the norm
+        - Gaps in functionality:  ISO-8601 parsing, timespans, humanization
+        
+        Features 
+        --------
+        
+        - Fully implemented, drop-in replacement for datetime 
+        - Supports Python 2.6, 2.7, 3.3, 3.4 and 3.5
+        - Time zone-aware & UTC by default
+        - Provides super-simple creation options for many common input scenarios
+        - Updated .replace method with support for relative offsets, including weeks
+        - Formats and parses strings automatically
+        - Partial support for ISO-8601
+        - Timezone conversion
+        - Timestamp available as a property
+        - Generates time spans, ranges, floors and ceilings in timeframes from year to microsecond
+        - Humanizes and supports a growing list of contributed locales
+        - Extensible for your own Arrow-derived types
+        
+        Quick start
+        -----------
+        
+        First:
+        
+        .. code-block:: console
+        
+            $ pip install arrow
+        
+        And then:
+        
+        .. code-block:: pycon
+        
+            >>> import arrow
+            >>> utc = arrow.utcnow()
+            >>> utc
+            <Arrow [2013-05-11T21:23:58.970460+00:00]>
+        
+            >>> utc = utc.replace(hours=-1)
+            >>> utc
+            <Arrow [2013-05-11T20:23:58.970460+00:00]>
+        
+            >>> local = utc.to('US/Pacific')
+            >>> local
+            <Arrow [2013-05-11T13:23:58.970460-07:00]>
+        
+            >>> arrow.get('2013-05-11T21:23:58.970460+00:00')
+            <Arrow [2013-05-11T21:23:58.970460+00:00]>
+        
+            >>> local.timestamp
+            1368303838
+        
+            >>> local.format()
+            '2013-05-11 13:23:58 -07:00'
+        
+            >>> local.format('YYYY-MM-DD HH:mm:ss ZZ')
+            '2013-05-11 13:23:58 -07:00'
+        
+            >>> local.humanize()
+            'an hour ago'
+        
+            >>> local.humanize(locale='ko_kr')
+            '1시간 전'
+            
+        Further documentation can be found at `arrow.readthedocs.org <http://arrow.readthedocs.org/en/latest/>`_
+        
+        Contributing
+        ------------
+        
+        Contributions are welcome, especially with localization.  See `locales.py <https://github.com/crsmithdev/arrow/blob/master/arrow/locales.py>`_ for what's currently supported.
+        
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow.egg-info/SOURCES.txt
@@ -0,0 +1,47 @@
+HISTORY.md
+LICENSE
+MANIFEST.in
+README.rst
+setup.cfg
+setup.py
+arrow/__init__.py
+arrow/api.py
+arrow/arrow.py
+arrow/factory.py
+arrow/formatter.py
+arrow/locales.py
+arrow/parser.py
+arrow/util.py
+arrow.egg-info/PKG-INFO
+arrow.egg-info/SOURCES.txt
+arrow.egg-info/dependency_links.txt
+arrow.egg-info/not-zip-safe
+arrow.egg-info/requires.txt
+arrow.egg-info/top_level.txt
+docs/Makefile
+docs/conf.py
+docs/index.rst
+docs/_themes/COPYING.txt
+docs/_themes/README.rst
+docs/_themes/f6/NEWS.txt
+docs/_themes/f6/README.rst
+docs/_themes/f6/layout.html
+docs/_themes/f6/theme.conf
+docs/_themes/f6/static/brillant.png
+docs/_themes/f6/static/f6.css
+tests/__init__.py
+tests/__init__.pyc
+tests/api_tests.py
+tests/api_tests.pyc
+tests/arrow_tests.py
+tests/arrow_tests.pyc
+tests/factory_tests.py
+tests/factory_tests.pyc
+tests/formatter_tests.py
+tests/formatter_tests.pyc
+tests/locales_tests.py
+tests/locales_tests.pyc
+tests/parser_tests.py
+tests/parser_tests.pyc
+tests/util_tests.py
+tests/util_tests.pyc
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow.egg-info/dependency_links.txt
@@ -0,0 +1,1 @@
+
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow.egg-info/not-zip-safe
@@ -0,0 +1,1 @@
+
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow.egg-info/requires.txt
@@ -0,0 +1,1 @@
+python-dateutil
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow.egg-info/top_level.txt
@@ -0,0 +1,1 @@
+arrow
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+from .arrow import Arrow
+from .factory import ArrowFactory
+from .api import get, now, utcnow
+
+__version__ = '0.10.0'
+VERSION = __version__
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow/api.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+'''
+Provides the default implementation of :class:`ArrowFactory <arrow.factory.ArrowFactory>`
+methods for use as a module API.
+
+'''
+
+from __future__ import absolute_import
+
+from arrow.factory import ArrowFactory
+
+
+# internal default factory.
+_factory = ArrowFactory()
+
+
+def get(*args, **kwargs):
+    ''' Implements the default :class:`ArrowFactory <arrow.factory.ArrowFactory>`
+    ``get`` method.
+
+    '''
+
+    return _factory.get(*args, **kwargs)
+
+def utcnow():
+    ''' Implements the default :class:`ArrowFactory <arrow.factory.ArrowFactory>`
+    ``utcnow`` method.
+
+    '''
+
+    return _factory.utcnow()
+
+
+def now(tz=None):
+    ''' Implements the default :class:`ArrowFactory <arrow.factory.ArrowFactory>`
+    ``now`` method.
+
+    '''
+
+    return _factory.now(tz)
+
+
+def factory(type):
+    ''' Returns an :class:`.ArrowFactory` for the specified :class:`Arrow <arrow.arrow.Arrow>`
+    or derived type.
+
+    :param type: the type, :class:`Arrow <arrow.arrow.Arrow>` or derived.
+
+    '''
+
+    return ArrowFactory(type)
+
+
+__all__ = ['get', 'utcnow', 'now', 'factory']
+
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow/arrow.py
@@ -0,0 +1,948 @@
+# -*- coding: utf-8 -*-
+'''
+Provides the :class:`Arrow <arrow.arrow.Arrow>` class, an enhanced ``datetime``
+replacement.
+
+'''
+
+from __future__ import absolute_import
+
+from datetime import datetime, timedelta, tzinfo
+from dateutil import tz as dateutil_tz
+from dateutil.relativedelta import relativedelta
+import calendar
+import sys
+import warnings
+
+
+from arrow import util, locales, parser, formatter
+
+
+class Arrow(object):
+    '''An :class:`Arrow <arrow.arrow.Arrow>` object.
+
+    Implements the ``datetime`` interface, behaving as an aware ``datetime`` while implementing
+    additional functionality.
+
+    :param year: the calendar year.
+    :param month: the calendar month.
+    :param day: the calendar day.
+    :param hour: (optional) the hour. Defaults to 0.
+    :param minute: (optional) the minute, Defaults to 0.
+    :param second: (optional) the second, Defaults to 0.
+    :param microsecond: (optional) the microsecond. Defaults 0.
+    :param tzinfo: (optional) the ``tzinfo`` object.  Defaults to ``None``.
+
+    If tzinfo is None, it is assumed to be UTC on creation.
+
+    Usage::
+
+        >>> import arrow
+        >>> arrow.Arrow(2013, 5, 5, 12, 30, 45)
+        <Arrow [2013-05-05T12:30:45+00:00]>
+
+    '''
+
+    resolution = datetime.resolution
+
+    _ATTRS = ['year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond']
+    _ATTRS_PLURAL = ['{0}s'.format(a) for a in _ATTRS]
+    _MONTHS_PER_QUARTER = 3
+
+    def __init__(self, year, month, day, hour=0, minute=0, second=0, microsecond=0,
+                 tzinfo=None):
+
+        if util.isstr(tzinfo):
+            tzinfo = parser.TzinfoParser.parse(tzinfo)
+        tzinfo = tzinfo or dateutil_tz.tzutc()
+
+        self._datetime = datetime(year, month, day, hour, minute, second,
+            microsecond, tzinfo)
+
+
+    # factories: single object, both original and from datetime.
+
+    @classmethod
+    def now(cls, tzinfo=None):
+        '''Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now".
+
+        :param tzinfo: (optional) a ``tzinfo`` object. Defaults to local time.
+
+        '''
+
+        utc = datetime.utcnow().replace(tzinfo=dateutil_tz.tzutc())
+        dt = utc.astimezone(dateutil_tz.tzlocal() if tzinfo is None else tzinfo)
+
+        return cls(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
+            dt.microsecond, dt.tzinfo)
+
+    @classmethod
+    def utcnow(cls):
+        ''' Constructs an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC
+        time.
+
+        '''
+
+        dt = datetime.utcnow()
+
+        return cls(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
+            dt.microsecond, dateutil_tz.tzutc())
+
+    @classmethod
+    def fromtimestamp(cls, timestamp, tzinfo=None):
+        ''' Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp.
+
+        :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
+        :param tzinfo: (optional) a ``tzinfo`` object.  Defaults to local time.
+
+        '''
+
+        tzinfo = tzinfo or dateutil_tz.tzlocal()
+        timestamp = cls._get_timestamp_from_input(timestamp)
+        dt = datetime.fromtimestamp(timestamp, tzinfo)
+
+        return cls(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
+            dt.microsecond, tzinfo)
+
+    @classmethod
+    def utcfromtimestamp(cls, timestamp):
+        '''Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a timestamp, in UTC time.
+
+        :param timestamp: an ``int`` or ``float`` timestamp, or a ``str`` that converts to either.
+
+        '''
+
+        timestamp = cls._get_timestamp_from_input(timestamp)
+        dt = datetime.utcfromtimestamp(timestamp)
+
+        return cls(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
+            dt.microsecond, dateutil_tz.tzutc())
+
+    @classmethod
+    def fromdatetime(cls, dt, tzinfo=None):
+        ''' Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``datetime`` and optional
+        ``tzinfo`` object.
+
+        :param dt: the ``datetime``
+        :param tzinfo: (optional) a ``tzinfo`` object.  Defaults to UTC.
+
+        '''
+
+        tzinfo = tzinfo or dt.tzinfo or dateutil_tz.tzutc()
+
+        return cls(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
+            dt.microsecond, tzinfo)
+
+    @classmethod
+    def fromdate(cls, date, tzinfo=None):
+        ''' Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a ``date`` and optional
+        ``tzinfo`` object.  Time values are set to 0.
+
+        :param date: the ``date``
+        :param tzinfo: (optional) a ``tzinfo`` object.  Defaults to UTC.
+        '''
+
+        tzinfo = tzinfo or dateutil_tz.tzutc()
+
+        return cls(date.year, date.month, date.day, tzinfo=tzinfo)
+
+    @classmethod
+    def strptime(cls, date_str, fmt, tzinfo=None):
+        ''' Constructs an :class:`Arrow <arrow.arrow.Arrow>` object from a date string and format,
+        in the style of ``datetime.strptime``.
+
+        :param date_str: the date string.
+        :param fmt: the format string.
+        :param tzinfo: (optional) an optional ``tzinfo``
+        '''
+
+        dt = datetime.strptime(date_str, fmt)
+        tzinfo = tzinfo or dt.tzinfo
+
+        return cls(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
+            dt.microsecond, tzinfo)
+
+
+    # factories: ranges and spans
+
+    @classmethod
+    def range(cls, frame, start, end=None, tz=None, limit=None):
+        ''' Returns an array of :class:`Arrow <arrow.arrow.Arrow>` objects, representing
+        an iteration of time between two inputs.
+
+        :param frame: the timeframe.  Can be any ``datetime`` property (day, hour, minute...).
+        :param start: A datetime expression, the start of the range.
+        :param end: (optional) A datetime expression, the end of the range.
+        :param tz: (optional) A timezone expression.  Defaults to UTC.
+        :param limit: (optional) A maximum number of tuples to return.
+
+        **NOTE**: the **end** or **limit** must be provided.  Call with **end** alone to
+        return the entire range, with **limit** alone to return a maximum # of results from the
+        start, and with both to cap a range at a maximum # of results.
+
+        Supported frame values: year, quarter, month, week, day, hour, minute, second
+
+        Recognized datetime expressions:
+
+            - An :class:`Arrow <arrow.arrow.Arrow>` object.
+            - A ``datetime`` object.
+
+        Recognized timezone expressions:
+
+            - A ``tzinfo`` object.
+            - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
+            - A ``str`` in ISO-8601 style, as in '+07:00'.
+            - A ``str``, one of the following:  'local', 'utc', 'UTC'.
+
+        Usage:
+
+            >>> start = datetime(2013, 5, 5, 12, 30)
+            >>> end = datetime(2013, 5, 5, 17, 15)
+            >>> for r in arrow.Arrow.range('hour', start, end):
+            ...     print repr(r)
+            ...
+            <Arrow [2013-05-05T12:30:00+00:00]>
+            <Arrow [2013-05-05T13:30:00+00:00]>
+            <Arrow [2013-05-05T14:30:00+00:00]>
+            <Arrow [2013-05-05T15:30:00+00:00]>
+            <Arrow [2013-05-05T16:30:00+00:00]>
+
+        '''
+
+        _, frame_relative, relative_steps = cls._get_frames(frame)
+
+        tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
+
+        start = cls._get_datetime(start).replace(tzinfo=tzinfo)
+        end, limit = cls._get_iteration_params(end, limit)
+        end = cls._get_datetime(end).replace(tzinfo=tzinfo)
+
+        current = cls.fromdatetime(start)
+        results = []
+
+        while current <= end and len(results) < limit:
+            results.append(current)
+
+            values = [getattr(current, f) for f in cls._ATTRS]
+            current = cls(*values, tzinfo=tzinfo) + relativedelta(**{frame_relative: relative_steps})
+
+        return results
+
+
+    @classmethod
+    def span_range(cls, frame, start, end, tz=None, limit=None):
+        ''' Returns an array of tuples, each :class:`Arrow <arrow.arrow.Arrow>` objects,
+        representing a series of timespans between two inputs.
+
+        :param frame: the timeframe.  Can be any ``datetime`` property (day, hour, minute...).
+        :param start: A datetime expression, the start of the range.
+        :param end: (optional) A datetime expression, the end of the range.
+        :param tz: (optional) A timezone expression.  Defaults to UTC.
+        :param limit: (optional) A maximum number of tuples to return.
+
+        **NOTE**: the **end** or **limit** must be provided.  Call with **end** alone to
+        return the entire range, with **limit** alone to return a maximum # of results from the
+        start, and with both to cap a range at a maximum # of results.
+
+        Supported frame values: year, quarter, month, week, day, hour, minute, second
+
+        Recognized datetime expressions:
+
+            - An :class:`Arrow <arrow.arrow.Arrow>` object.
+            - A ``datetime`` object.
+
+        Recognized timezone expressions:
+
+            - A ``tzinfo`` object.
+            - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
+            - A ``str`` in ISO-8601 style, as in '+07:00'.
+            - A ``str``, one of the following:  'local', 'utc', 'UTC'.
+
+        Usage:
+
+            >>> start = datetime(2013, 5, 5, 12, 30)
+            >>> end = datetime(2013, 5, 5, 17, 15)
+            >>> for r in arrow.Arrow.span_range('hour', start, end):
+            ...     print r
+            ...
+            (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T12:59:59.999999+00:00]>)
+            (<Arrow [2013-05-05T13:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
+            (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T14:59:59.999999+00:00]>)
+            (<Arrow [2013-05-05T15:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
+            (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T16:59:59.999999+00:00]>)
+
+        '''
+        tzinfo = cls._get_tzinfo(start.tzinfo if tz is None else tz)
+        start = cls.fromdatetime(start, tzinfo).span(frame)[0]
+        _range = cls.range(frame, start, end, tz, limit)
+        return [r.span(frame) for r in _range]
+
+
+    # representations
+
+    def __repr__(self):
+
+        dt = self._datetime
+        attrs = ', '.join([str(i) for i in [dt.year, dt.month, dt.day, dt.hour, dt.minute,
+            dt.second, dt.microsecond]])
+
+        return '<{0} [{1}]>'.format(self.__class__.__name__, self.__str__())
+
+    def __str__(self):
+        return self._datetime.isoformat()
+
+    def __format__(self, formatstr):
+
+        if len(formatstr) > 0:
+            return self.format(formatstr)
+
+        return str(self)
+
+    def __hash__(self):
+        return self._datetime.__hash__()
+
+
+    # attributes & properties
+
+    def __getattr__(self, name):
+
+        if name == 'week':
+            return self.isocalendar()[1]
+
+        if name == 'quarter':
+            return int((self.month-1)/self._MONTHS_PER_QUARTER) + 1
+
+        if not name.startswith('_'):
+            value = getattr(self._datetime, name, None)
+
+            if value is not None:
+                return value
+
+        return object.__getattribute__(self, name)
+
+    @property
+    def tzinfo(self):
+        ''' Gets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object. '''
+
+        return self._datetime.tzinfo
+
+    @tzinfo.setter
+    def tzinfo(self, tzinfo):
+        ''' Sets the ``tzinfo`` of the :class:`Arrow <arrow.arrow.Arrow>` object. '''
+
+        self._datetime = self._datetime.replace(tzinfo=tzinfo)
+
+    @property
+    def datetime(self):
+        ''' Returns a datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object. '''
+
+        return self._datetime
+
+    @property
+    def naive(self):
+        ''' Returns a naive datetime representation of the :class:`Arrow <arrow.arrow.Arrow>` object. '''
+
+        return self._datetime.replace(tzinfo=None)
+
+    @property
+    def timestamp(self):
+        ''' Returns a timestamp representation of the :class:`Arrow <arrow.arrow.Arrow>` object. '''
+
+        return calendar.timegm(self._datetime.utctimetuple())
+
+    @property
+    def float_timestamp(self):
+        ''' Returns a floating-point representation of the :class:`Arrow <arrow.arrow.Arrow>` object. '''
+
+        return self.timestamp + float(self.microsecond) / 1000000
+
+
+    # mutation and duplication.
+
+    def clone(self):
+        ''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, cloned from the current one.
+
+        Usage:
+
+            >>> arw = arrow.utcnow()
+            >>> cloned = arw.clone()
+
+        '''
+
+        return self.fromdatetime(self._datetime)
+
+    def replace(self, **kwargs):
+        ''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
+        according to inputs.
+
+        Use single property names to set their value absolutely:
+
+        >>> import arrow
+        >>> arw = arrow.utcnow()
+        >>> arw
+        <Arrow [2013-05-11T22:27:34.787885+00:00]>
+        >>> arw.replace(year=2014, month=6)
+        <Arrow [2014-06-11T22:27:34.787885+00:00]>
+
+        You can also provide a timezone expression can also be replaced:
+
+        >>> arw.replace(tzinfo=tz.tzlocal())
+        <Arrow [2013-05-11T22:27:34.787885-07:00]>
+
+       Use plural property names to shift their current value relatively (**deprecated**):
+
+       >>> arw.replace(years=1, months=-1)
+       <Arrow [2014-04-11T22:27:34.787885+00:00]>
+
+        Recognized timezone expressions:
+
+            - A ``tzinfo`` object.
+            - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
+            - A ``str`` in ISO-8601 style, as in '+07:00'.
+            - A ``str``, one of the following:  'local', 'utc', 'UTC'.
+
+        '''
+
+        absolute_kwargs = {}
+        relative_kwargs = {}  # TODO: DEPRECATED; remove in next release
+
+        for key, value in kwargs.items():
+
+            if key in self._ATTRS:
+                absolute_kwargs[key] = value
+            elif key in self._ATTRS_PLURAL or key in ['weeks', 'quarters']:
+                # TODO: DEPRECATED
+                warnings.warn("replace() with plural property to shift value"
+                              "is deprecated, use shift() instead",
+                              DeprecationWarning)
+                relative_kwargs[key] = value
+            elif key in ['week', 'quarter']:
+                raise AttributeError('setting absolute {0} is not supported'.format(key))
+            elif key !='tzinfo':
+                raise AttributeError('unknown attribute: "{0}"'.format(key))
+
+        # core datetime does not support quarters, translate to months.
+        relative_kwargs.setdefault('months', 0)
+        relative_kwargs['months'] += relative_kwargs.pop('quarters', 0) * self._MONTHS_PER_QUARTER
+
+        current = self._datetime.replace(**absolute_kwargs)
+        current += relativedelta(**relative_kwargs) # TODO: DEPRECATED
+
+        tzinfo = kwargs.get('tzinfo')
+
+        if tzinfo is not None:
+            tzinfo = self._get_tzinfo(tzinfo)
+            current = current.replace(tzinfo=tzinfo)
+
+        return self.fromdatetime(current)
+
+    def shift(self, **kwargs):
+        ''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object with attributes updated
+        according to inputs.
+
+        Use plural property names to shift their current value relatively:
+
+        >>> import arrow
+        >>> arw = arrow.utcnow()
+        >>> arw
+        <Arrow [2013-05-11T22:27:34.787885+00:00]>
+        >>> arw.shift(years=1, months=-1)
+        <Arrow [2014-04-11T22:27:34.787885+00:00]>
+
+        '''
+
+        relative_kwargs = {}
+
+        for key, value in kwargs.items():
+
+            if key in self._ATTRS_PLURAL or key in ['weeks', 'quarters']:
+                relative_kwargs[key] = value
+            else:
+                raise AttributeError()
+
+        # core datetime does not support quarters, translate to months.
+        relative_kwargs.setdefault('months', 0)
+        relative_kwargs['months'] += relative_kwargs.pop('quarters', 0) * self._MONTHS_PER_QUARTER
+
+        current = self._datetime + relativedelta(**relative_kwargs)
+
+        return self.fromdatetime(current)
+
+    def to(self, tz):
+        ''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, converted
+        to the target timezone.
+
+        :param tz: an expression representing a timezone.
+
+        Recognized timezone expressions:
+
+            - A ``tzinfo`` object.
+            - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
+            - A ``str`` in ISO-8601 style, as in '+07:00'.
+            - A ``str``, one of the following:  'local', 'utc', 'UTC'.
+
+        Usage::
+
+            >>> utc = arrow.utcnow()
+            >>> utc
+            <Arrow [2013-05-09T03:49:12.311072+00:00]>
+
+            >>> utc.to('US/Pacific')
+            <Arrow [2013-05-08T20:49:12.311072-07:00]>
+
+            >>> utc.to(tz.tzlocal())
+            <Arrow [2013-05-08T20:49:12.311072-07:00]>
+
+            >>> utc.to('-07:00')
+            <Arrow [2013-05-08T20:49:12.311072-07:00]>
+
+            >>> utc.to('local')
+            <Arrow [2013-05-08T20:49:12.311072-07:00]>
+
+            >>> utc.to('local').to('utc')
+            <Arrow [2013-05-09T03:49:12.311072+00:00]>
+
+        '''
+
+        if not isinstance(tz, tzinfo):
+            tz = parser.TzinfoParser.parse(tz)
+
+        dt = self._datetime.astimezone(tz)
+
+        return self.__class__(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second,
+            dt.microsecond, dt.tzinfo)
+
+    def span(self, frame, count=1):
+        ''' Returns two new :class:`Arrow <arrow.arrow.Arrow>` objects, representing the timespan
+        of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
+
+        :param frame: the timeframe.  Can be any ``datetime`` property (day, hour, minute...).
+        :param count: (optional) the number of frames to span.
+
+        Supported frame values: year, quarter, month, week, day, hour, minute, second
+
+        Usage::
+
+            >>> arrow.utcnow()
+            <Arrow [2013-05-09T03:32:36.186203+00:00]>
+
+            >>> arrow.utcnow().span('hour')
+            (<Arrow [2013-05-09T03:00:00+00:00]>, <Arrow [2013-05-09T03:59:59.999999+00:00]>)
+
+            >>> arrow.utcnow().span('day')
+            (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-09T23:59:59.999999+00:00]>)
+
+            >>> arrow.utcnow().span('day', count=2)
+            (<Arrow [2013-05-09T00:00:00+00:00]>, <Arrow [2013-05-10T23:59:59.999999+00:00]>)
+
+        '''
+
+        frame_absolute, frame_relative, relative_steps = self._get_frames(frame)
+
+        if frame_absolute == 'week':
+            attr = 'day'
+        elif frame_absolute == 'quarter':
+            attr = 'month'
+        else:
+            attr = frame_absolute
+
+        index = self._ATTRS.index(attr)
+        frames = self._ATTRS[:index + 1]
+
+        values = [getattr(self, f) for f in frames]
+
+        for i in range(3 - len(values)):
+            values.append(1)
+
+        floor = self.__class__(*values, tzinfo=self.tzinfo)
+
+        if frame_absolute == 'week':
+            floor = floor + relativedelta(days=-(self.isoweekday() - 1))
+        elif frame_absolute == 'quarter':
+            floor = floor + relativedelta(months=-((self.month - 1) % 3))
+
+        ceil = floor + relativedelta(
+            **{frame_relative: count * relative_steps}) + relativedelta(microseconds=-1)
+
+        return floor, ceil
+
+    def floor(self, frame):
+        ''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "floor"
+        of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
+        Equivalent to the first element in the 2-tuple returned by
+        :func:`span <arrow.arrow.Arrow.span>`.
+
+        :param frame: the timeframe.  Can be any ``datetime`` property (day, hour, minute...).
+
+        Usage::
+
+            >>> arrow.utcnow().floor('hour')
+            <Arrow [2013-05-09T03:00:00+00:00]>
+        '''
+
+        return self.span(frame)[0]
+
+    def ceil(self, frame):
+        ''' Returns a new :class:`Arrow <arrow.arrow.Arrow>` object, representing the "ceiling"
+        of the timespan of the :class:`Arrow <arrow.arrow.Arrow>` object in a given timeframe.
+        Equivalent to the second element in the 2-tuple returned by
+        :func:`span <arrow.arrow.Arrow.span>`.
+
+        :param frame: the timeframe.  Can be any ``datetime`` property (day, hour, minute...).
+
+        Usage::
+
+            >>> arrow.utcnow().ceil('hour')
+            <Arrow [2013-05-09T03:59:59.999999+00:00]>
+        '''
+
+        return self.span(frame)[1]
+
+
+    # string output and formatting.
+
+    def format(self, fmt='YYYY-MM-DD HH:mm:ssZZ', locale='en_us'):
+        ''' Returns a string representation of the :class:`Arrow <arrow.arrow.Arrow>` object,
+        formatted according to a format string.
+
+        :param fmt: the format string.
+
+        Usage::
+
+            >>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ')
+            '2013-05-09 03:56:47 -00:00'
+
+            >>> arrow.utcnow().format('X')
+            '1368071882'
+
+            >>> arrow.utcnow().format('MMMM DD, YYYY')
+            'May 09, 2013'
+
+            >>> arrow.utcnow().format()
+            '2013-05-09 03:56:47 -00:00'
+
+        '''
+
+        return formatter.DateTimeFormatter(locale).format(self._datetime, fmt)
+
+
+    def humanize(self, other=None, locale='en_us', only_distance=False):
+        ''' Returns a localized, humanized representation of a relative difference in time.
+
+        :param other: (optional) an :class:`Arrow <arrow.arrow.Arrow>` or ``datetime`` object.
+            Defaults to now in the current :class:`Arrow <arrow.arrow.Arrow>` object's timezone.
+        :param locale: (optional) a ``str`` specifying a locale.  Defaults to 'en_us'.
+        :param only_distance: (optional) returns only time difference eg: "11 seconds" without "in" or "ago" part.
+
+        Usage::
+
+            >>> earlier = arrow.utcnow().replace(hours=-2)
+            >>> earlier.humanize()
+            '2 hours ago'
+
+            >>> later = later = earlier.replace(hours=4)
+            >>> later.humanize(earlier)
+            'in 4 hours'
+
+        '''
+
+        locale = locales.get_locale(locale)
+
+        if other is None:
+            utc = datetime.utcnow().replace(tzinfo=dateutil_tz.tzutc())
+            dt = utc.astimezone(self._datetime.tzinfo)
+
+        elif isinstance(other, Arrow):
+            dt = other._datetime
+
+        elif isinstance(other, datetime):
+            if other.tzinfo is None:
+                dt = other.replace(tzinfo=self._datetime.tzinfo)
+            else:
+                dt = other.astimezone(self._datetime.tzinfo)
+
+        else:
+            raise TypeError()
+
+        delta = int(util.total_seconds(self._datetime - dt))
+        sign = -1 if delta < 0 else 1
+        diff = abs(delta)
+        delta = diff
+
+        if diff < 10:
+            return locale.describe('now', only_distance=only_distance)
+
+        if diff < 45:
+            return locale.describe('seconds', sign, only_distance=only_distance)
+
+        elif diff < 90:
+            return locale.describe('minute', sign, only_distance=only_distance)
+        elif diff < 2700:
+            minutes = sign * int(max(delta / 60, 2))
+            return locale.describe('minutes', minutes, only_distance=only_distance)
+
+        elif diff < 5400:
+            return locale.describe('hour', sign, only_distance=only_distance)
+        elif diff < 79200:
+            hours = sign * int(max(delta / 3600, 2))
+            return locale.describe('hours', hours, only_distance=only_distance)
+
+        elif diff < 129600:
+            return locale.describe('day', sign, only_distance=only_distance)
+        elif diff < 2160000:
+            days = sign * int(max(delta / 86400, 2))
+            return locale.describe('days', days, only_distance=only_distance)
+
+        elif diff < 3888000:
+            return locale.describe('month', sign, only_distance=only_distance)
+        elif diff < 29808000:
+            self_months = self._datetime.year * 12 + self._datetime.month
+            other_months = dt.year * 12 + dt.month
+
+            months = sign * int(max(abs(other_months - self_months), 2))
+
+            return locale.describe('months', months, only_distance=only_distance)
+
+        elif diff < 47260800:
+            return locale.describe('year', sign, only_distance=only_distance)
+        else:
+            years = sign * int(max(delta / 31536000, 2))
+            return locale.describe('years', years, only_distance=only_distance)
+
+
+    # math
+
+    def __add__(self, other):
+
+        if isinstance(other, (timedelta, relativedelta)):
+            return self.fromdatetime(self._datetime + other, self._datetime.tzinfo)
+
+        raise TypeError()
+
+    def __radd__(self, other):
+        return self.__add__(other)
+
+    def __sub__(self, other):
+
+        if isinstance(other, (timedelta, relativedelta)):
+            return self.fromdatetime(self._datetime - other, self._datetime.tzinfo)
+
+        elif isinstance(other, datetime):
+            return self._datetime - other
+
+        elif isinstance(other, Arrow):
+            return self._datetime - other._datetime
+
+        raise TypeError()
+
+    def __rsub__(self, other):
+
+        if isinstance(other, datetime):
+            return other - self._datetime
+
+        raise TypeError()
+
+
+    # comparisons
+
+    def _cmperror(self, other):
+        raise TypeError('can\'t compare \'{0}\' to \'{1}\''.format(
+            type(self), type(other)))
+
+    def __eq__(self, other):
+
+        if not isinstance(other, (Arrow, datetime)):
+            return False
+
+        return self._datetime == self._get_datetime(other)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __gt__(self, other):
+
+        if not isinstance(other, (Arrow, datetime)):
+            self._cmperror(other)
+
+        return self._datetime > self._get_datetime(other)
+
+    def __ge__(self, other):
+
+        if not isinstance(other, (Arrow, datetime)):
+            self._cmperror(other)
+
+        return self._datetime >= self._get_datetime(other)
+
+    def __lt__(self, other):
+
+        if not isinstance(other, (Arrow, datetime)):
+            self._cmperror(other)
+
+        return self._datetime < self._get_datetime(other)
+
+    def __le__(self, other):
+
+        if not isinstance(other, (Arrow, datetime)):
+            self._cmperror(other)
+
+        return self._datetime <= self._get_datetime(other)
+
+
+    # datetime methods
+
+    def date(self):
+        ''' Returns a ``date`` object with the same year, month and day. '''
+
+        return self._datetime.date()
+
+    def time(self):
+        ''' Returns a ``time`` object with the same hour, minute, second, microsecond. '''
+
+        return self._datetime.time()
+
+    def timetz(self):
+        ''' Returns a ``time`` object with the same hour, minute, second, microsecond and tzinfo. '''
+
+        return self._datetime.timetz()
+
+    def astimezone(self, tz):
+        ''' Returns a ``datetime`` object, adjusted to the specified tzinfo.
+
+        :param tz: a ``tzinfo`` object.
+
+        '''
+
+        return self._datetime.astimezone(tz)
+
+    def utcoffset(self):
+        ''' Returns a ``timedelta`` object representing the whole number of minutes difference from UTC time. '''
+
+        return self._datetime.utcoffset()
+
+    def dst(self):
+        ''' Returns the daylight savings time adjustment. '''
+        return self._datetime.dst()
+
+    def timetuple(self):
+        ''' Returns a ``time.struct_time``, in the current timezone. '''
+
+        return self._datetime.timetuple()
+
+    def utctimetuple(self):
+        ''' Returns a ``time.struct_time``, in UTC time. '''
+
+        return self._datetime.utctimetuple()
+
+    def toordinal(self):
+        ''' Returns the proleptic Gregorian ordinal of the date. '''
+
+        return self._datetime.toordinal()
+
+    def weekday(self):
+        ''' Returns the day of the week as an integer (0-6). '''
+
+        return self._datetime.weekday()
+
+    def isoweekday(self):
+        ''' Returns the ISO day of the week as an integer (1-7). '''
+
+        return self._datetime.isoweekday()
+
+    def isocalendar(self):
+        ''' Returns a 3-tuple, (ISO year, ISO week number, ISO weekday). '''
+
+        return self._datetime.isocalendar()
+
+    def isoformat(self, sep='T'):
+        '''Returns an ISO 8601 formatted representation of the date and time. '''
+
+        return self._datetime.isoformat(sep)
+
+    def ctime(self):
+        ''' Returns a ctime formatted representation of the date and time. '''
+
+        return self._datetime.ctime()
+
+    def strftime(self, format):
+        ''' Formats in the style of ``datetime.strptime``.
+
+        :param format: the format string.
+
+        '''
+
+        return self._datetime.strftime(format)
+
+    def for_json(self):
+        '''Serializes for the ``for_json`` protocol of simplejson.'''
+        return self.isoformat()
+
+    # internal tools.
+
+    @staticmethod
+    def _get_tzinfo(tz_expr):
+
+        if tz_expr is None:
+            return dateutil_tz.tzutc()
+        if isinstance(tz_expr, tzinfo):
+            return tz_expr
+        else:
+            try:
+                return parser.TzinfoParser.parse(tz_expr)
+            except parser.ParserError:
+                raise ValueError('\'{0}\' not recognized as a timezone'.format(
+                    tz_expr))
+
+    @classmethod
+    def _get_datetime(cls, expr):
+
+        if isinstance(expr, Arrow):
+            return expr.datetime
+
+        if isinstance(expr, datetime):
+            return expr
+
+        try:
+            expr = float(expr)
+            return cls.utcfromtimestamp(expr).datetime
+        except:
+            raise ValueError(
+                '\'{0}\' not recognized as a timestamp or datetime'.format(expr))
+
+    @classmethod
+    def _get_frames(cls, name):
+
+        if name in cls._ATTRS:
+            return name, '{0}s'.format(name), 1
+
+        elif name in ['week', 'weeks']:
+            return 'week', 'weeks', 1
+        elif name in ['quarter', 'quarters']:
+            return 'quarter', 'months', 3
+
+        raise AttributeError()
+
+    @classmethod
+    def _get_iteration_params(cls, end, limit):
+
+        if end is None:
+
+            if limit is None:
+                raise Exception('one of \'end\' or \'limit\' is required')
+
+            return cls.max, limit
+
+        else:
+            if limit is None:
+                return end, sys.maxsize
+            return end, limit
+
+    @staticmethod
+    def _get_timestamp_from_input(timestamp):
+
+        try:
+            return float(timestamp)
+        except:
+            raise ValueError('cannot parse \'{0}\' as a timestamp'.format(timestamp))
+
+Arrow.min = Arrow.fromdatetime(datetime.min)
+Arrow.max = Arrow.fromdatetime(datetime.max)
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow/factory.py
@@ -0,0 +1,254 @@
+# -*- coding: utf-8 -*-
+"""
+Implements the :class:`ArrowFactory <arrow.factory.ArrowFactory>` class,
+providing factory methods for common :class:`Arrow <arrow.arrow.Arrow>`
+construction scenarios.
+
+"""
+
+from __future__ import absolute_import
+
+from arrow.arrow import Arrow
+from arrow import parser
+from arrow.util import is_timestamp, isstr
+
+from datetime import datetime, tzinfo, date
+from dateutil import tz as dateutil_tz
+from time import struct_time
+import calendar
+
+
+class ArrowFactory(object):
+    ''' A factory for generating :class:`Arrow <arrow.arrow.Arrow>` objects.
+
+    :param type: (optional) the :class:`Arrow <arrow.arrow.Arrow>`-based class to construct from.
+        Defaults to :class:`Arrow <arrow.arrow.Arrow>`.
+
+    '''
+
+    def __init__(self, type=Arrow):
+        self.type = type
+
+    def get(self, *args, **kwargs):
+        ''' Returns an :class:`Arrow <arrow.arrow.Arrow>` object based on flexible inputs.
+
+        Usage::
+
+            >>> import arrow
+
+        **No inputs** to get current UTC time::
+
+            >>> arrow.get()
+            <Arrow [2013-05-08T05:51:43.316458+00:00]>
+
+        **None** to also get current UTC time::
+
+            >>> arrow.get(None)
+            <Arrow [2013-05-08T05:51:43.316458+00:00]>
+
+        **One** :class:`Arrow <arrow.arrow.Arrow>` object, to get a copy.
+
+            >>> arw = arrow.utcnow()
+            >>> arrow.get(arw)
+            <Arrow [2013-10-23T15:21:54.354846+00:00]>
+
+        **One** ``str``, ``float``, or ``int``, convertible to a floating-point timestamp, to get that timestamp in UTC::
+
+            >>> arrow.get(1367992474.293378)
+            <Arrow [2013-05-08T05:54:34.293378+00:00]>
+
+            >>> arrow.get(1367992474)
+            <Arrow [2013-05-08T05:54:34+00:00]>
+
+            >>> arrow.get('1367992474.293378')
+            <Arrow [2013-05-08T05:54:34.293378+00:00]>
+
+            >>> arrow.get('1367992474')
+            <Arrow [2013-05-08T05:54:34+0struct_time0:00]>
+
+        **One** ISO-8601-formatted ``str``, to parse it::
+
+            >>> arrow.get('2013-09-29T01:26:43.830580')
+            <Arrow [2013-09-29T01:26:43.830580+00:00]>
+
+        **One** ``tzinfo``, to get the current time in that timezone::
+
+            >>> arrow.get(tz.tzlocal())
+            <Arrow [2013-05-07T22:57:28.484717-07:00]>
+
+        **One** naive ``datetime``, to get that datetime in UTC::
+
+            >>> arrow.get(datetime(2013, 5, 5))
+            <Arrow [2013-05-05T00:00:00+00:00]>
+
+        **One** aware ``datetime``, to get that datetime::
+
+            >>> arrow.get(datetime(2013, 5, 5, tzinfo=tz.tzlocal()))
+            <Arrow [2013-05-05T00:00:00-07:00]>
+
+        **One** naive ``date``, to get that date in UTC::
+
+            >>> arrow.get(date(2013, 5, 5))
+            <Arrow [2013-05-05T00:00:00+00:00]>
+
+        **Two** arguments, a naive or aware ``datetime``, and a timezone expression (as above)::
+
+            >>> arrow.get(datetime(2013, 5, 5), 'US/Pacific')
+            <Arrow [2013-05-05T00:00:00-07:00]>
+
+        **Two** arguments, a naive ``date``, and a timezone expression (as above)::
+
+            >>> arrow.get(date(2013, 5, 5), 'US/Pacific')
+            <Arrow [2013-05-05T00:00:00-07:00]>
+
+        **Two** arguments, both ``str``, to parse the first according to the format of the second::
+
+            >>> arrow.get('2013-05-05 12:30:45', 'YYYY-MM-DD HH:mm:ss')
+            <Arrow [2013-05-05T12:30:45+00:00]>
+
+        **Two** arguments, first a ``str`` to parse and second a ``list`` of formats to try::
+
+            >>> arrow.get('2013-05-05 12:30:45', ['MM/DD/YYYY', 'YYYY-MM-DD HH:mm:ss'])
+            <Arrow [2013-05-05T12:30:45+00:00]>
+
+        **Three or more** arguments, as for the constructor of a ``datetime``::
+
+            >>> arrow.get(2013, 5, 5, 12, 30, 45)
+            <Arrow [2013-05-05T12:30:45+00:00]>
+
+        **One** time.struct time::
+            >>> arrow.get(gmtime(0))
+            <Arrow [1970-01-01T00:00:00+00:00]>
+
+        '''
+
+        arg_count = len(args)
+        locale = kwargs.get('locale', 'en_us')
+        tz = kwargs.get('tzinfo', None)
+
+        # () -> now, @ utc.
+        if arg_count == 0:
+            if isinstance(tz, tzinfo):
+                return self.type.now(tz)
+            return self.type.utcnow()
+
+        if arg_count == 1:
+            arg = args[0]
+
+            # (None) -> now, @ utc.
+            if arg is None:
+                return self.type.utcnow()
+
+            # try (int, float, str(int), str(float)) -> utc, from timestamp.
+            if is_timestamp(arg):
+                return self.type.utcfromtimestamp(arg)
+
+            # (Arrow) -> from the object's datetime.
+            if isinstance(arg, Arrow):
+                return self.type.fromdatetime(arg.datetime)
+
+            # (datetime) -> from datetime.
+            if isinstance(arg, datetime):
+                return self.type.fromdatetime(arg)
+
+            # (date) -> from date.
+            if isinstance(arg, date):
+                return self.type.fromdate(arg)
+
+            # (tzinfo) -> now, @ tzinfo.
+            elif isinstance(arg, tzinfo):
+                return self.type.now(arg)
+
+            # (str) -> now, @ tzinfo.
+            elif isstr(arg):
+                dt = parser.DateTimeParser(locale).parse_iso(arg)
+                return self.type.fromdatetime(dt)
+
+            # (struct_time) -> from struct_time
+            elif isinstance(arg, struct_time):
+                return self.type.utcfromtimestamp(calendar.timegm(arg))
+
+            else:
+                raise TypeError('Can\'t parse single argument type of \'{0}\''.format(type(arg)))
+
+        elif arg_count == 2:
+
+            arg_1, arg_2 = args[0], args[1]
+
+            if isinstance(arg_1, datetime):
+
+                # (datetime, tzinfo) -> fromdatetime @ tzinfo/string.
+                if isinstance(arg_2, tzinfo) or isstr(arg_2):
+                    return self.type.fromdatetime(arg_1, arg_2)
+                else:
+                    raise TypeError('Can\'t parse two arguments of types \'datetime\', \'{0}\''.format(
+                        type(arg_2)))
+
+            # (date, tzinfo/str) -> fromdate @ tzinfo/string.
+            elif isinstance(arg_1, date):
+
+                if isinstance(arg_2, tzinfo) or isstr(arg_2):
+                    return self.type.fromdate(arg_1, tzinfo=arg_2)
+                else:
+                    raise TypeError('Can\'t parse two arguments of types \'date\', \'{0}\''.format(
+                        type(arg_2)))
+
+            # (str, format) -> parse.
+            elif isstr(arg_1) and (isstr(arg_2) or isinstance(arg_2, list)):
+                dt = parser.DateTimeParser(locale).parse(args[0], args[1])
+                return self.type.fromdatetime(dt, tzinfo=tz)
+
+            else:
+                raise TypeError('Can\'t parse two arguments of types \'{0}\', \'{1}\''.format(
+                    type(arg_1), type(arg_2)))
+
+        # 3+ args -> datetime-like via constructor.
+        else:
+            return self.type(*args, **kwargs)
+
+    def utcnow(self):
+        '''Returns an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now" in UTC time.
+
+        Usage::
+
+            >>> import arrow
+            >>> arrow.utcnow()
+            <Arrow [2013-05-08T05:19:07.018993+00:00]>
+        '''
+
+        return self.type.utcnow()
+
+    def now(self, tz=None):
+        '''Returns an :class:`Arrow <arrow.arrow.Arrow>` object, representing "now".
+
+        :param tz: (optional) An expression representing a timezone.  Defaults to local time.
+
+        Recognized timezone expressions:
+
+            - A ``tzinfo`` object.
+            - A ``str`` describing a timezone, similar to 'US/Pacific', or 'Europe/Berlin'.
+            - A ``str`` in ISO-8601 style, as in '+07:00'.
+            - A ``str``, one of the following:  'local', 'utc', 'UTC'.
+
+        Usage::
+
+            >>> import arrow
+            >>> arrow.now()
+            <Arrow [2013-05-07T22:19:11.363410-07:00]>
+
+            >>> arrow.now('US/Pacific')
+            <Arrow [2013-05-07T22:19:15.251821-07:00]>
+
+            >>> arrow.now('+02:00')
+            <Arrow [2013-05-08T07:19:25.618646+02:00]>
+
+            >>> arrow.now('local')
+            <Arrow [2013-05-07T22:19:39.130059-07:00]>
+        '''
+
+        if tz is None:
+            tz = dateutil_tz.tzlocal()
+        elif not isinstance(tz, tzinfo):
+            tz = parser.TzinfoParser.parse(tz)
+
+        return self.type.now(tz)
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow/formatter.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import calendar
+import re
+from dateutil import tz as dateutil_tz
+from arrow import util, locales
+
+
+class DateTimeFormatter(object):
+
+    _FORMAT_RE = re.compile('(YYY?Y?|MM?M?M?|Do|DD?D?D?|d?dd?d?|HH?|hh?|mm?|ss?|SS?S?S?S?S?|ZZ?|a|A|X)')
+
+    def __init__(self, locale='en_us'):
+
+        self.locale = locales.get_locale(locale)
+
+    def format(cls, dt, fmt):
+
+        return cls._FORMAT_RE.sub(lambda m: cls._format_token(dt, m.group(0)), fmt)
+
+    def _format_token(self, dt, token):
+
+        if token == 'YYYY':
+            return self.locale.year_full(dt.year)
+        if token == 'YY':
+            return self.locale.year_abbreviation(dt.year)
+
+        if token == 'MMMM':
+            return self.locale.month_name(dt.month)
+        if token == 'MMM':
+            return self.locale.month_abbreviation(dt.month)
+        if token == 'MM':
+            return '{0:02d}'.format(dt.month)
+        if token == 'M':
+            return str(dt.month)
+
+        if token == 'DDDD':
+            return '{0:03d}'.format(dt.timetuple().tm_yday)
+        if token == 'DDD':
+            return str(dt.timetuple().tm_yday)
+        if token == 'DD':
+            return '{0:02d}'.format(dt.day)
+        if token == 'D':
+            return str(dt.day)
+
+        if token == 'Do':
+            return self.locale.ordinal_number(dt.day)
+
+        if token == 'dddd':
+            return self.locale.day_name(dt.isoweekday())
+        if token == 'ddd':
+            return self.locale.day_abbreviation(dt.isoweekday())
+        if token == 'd':
+            return str(dt.isoweekday())
+
+        if token == 'HH':
+            return '{0:02d}'.format(dt.hour)
+        if token == 'H':
+            return str(dt.hour)
+        if token == 'hh':
+            return '{0:02d}'.format(dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12))
+        if token == 'h':
+            return str(dt.hour if 0 < dt.hour < 13 else abs(dt.hour - 12))
+
+        if token == 'mm':
+            return '{0:02d}'.format(dt.minute)
+        if token == 'm':
+            return str(dt.minute)
+
+        if token == 'ss':
+            return '{0:02d}'.format(dt.second)
+        if token == 's':
+            return str(dt.second)
+
+        if token == 'SSSSSS':
+            return str('{0:06d}'.format(int(dt.microsecond)))
+        if token == 'SSSSS':
+            return str('{0:05d}'.format(int(dt.microsecond / 10)))
+        if token == 'SSSS':
+            return str('{0:04d}'.format(int(dt.microsecond / 100)))
+        if token == 'SSS':
+            return str('{0:03d}'.format(int(dt.microsecond / 1000)))
+        if token == 'SS':
+            return str('{0:02d}'.format(int(dt.microsecond / 10000)))
+        if token == 'S':
+            return str(int(dt.microsecond / 100000))
+
+        if token == 'X':
+            return str(calendar.timegm(dt.utctimetuple()))
+
+        if token in ['ZZ', 'Z']:
+            separator = ':' if token == 'ZZ' else ''
+            tz = dateutil_tz.tzutc() if dt.tzinfo is None else dt.tzinfo
+            total_minutes = int(util.total_seconds(tz.utcoffset(dt)) / 60)
+
+            sign = '+' if total_minutes >= 0 else '-'
+            total_minutes = abs(total_minutes)
+            hour, minute = divmod(total_minutes, 60)
+
+            return '{0}{1:02d}{2}{3:02d}'.format(sign, hour, separator, minute)
+
+        if token in ('a', 'A'):
+            return self.locale.meridian(dt.hour, token)
+
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow/locales.py
@@ -0,0 +1,2011 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import inspect
+import sys
+
+
+def get_locale(name):
+    '''Returns an appropriate :class:`Locale <arrow.locales.Locale>`
+    corresponding to an inpute locale name.
+
+    :param name: the name of the locale.
+
+    '''
+
+    locale_cls = _locales.get(name.lower())
+
+    if locale_cls is None:
+        raise ValueError('Unsupported locale \'{0}\''.format(name))
+
+    return locale_cls()
+
+
+# base locale type.
+
+class Locale(object):
+    ''' Represents locale-specific data and functionality. '''
+
+    names = []
+
+    timeframes = {
+        'now': '',
+        'seconds': '',
+        'minute': '',
+        'minutes': '',
+        'hour': '',
+        'hours': '',
+        'day': '',
+        'days': '',
+        'month': '',
+        'months': '',
+        'year': '',
+        'years': '',
+    }
+
+    meridians = {
+        'am': '',
+        'pm': '',
+        'AM': '',
+        'PM': '',
+    }
+
+    past = None
+    future = None
+
+    month_names = []
+    month_abbreviations = []
+
+    day_names = []
+    day_abbreviations = []
+
+    ordinal_day_re = r'(\d+)'
+
+    def __init__(self):
+
+        self._month_name_to_ordinal = None
+
+    def describe(self, timeframe, delta=0, only_distance=False):
+        ''' Describes a delta within a timeframe in plain language.
+
+        :param timeframe: a string representing a timeframe.
+        :param delta: a quantity representing a delta in a timeframe.
+        :param only_distance: return only distance eg: "11 seconds" without "in" or "ago" keywords
+        '''
+
+        humanized = self._format_timeframe(timeframe, delta)
+        if not only_distance:
+            humanized = self._format_relative(humanized, timeframe, delta)
+
+        return humanized
+
+    def day_name(self, day):
+        ''' Returns the day name for a specified day of the week.
+
+        :param day: the ``int`` day of the week (1-7).
+
+        '''
+
+        return self.day_names[day]
+
+    def day_abbreviation(self, day):
+        ''' Returns the day abbreviation for a specified day of the week.
+
+        :param day: the ``int`` day of the week (1-7).
+
+        '''
+
+        return self.day_abbreviations[day]
+
+    def month_name(self, month):
+        ''' Returns the month name for a specified month of the year.
+
+        :param month: the ``int`` month of the year (1-12).
+
+        '''
+
+        return self.month_names[month]
+
+    def month_abbreviation(self, month):
+        ''' Returns the month abbreviation for a specified month of the year.
+
+        :param month: the ``int`` month of the year (1-12).
+
+        '''
+
+        return self.month_abbreviations[month]
+
+    def month_number(self, name):
+        ''' Returns the month number for a month specified by name or abbreviation.
+
+        :param name: the month name or abbreviation.
+
+        '''
+
+        if self._month_name_to_ordinal is None:
+            self._month_name_to_ordinal = self._name_to_ordinal(self.month_names)
+            self._month_name_to_ordinal.update(self._name_to_ordinal(self.month_abbreviations))
+
+        return self._month_name_to_ordinal.get(name)
+
+    def year_full(self, year):
+        '''  Returns the year for specific locale if available
+
+        :param name: the ``int`` year (4-digit)
+        '''
+        return '{0:04d}'.format(year)
+
+    def year_abbreviation(self, year):
+        ''' Returns the year for specific locale if available
+
+        :param name: the ``int`` year (4-digit)
+        '''
+        return '{0:04d}'.format(year)[2:]
+
+    def meridian(self, hour, token):
+        ''' Returns the meridian indicator for a specified hour and format token.
+
+        :param hour: the ``int`` hour of the day.
+        :param token: the format token.
+        '''
+
+        if token == 'a':
+            return self.meridians['am'] if hour < 12 else self.meridians['pm']
+        if token == 'A':
+            return self.meridians['AM'] if hour < 12 else self.meridians['PM']
+
+    def ordinal_number(self, n):
+        ''' Returns the ordinal format of a given integer
+
+        :param n: an integer
+        '''
+        return self._ordinal_number(n)
+
+    def _ordinal_number(self, n):
+        return '{0}'.format(n)
+
+    def _name_to_ordinal(self, lst):
+        return dict(map(lambda i: (i[1].lower(), i[0] + 1), enumerate(lst[1:])))
+
+    def _format_timeframe(self, timeframe, delta):
+
+        return self.timeframes[timeframe].format(abs(delta))
+
+    def _format_relative(self, humanized, timeframe, delta):
+
+        if timeframe == 'now':
+            return humanized
+
+        direction = self.past if delta < 0 else self.future
+
+        return direction.format(humanized)
+
+
+# base locale type implementations.
+
+class EnglishLocale(Locale):
+
+    names = ['en', 'en_us', 'en_gb', 'en_au', 'en_be', 'en_jp', 'en_za', 'en_ca']
+
+    past = '{0} ago'
+    future = 'in {0}'
+
+    timeframes = {
+        'now': 'just now',
+        'seconds': 'seconds',
+        'minute': 'a minute',
+        'minutes': '{0} minutes',
+        'hour': 'an hour',
+        'hours': '{0} hours',
+        'day': 'a day',
+        'days': '{0} days',
+        'month': 'a month',
+        'months': '{0} months',
+        'year': 'a year',
+        'years': '{0} years',
+    }
+
+    meridians = {
+        'am': 'am',
+        'pm': 'pm',
+        'AM': 'AM',
+        'PM': 'PM',
+    }
+
+    month_names = ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July',
+        'August', 'September', 'October', 'November', 'December']
+    month_abbreviations = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
+        'Sep', 'Oct', 'Nov', 'Dec']
+
+    day_names = ['', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']
+    day_abbreviations = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+
+    ordinal_day_re = r'((?P<value>[2-3]?1(?=st)|[2-3]?2(?=nd)|[2-3]?3(?=rd)|[1-3]?[04-9](?=th)|1[1-3](?=th))(st|nd|rd|th))'
+
+    def _ordinal_number(self, n):
+        if n % 100 not in (11, 12, 13):
+            remainder = abs(n) % 10
+            if remainder == 1:
+                return '{0}st'.format(n)
+            elif remainder == 2:
+                return '{0}nd'.format(n)
+            elif remainder == 3:
+                return '{0}rd'.format(n)
+        return '{0}th'.format(n)
+
+
+class ItalianLocale(Locale):
+    names = ['it', 'it_it']
+    past = '{0} fa'
+    future = 'tra {0}'
+
+    timeframes = {
+        'now': 'adesso',
+        'seconds': 'qualche secondo',
+        'minute': 'un minuto',
+        'minutes': '{0} minuti',
+        'hour': 'un\'ora',
+        'hours': '{0} ore',
+        'day': 'un giorno',
+        'days': '{0} giorni',
+        'month': 'un mese',
+        'months': '{0} mesi',
+        'year': 'un anno',
+        'years': '{0} anni',
+    }
+
+    month_names = ['', 'gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio',
+        'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre']
+    month_abbreviations = ['', 'gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago',
+        'set', 'ott', 'nov', 'dic']
+
+    day_names = ['', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato', 'domenica']
+    day_abbreviations = ['', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab', 'dom']
+
+    ordinal_day_re = r'((?P<value>[1-3]?[0-9](?=[ºª]))[ºª])'
+
+    def _ordinal_number(self, n):
+        return '{0}º'.format(n)
+
+
+class SpanishLocale(Locale):
+    names = ['es', 'es_es']
+    past = 'hace {0}'
+    future = 'en {0}'
+
+    timeframes = {
+        'now': 'ahora',
+        'seconds': 'segundos',
+        'minute': 'un minuto',
+        'minutes': '{0} minutos',
+        'hour': 'una hora',
+        'hours': '{0} horas',
+        'day': 'un día',
+        'days': '{0} días',
+        'month': 'un mes',
+        'months': '{0} meses',
+        'year': 'un año',
+        'years': '{0} años',
+    }
+
+    month_names = ['', 'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio',
+        'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre']
+    month_abbreviations = ['', 'ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago',
+        'sep', 'oct', 'nov', 'dic']
+
+    day_names = ['', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado', 'domingo']
+    day_abbreviations = ['', 'lun', 'mar', 'mie', 'jue', 'vie', 'sab', 'dom']
+
+    ordinal_day_re = r'((?P<value>[1-3]?[0-9](?=[ºª]))[ºª])'
+
+    def _ordinal_number(self, n):
+        return '{0}º'.format(n)
+
+
+class FrenchLocale(Locale):
+    names = ['fr', 'fr_fr']
+    past = 'il y a {0}'
+    future = 'dans {0}'
+
+    timeframes = {
+        'now': 'maintenant',
+        'seconds': 'quelques secondes',
+        'minute': 'une minute',
+        'minutes': '{0} minutes',
+        'hour': 'une heure',
+        'hours': '{0} heures',
+        'day': 'un jour',
+        'days': '{0} jours',
+        'month': 'un mois',
+        'months': '{0} mois',
+        'year': 'un an',
+        'years': '{0} ans',
+    }
+
+    month_names = ['', 'janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet',
+        'août', 'septembre', 'octobre', 'novembre', 'décembre']
+    month_abbreviations = ['', 'janv', 'févr', 'mars', 'avr', 'mai', 'juin', 'juil', 'août',
+        'sept', 'oct', 'nov', 'déc']
+
+    day_names = ['', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']
+    day_abbreviations = ['', 'lun', 'mar', 'mer', 'jeu', 'ven', 'sam', 'dim']
+
+    ordinal_day_re = r'((?P<value>\b1(?=er\b)|[1-3]?[02-9](?=e\b)|[1-3]1(?=e\b))(er|e)\b)'
+
+    def _ordinal_number(self, n):
+        if abs(n) == 1:
+            return '{0}er'.format(n)
+        return '{0}e'.format(n)
+
+
+class GreekLocale(Locale):
+
+    names = ['el', 'el_gr']
+
+    past = '{0} πριν'
+    future = 'σε {0}'
+
+    timeframes = {
+        'now': 'τώρα',
+        'seconds': 'δευτερόλεπτα',
+        'minute': 'ένα λεπτό',
+        'minutes': '{0} λεπτά',
+        'hour': 'μια ώρα',
+        'hours': '{0} ώρες',
+        'day': 'μια μέρα',
+        'days': '{0} μέρες',
+        'month': 'ένα μήνα',
+        'months': '{0} μήνες',
+        'year': 'ένα χρόνο',
+        'years': '{0} χρόνια',
+    }
+
+    month_names = ['', 'Ιανουαρίου', 'Φεβρουαρίου', 'Μαρτίου', 'Απριλίου', 'Μαΐου', 'Ιουνίου',
+        'Ιουλίου', 'Αυγούστου', 'Σεπτεμβρίου', 'Οκτωβρίου', 'Νοεμβρίου', 'Δεκεμβρίου']
+    month_abbreviations = ['', 'Ιαν', 'Φεβ', 'Μαρ', 'Απρ', 'Μαϊ', 'Ιον', 'Ιολ', 'Αυγ',
+        'Σεπ', 'Οκτ', 'Νοε', 'Δεκ']
+
+    day_names = ['', 'Δευτέρα', 'Τρίτη', 'Τετάρτη', 'Πέμπτη', 'Παρασκευή', 'Σάββατο', 'Κυριακή']
+    day_abbreviations = ['', 'Δευ', 'Τρι', 'Τετ', 'Πεμ', 'Παρ', 'Σαβ', 'Κυρ']
+
+
+class JapaneseLocale(Locale):
+
+    names = ['ja', 'ja_jp']
+
+    past = '{0}前'
+    future = '{0}後'
+
+    timeframes = {
+        'now': '現在',
+        'seconds': '数秒',
+        'minute': '1分',
+        'minutes': '{0}分',
+        'hour': '1時間',
+        'hours': '{0}時間',
+        'day': '1日',
+        'days': '{0}日',
+        'month': '1ヶ月',
+        'months': '{0}ヶ月',
+        'year': '1年',
+        'years': '{0}年',
+    }
+
+    month_names = ['', '1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月',
+        '9月', '10月', '11月', '12月']
+    month_abbreviations = ['', ' 1', ' 2', ' 3', ' 4', ' 5', ' 6', ' 7', ' 8',
+        ' 9', '10', '11', '12']
+
+    day_names = ['', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日']
+    day_abbreviations = ['', '月', '火', '水', '木', '金', '土', '日']
+
+
+class SwedishLocale(Locale):
+
+    names = ['sv', 'sv_se']
+
+    past = 'för {0} sen'
+    future = 'om {0}'
+
+    timeframes = {
+        'now': 'just nu',
+        'seconds': 'några sekunder',
+        'minute': 'en minut',
+        'minutes': '{0} minuter',
+        'hour': 'en timme',
+        'hours': '{0} timmar',
+        'day': 'en dag',
+        'days': '{0} dagar',
+        'month': 'en månad',
+        'months': '{0} månader',
+        'year': 'ett år',
+        'years': '{0} år',
+    }
+
+    month_names = ['', 'januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli',
+        'augusti', 'september', 'oktober', 'november', 'december']
+    month_abbreviations = ['', 'jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul',
+        'aug', 'sep', 'okt', 'nov', 'dec']
+
+    day_names = ['', 'måndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag', 'söndag']
+    day_abbreviations = ['', 'mån', 'tis', 'ons', 'tor', 'fre', 'lör', 'sön']
+
+
+class FinnishLocale(Locale):
+
+    names = ['fi', 'fi_fi']
+
+    # The finnish grammar is very complex, and its hard to convert
+    # 1-to-1 to something like English.
+
+    past = '{0} sitten'
+    future = '{0} kuluttua'
+
+    timeframes = {
+        'now': ['juuri nyt', 'juuri nyt'],
+        'seconds': ['muutama sekunti', 'muutaman sekunnin'],
+        'minute': ['minuutti', 'minuutin'],
+        'minutes': ['{0} minuuttia', '{0} minuutin'],
+        'hour': ['tunti', 'tunnin'],
+        'hours': ['{0} tuntia', '{0} tunnin'],
+        'day': ['päivä', 'päivä'],
+        'days': ['{0} päivää', '{0} päivän'],
+        'month': ['kuukausi', 'kuukauden'],
+        'months': ['{0} kuukautta', '{0} kuukauden'],
+        'year': ['vuosi', 'vuoden'],
+        'years': ['{0} vuotta', '{0} vuoden'],
+    }
+
+    # Months and days are lowercase in Finnish
+    month_names = ['', 'tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu',
+                       'toukokuu', 'kesäkuu', 'heinäkuu', 'elokuu',
+                       'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu']
+
+    month_abbreviations = ['', 'tammi', 'helmi', 'maalis', 'huhti',
+                               'touko', 'kesä', 'heinä', 'elo',
+                               'syys', 'loka', 'marras', 'joulu']
+
+    day_names = ['', 'maanantai', 'tiistai', 'keskiviikko', 'torstai',
+                     'perjantai', 'lauantai', 'sunnuntai']
+
+    day_abbreviations = ['', 'ma', 'ti', 'ke', 'to', 'pe', 'la', 'su']
+
+    def _format_timeframe(self, timeframe, delta):
+        return (self.timeframes[timeframe][0].format(abs(delta)),
+                self.timeframes[timeframe][1].format(abs(delta)))
+
+    def _format_relative(self, humanized, timeframe, delta):
+        if timeframe == 'now':
+            return humanized[0]
+
+        direction = self.past if delta < 0 else self.future
+        which = 0 if delta < 0 else 1
+
+        return direction.format(humanized[which])
+
+    def _ordinal_number(self, n):
+        return '{0}.'.format(n)
+
+
+class ChineseCNLocale(Locale):
+
+    names = ['zh', 'zh_cn']
+
+    past = '{0}前'
+    future = '{0}后'
+
+    timeframes = {
+        'now': '刚才',
+        'seconds': '几秒',
+        'minute': '1分钟',
+        'minutes': '{0}分钟',
+        'hour': '1小时',
+        'hours': '{0}小时',
+        'day': '1天',
+        'days': '{0}天',
+        'month': '1个月',
+        'months': '{0}个月',
+        'year': '1年',
+        'years': '{0}年',
+    }
+
+    month_names = ['', '一月', '二月', '三月', '四月', '五月', '六月', '七月',
+        '八月', '九月', '十月', '十一月', '十二月']
+    month_abbreviations = ['', ' 1', ' 2', ' 3', ' 4', ' 5', ' 6', ' 7', ' 8',
+        ' 9', '10', '11', '12']
+
+    day_names = ['', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']
+    day_abbreviations = ['', '一', '二', '三', '四', '五', '六', '日']
+
+
+class ChineseTWLocale(Locale):
+
+    names = ['zh_tw']
+
+    past = '{0}前'
+    future = '{0}後'
+
+    timeframes = {
+        'now': '剛才',
+        'seconds': '幾秒',
+        'minute': '1分鐘',
+        'minutes': '{0}分鐘',
+        'hour': '1小時',
+        'hours': '{0}小時',
+        'day': '1天',
+        'days': '{0}天',
+        'month': '1個月',
+        'months': '{0}個月',
+        'year': '1年',
+        'years': '{0}年',
+    }
+
+    month_names = ['', '1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月',
+        '9月', '10月', '11月', '12月']
+    month_abbreviations = ['', ' 1', ' 2', ' 3', ' 4', ' 5', ' 6', ' 7', ' 8',
+        ' 9', '10', '11', '12']
+
+    day_names = ['', '周一', '周二', '周三', '周四', '周五', '周六', '周日']
+    day_abbreviations = ['', '一', '二', '三', '四', '五', '六', '日']
+
+
+class KoreanLocale(Locale):
+
+    names = ['ko', 'ko_kr']
+
+    past = '{0} 전'
+    future = '{0} 후'
+
+    timeframes = {
+        'now': '지금',
+        'seconds': '몇 초',
+        'minute': '1분',
+        'minutes': '{0}분',
+        'hour': '1시간',
+        'hours': '{0}시간',
+        'day': '1일',
+        'days': '{0}일',
+        'month': '1개월',
+        'months': '{0}개월',
+        'year': '1년',
+        'years': '{0}년',
+    }
+
+    month_names = ['', '1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월',
+        '9월', '10월', '11월', '12월']
+    month_abbreviations = ['', ' 1', ' 2', ' 3', ' 4', ' 5', ' 6', ' 7', ' 8',
+        ' 9', '10', '11', '12']
+
+    day_names = ['', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일', '일요일']
+    day_abbreviations = ['', '월', '화', '수', '목', '금', '토', '일']
+
+
+# derived locale types & implementations.
+class DutchLocale(Locale):
+
+    names = ['nl', 'nl_nl']
+
+    past = '{0} geleden'
+    future = 'over {0}'
+
+    timeframes = {
+        'now': 'nu',
+        'seconds': 'seconden',
+        'minute': 'een minuut',
+        'minutes': '{0} minuten',
+        'hour': 'een uur',
+        'hours': '{0} uur',
+        'day': 'een dag',
+        'days': '{0} dagen',
+        'month': 'een maand',
+        'months': '{0} maanden',
+        'year': 'een jaar',
+        'years': '{0} jaar',
+    }
+
+    # In Dutch names of months and days are not starting with a capital letter
+    # like in the English language.
+    month_names = ['', 'januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli',
+        'augustus', 'september', 'oktober', 'november', 'december']
+    month_abbreviations = ['', 'jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug',
+        'sep', 'okt', 'nov', 'dec']
+
+    day_names = ['', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag', 'zondag']
+    day_abbreviations = ['', 'ma', 'di', 'wo', 'do', 'vr', 'za', 'zo']
+
+
+class SlavicBaseLocale(Locale):
+
+    def _format_timeframe(self, timeframe, delta):
+
+        form = self.timeframes[timeframe]
+        delta = abs(delta)
+
+        if isinstance(form, list):
+
+            if delta % 10 == 1 and delta % 100 != 11:
+                form = form[0]
+            elif 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20):
+                form = form[1]
+            else:
+                form = form[2]
+
+        return form.format(delta)
+
+class BelarusianLocale(SlavicBaseLocale):
+
+    names = ['be', 'be_by']
+
+    past = '{0} таму'
+    future = 'праз {0}'
+
+    timeframes = {
+        'now': 'зараз',
+        'seconds': 'некалькі секунд',
+        'minute': 'хвіліну',
+        'minutes': ['{0} хвіліну', '{0} хвіліны', '{0} хвілін'],
+        'hour': 'гадзіну',
+        'hours': ['{0} гадзіну', '{0} гадзіны', '{0} гадзін'],
+        'day': 'дзень',
+        'days': ['{0} дзень', '{0} дні', '{0} дзён'],
+        'month': 'месяц',
+        'months': ['{0} месяц', '{0} месяцы', '{0} месяцаў'],
+        'year': 'год',
+        'years': ['{0} год', '{0} гады', '{0} гадоў'],
+    }
+
+    month_names = ['', 'студзеня', 'лютага', 'сакавіка', 'красавіка', 'траўня', 'чэрвеня',
+        'ліпеня', 'жніўня', 'верасня', 'кастрычніка', 'лістапада', 'снежня']
+    month_abbreviations = ['', 'студ', 'лют', 'сак', 'крас', 'трав', 'чэрв', 'ліп', 'жнів',
+        'вер', 'каст', 'ліст', 'снеж']
+
+    day_names = ['', 'панядзелак', 'аўторак', 'серада', 'чацвер', 'пятніца', 'субота', 'нядзеля']
+    day_abbreviations = ['', 'пн', 'ат', 'ср', 'чц', 'пт', 'сб', 'нд']
+
+
+class PolishLocale(SlavicBaseLocale):
+
+    names = ['pl', 'pl_pl']
+
+    past = '{0} temu'
+    future = 'za {0}'
+
+    timeframes = {
+        'now': 'teraz',
+        'seconds': 'kilka sekund',
+        'minute': 'minutę',
+        'minutes': ['{0} minut', '{0} minuty', '{0} minut'],
+        'hour': 'godzina',
+        'hours': ['{0} godzin', '{0} godziny', '{0} godzin'],
+        'day': 'dzień',
+        'days': ['{0} dzień', '{0} dni', '{0} dni'],
+        'month': 'miesiąc',
+        'months': ['{0} miesiąc', '{0} miesiące', '{0} miesięcy'],
+        'year': 'rok',
+        'years': ['{0} rok', '{0} lata', '{0} lat'],
+    }
+
+    month_names = ['', 'styczeń', 'luty', 'marzec', 'kwiecień', 'maj',
+        'czerwiec', 'lipiec', 'sierpień', 'wrzesień', 'październik',
+        'listopad', 'grudzień']
+    month_abbreviations = ['', 'sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip',
+        'sie', 'wrz', 'paź', 'lis', 'gru']
+
+    day_names = ['', 'poniedziałek', 'wtorek', 'środa', 'czwartek', 'piątek',
+        'sobota', 'niedziela']
+    day_abbreviations = ['', 'Pn', 'Wt', 'Śr', 'Czw', 'Pt', 'So', 'Nd']
+
+
+class RussianLocale(SlavicBaseLocale):
+
+    names = ['ru', 'ru_ru']
+
+    past = '{0} назад'
+    future = 'через {0}'
+
+    timeframes = {
+        'now': 'сейчас',
+        'seconds': 'несколько секунд',
+        'minute': 'минуту',
+        'minutes': ['{0} минуту', '{0} минуты', '{0} минут'],
+        'hour': 'час',
+        'hours': ['{0} час', '{0} часа', '{0} часов'],
+        'day': 'день',
+        'days': ['{0} день', '{0} дня', '{0} дней'],
+        'month': 'месяц',
+        'months': ['{0} месяц', '{0} месяца', '{0} месяцев'],
+        'year': 'год',
+        'years': ['{0} год', '{0} года', '{0} лет'],
+    }
+
+    month_names = ['', 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня',
+        'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря']
+    month_abbreviations = ['', 'янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл',
+        'авг', 'сен', 'окт', 'ноя', 'дек']
+
+    day_names = ['', 'понедельник', 'вторник', 'среда', 'четверг', 'пятница',
+        'суббота', 'воскресенье']
+    day_abbreviations = ['', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс']
+
+
+class BulgarianLocale(SlavicBaseLocale):
+
+    names = ['bg', 'bg_BG']
+
+    past = '{0} назад'
+    future = 'напред {0}'
+
+    timeframes = {
+        'now': 'сега',
+        'seconds': 'няколко секунди',
+        'minute': 'минута',
+        'minutes': ['{0} минута', '{0} минути', '{0} минути'],
+        'hour': 'час',
+        'hours': ['{0} час', '{0} часа', '{0} часа'],
+        'day': 'ден',
+        'days': ['{0} ден', '{0} дни', '{0} дни'],
+        'month': 'месец',
+        'months': ['{0} месец', '{0} месеца', '{0} месеца'],
+        'year': 'година',
+        'years': ['{0} година', '{0} години', '{0} години'],
+    }
+
+    month_names = ['', 'януари', 'февруари', 'март', 'април', 'май', 'юни',
+        'юли', 'август', 'септември', 'октомври', 'ноември', 'декември']
+    month_abbreviations = ['', 'ян', 'февр', 'март', 'апр', 'май', 'юни', 'юли',
+        'авг', 'септ', 'окт', 'ноем', 'дек']
+
+    day_names = ['', 'понеделник', 'вторник', 'сряда', 'четвъртък', 'петък',
+        'събота', 'неделя']
+    day_abbreviations = ['', 'пон', 'вт', 'ср', 'четв', 'пет', 'съб', 'нед']
+
+
+class UkrainianLocale(SlavicBaseLocale):
+
+    names = ['ua', 'uk_ua']
+
+    past = '{0} тому'
+    future = 'за {0}'
+
+    timeframes = {
+        'now': 'зараз',
+        'seconds': 'кілька секунд',
+        'minute': 'хвилину',
+        'minutes': ['{0} хвилину', '{0} хвилини', '{0} хвилин'],
+        'hour': 'годину',
+        'hours': ['{0} годину', '{0} години', '{0} годин'],
+        'day': 'день',
+        'days': ['{0} день', '{0} дні', '{0} днів'],
+        'month': 'місяць',
+        'months': ['{0} місяць', '{0} місяці', '{0} місяців'],
+        'year': 'рік',
+        'years': ['{0} рік', '{0} роки', '{0} років'],
+    }
+
+    month_names = ['', 'січня', 'лютого', 'березня', 'квітня', 'травня', 'червня',
+        'липня', 'серпня', 'вересня', 'жовтня', 'листопада', 'грудня']
+    month_abbreviations = ['', 'січ', 'лют', 'бер', 'квіт', 'трав', 'черв', 'лип', 'серп',
+        'вер', 'жовт', 'лист', 'груд']
+
+    day_names = ['', 'понеділок', 'вівторок', 'середа', 'четвер', 'п’ятниця', 'субота', 'неділя']
+    day_abbreviations = ['', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'нд']
+
+
+class _DeutschLocaleCommonMixin(object):
+
+    past = 'vor {0}'
+    future = 'in {0}'
+
+    timeframes = {
+        'now': 'gerade eben',
+        'seconds': 'Sekunden',
+        'minute': 'einer Minute',
+        'minutes': '{0} Minuten',
+        'hour': 'einer Stunde',
+        'hours': '{0} Stunden',
+        'day': 'einem Tag',
+        'days': '{0} Tagen',
+        'month': 'einem Monat',
+        'months': '{0} Monaten',
+        'year': 'einem Jahr',
+        'years': '{0} Jahren',
+    }
+
+    month_names = [
+        '', 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli',
+        'August', 'September', 'Oktober', 'November', 'Dezember'
+    ]
+
+    month_abbreviations = [
+        '', 'Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep',
+        'Okt', 'Nov', 'Dez'
+    ]
+
+    day_names = [
+        '', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag',
+        'Samstag', 'Sonntag'
+    ]
+
+    day_abbreviations = [
+        '', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'
+    ]
+
+    def _ordinal_number(self, n):
+        return '{0}.'.format(n)
+
+
+class GermanLocale(_DeutschLocaleCommonMixin, Locale):
+
+    names = ['de', 'de_de']
+
+    timeframes = _DeutschLocaleCommonMixin.timeframes.copy()
+    timeframes['days'] = '{0} Tagen'
+
+
+class AustriaLocale(_DeutschLocaleCommonMixin, Locale):
+
+    names = ['de', 'de_at']
+
+    timeframes = _DeutschLocaleCommonMixin.timeframes.copy()
+    timeframes['days'] = '{0} Tage'
+
+
+class NorwegianLocale(Locale):
+
+    names = ['nb', 'nb_no']
+
+    past = 'for {0} siden'
+    future = 'om {0}'
+
+    timeframes = {
+        'now': 'nå nettopp',
+        'seconds': 'noen sekunder',
+        'minute': 'ett minutt',
+        'minutes': '{0} minutter',
+        'hour': 'en time',
+        'hours': '{0} timer',
+        'day': 'en dag',
+        'days': '{0} dager',
+        'month': 'en måned',
+        'months': '{0} måneder',
+        'year': 'ett år',
+        'years': '{0} år',
+    }
+
+    month_names = ['', 'januar', 'februar', 'mars', 'april', 'mai', 'juni',
+                   'juli', 'august', 'september', 'oktober', 'november',
+                   'desember']
+    month_abbreviations = ['', 'jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul',
+                           'aug', 'sep', 'okt', 'nov', 'des']
+
+    day_names = ['', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag',
+                 'lørdag', 'søndag']
+    day_abbreviations = ['', 'ma', 'ti', 'on', 'to', 'fr', 'lø', 'sø']
+
+
+class NewNorwegianLocale(Locale):
+
+    names = ['nn', 'nn_no']
+
+    past = 'for {0} sidan'
+    future = 'om {0}'
+
+    timeframes = {
+        'now': 'no nettopp',
+        'seconds': 'nokre sekund',
+        'minute': 'ett minutt',
+        'minutes': '{0} minutt',
+        'hour': 'ein time',
+        'hours': '{0} timar',
+        'day': 'ein dag',
+        'days': '{0} dagar',
+        'month': 'en månad',
+        'months': '{0} månader',
+        'year': 'eit år',
+        'years': '{0} år',
+    }
+
+    month_names = ['', 'januar', 'februar', 'mars', 'april', 'mai', 'juni',
+                   'juli', 'august', 'september', 'oktober', 'november',
+                   'desember']
+    month_abbreviations = ['', 'jan', 'feb', 'mar', 'apr', 'mai', 'jun', 'jul',
+                           'aug', 'sep', 'okt', 'nov', 'des']
+
+    day_names = ['', 'måndag', 'tysdag', 'onsdag', 'torsdag', 'fredag',
+                 'laurdag', 'sundag']
+    day_abbreviations = ['', 'må', 'ty', 'on', 'to', 'fr', 'la', 'su']
+
+
+class PortugueseLocale(Locale):
+    names = ['pt', 'pt_pt']
+
+    past = 'há {0}'
+    future = 'em {0}'
+
+    timeframes = {
+        'now': 'agora',
+        'seconds': 'segundos',
+        'minute': 'um minuto',
+        'minutes': '{0} minutos',
+        'hour': 'uma hora',
+        'hours': '{0} horas',
+        'day': 'um dia',
+        'days': '{0} dias',
+        'month': 'um mês',
+        'months': '{0} meses',
+        'year': 'um ano',
+        'years': '{0} anos',
+    }
+
+    month_names = ['', 'janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho',
+        'agosto', 'setembro', 'outubro', 'novembro', 'dezembro']
+    month_abbreviations = ['', 'jan', 'fev', 'mar', 'abr', 'maio', 'jun', 'jul', 'ago',
+        'set', 'out', 'nov', 'dez']
+
+    day_names = ['', 'segunda-feira', 'terça-feira', 'quarta-feira', 'quinta-feira', 'sexta-feira',
+        'sábado', 'domingo']
+    day_abbreviations = ['', 'seg', 'ter', 'qua', 'qui', 'sex', 'sab', 'dom']
+
+
+class BrazilianPortugueseLocale(PortugueseLocale):
+    names = ['pt_br']
+
+    past = 'fazem {0}'
+
+
+class TagalogLocale(Locale):
+
+    names = ['tl']
+
+    past = 'nakaraang {0}'
+    future = '{0} mula ngayon'
+
+    timeframes = {
+        'now': 'ngayon lang',
+        'seconds': 'segundo',
+        'minute': 'isang minuto',
+        'minutes': '{0} minuto',
+        'hour': 'isang oras',
+        'hours': '{0} oras',
+        'day': 'isang araw',
+        'days': '{0} araw',
+        'month': 'isang buwan',
+        'months': '{0} buwan',
+        'year': 'isang taon',
+        'years': '{0} taon',
+    }
+
+    month_names = ['', 'Enero', 'Pebrero', 'Marso', 'Abril', 'Mayo', 'Hunyo', 'Hulyo',
+        'Agosto', 'Setyembre', 'Oktubre', 'Nobyembre', 'Disyembre']
+    month_abbreviations = ['', 'Ene', 'Peb', 'Mar', 'Abr', 'May', 'Hun', 'Hul', 'Ago',
+        'Set', 'Okt', 'Nob', 'Dis']
+
+    day_names = ['', 'Lunes', 'Martes', 'Miyerkules', 'Huwebes', 'Biyernes', 'Sabado', 'Linggo']
+    day_abbreviations = ['', 'Lun', 'Mar', 'Miy', 'Huw', 'Biy', 'Sab', 'Lin']
+
+
+class VietnameseLocale(Locale):
+
+    names = ['vi', 'vi_vn']
+
+    past = '{0} trước'
+    future = '{0} nữa'
+
+    timeframes = {
+        'now': 'hiện tại',
+        'seconds': 'giây',
+        'minute': 'một phút',
+        'minutes': '{0} phút',
+        'hour': 'một giờ',
+        'hours': '{0} giờ',
+        'day': 'một ngày',
+        'days': '{0} ngày',
+        'month': 'một tháng',
+        'months': '{0} tháng',
+        'year': 'một năm',
+        'years': '{0} năm',
+    }
+
+    month_names = ['', 'Tháng Một', 'Tháng Hai', 'Tháng Ba', 'Tháng Tư', 'Tháng Năm', 'Tháng Sáu', 'Tháng Bảy',
+        'Tháng Tám', 'Tháng Chín', 'Tháng Mười', 'Tháng Mười Một', 'Tháng Mười Hai']
+    month_abbreviations = ['', 'Tháng 1', 'Tháng 2', 'Tháng 3', 'Tháng 4', 'Tháng 5', 'Tháng 6', 'Tháng 7', 'Tháng 8',
+        'Tháng 9', 'Tháng 10', 'Tháng 11', 'Tháng 12']
+
+    day_names = ['', 'Thứ Hai', 'Thứ Ba', 'Thứ Tư', 'Thứ Năm', 'Thứ Sáu', 'Thứ Bảy', 'Chủ Nhật']
+    day_abbreviations = ['', 'Thứ 2', 'Thứ 3', 'Thứ 4', 'Thứ 5', 'Thứ 6', 'Thứ 7', 'CN']
+
+
+class TurkishLocale(Locale):
+
+    names = ['tr', 'tr_tr']
+
+    past = '{0} önce'
+    future = '{0} sonra'
+
+    timeframes = {
+        'now': 'şimdi',
+        'seconds': 'saniye',
+        'minute': 'bir dakika',
+        'minutes': '{0} dakika',
+        'hour': 'bir saat',
+        'hours': '{0} saat',
+        'day': 'bir gün',
+        'days': '{0} gün',
+        'month': 'bir ay',
+        'months': '{0} ay',
+        'year': 'yıl',
+        'years': '{0} yıl',
+    }
+
+    month_names = ['', 'Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz',
+        'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık']
+    month_abbreviations = ['', 'Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu',
+        'Eyl', 'Eki', 'Kas', 'Ara']
+
+    day_names = ['', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar']
+    day_abbreviations = ['', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt', 'Paz']
+
+
+class AzerbaijaniLocale(Locale):
+
+    names = ['az', 'az_az']
+
+    past = '{0} əvvəl'
+    future = '{0} sonra'
+
+    timeframes = {
+        'now': 'indi',
+        'seconds': 'saniyə',
+        'minute': 'bir dəqiqə',
+        'minutes': '{0} dəqiqə',
+        'hour': 'bir saat',
+        'hours': '{0} saat',
+        'day': 'bir gün',
+        'days': '{0} gün',
+        'month': 'bir ay',
+        'months': '{0} ay',
+        'year': 'il',
+        'years': '{0} il',
+    }
+
+    month_names = ['', 'Yanvar', 'Fevral', 'Mart', 'Aprel', 'May', 'İyun', 'İyul',
+        'Avqust', 'Sentyabr', 'Oktyabr', 'Noyabr', 'Dekabr']
+    month_abbreviations = ['', 'Yan', 'Fev', 'Mar', 'Apr', 'May', 'İyn', 'İyl', 'Avq',
+        'Sen', 'Okt', 'Noy', 'Dek']
+
+    day_names = ['', 'Bazar ertəsi', 'Çərşənbə axşamı', 'Çərşənbə', 'Cümə axşamı', 'Cümə', 'Şənbə', 'Bazar']
+    day_abbreviations = ['', 'Ber', 'Çax', 'Çər', 'Cax', 'Cüm', 'Şnb', 'Bzr']
+
+
+class ArabicLocale(Locale):
+
+    names = ['ar', 'ar_eg']
+
+    past = 'منذ {0}'
+    future = 'خلال {0}'
+
+    timeframes = {
+        'now': 'الآن',
+        'seconds': 'ثوان',
+        'minute': 'دقيقة',
+        'minutes': '{0} دقائق',
+        'hour': 'ساعة',
+        'hours': '{0} ساعات',
+        'day': 'يوم',
+        'days': '{0} أيام',
+        'month': 'شهر',
+        'months': '{0} شهور',
+        'year': 'سنة',
+        'years': '{0} سنوات',
+    }
+
+    month_names = ['', 'يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو',
+        'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر']
+    month_abbreviations = ['', 'يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو',
+        'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر']
+
+    day_names = ['', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت', 'الأحد']
+    day_abbreviations = ['', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت', 'أحد']
+
+
+class IcelandicLocale(Locale):
+
+    def _format_timeframe(self, timeframe, delta):
+
+        timeframe = self.timeframes[timeframe]
+        if delta < 0:
+            timeframe = timeframe[0]
+        elif delta > 0:
+            timeframe = timeframe[1]
+
+        return timeframe.format(abs(delta))
+
+    names = ['is', 'is_is']
+
+    past = 'fyrir {0} síðan'
+    future = 'eftir {0}'
+
+    timeframes = {
+        'now':     'rétt í þessu',
+        'seconds': ('nokkrum sekúndum', 'nokkrar sekúndur'),
+        'minute':  ('einni mínútu', 'eina mínútu'),
+        'minutes': ('{0} mínútum', '{0} mínútur'),
+        'hour':    ('einum tíma', 'einn tíma'),
+        'hours':   ('{0} tímum', '{0} tíma'),
+        'day':     ('einum degi', 'einn dag'),
+        'days':    ('{0} dögum', '{0} daga'),
+        'month':   ('einum mánuði', 'einn mánuð'),
+        'months':  ('{0} mánuðum', '{0} mánuði'),
+        'year':    ('einu ári', 'eitt ár'),
+        'years':   ('{0} árum', '{0} ár'),
+    }
+
+    meridians = {
+        'am': 'f.h.',
+        'pm': 'e.h.',
+        'AM': 'f.h.',
+        'PM': 'e.h.',
+    }
+
+    month_names = ['', 'janúar', 'febrúar', 'mars', 'apríl', 'maí', 'júní',
+        'júlí', 'ágúst', 'september', 'október', 'nóvember', 'desember']
+    month_abbreviations = ['', 'jan', 'feb', 'mar', 'apr', 'maí', 'jún',
+        'júl', 'ágú', 'sep', 'okt', 'nóv', 'des']
+
+    day_names = ['', 'mánudagur', 'þriðjudagur', 'miðvikudagur', 'fimmtudagur',
+        'föstudagur', 'laugardagur', 'sunnudagur']
+    day_abbreviations = ['', 'mán', 'þri', 'mið', 'fim', 'fös', 'lau', 'sun']
+
+
+class DanishLocale(Locale):
+
+    names = ['da', 'da_dk']
+
+    past = 'for {0} siden'
+    future = 'efter {0}'
+
+    timeframes = {
+        'now':     'lige nu',
+        'seconds': 'et par sekunder',
+        'minute':  'et minut',
+        'minutes': '{0} minutter',
+        'hour':    'en time',
+        'hours':   '{0} timer',
+        'day':     'en dag',
+        'days':    '{0} dage',
+        'month':   'en måned',
+        'months':  '{0} måneder',
+        'year':    'et år',
+        'years':   '{0} år',
+    }
+
+    month_names = ['', 'januar', 'februar', 'marts', 'april', 'maj', 'juni',
+        'juli', 'august', 'september', 'oktober', 'november', 'december']
+    month_abbreviations = ['', 'jan', 'feb', 'mar', 'apr', 'maj', 'jun',
+        'jul', 'aug', 'sep', 'okt', 'nov', 'dec']
+
+    day_names = ['', 'mandag', 'tirsdag', 'onsdag', 'torsdag', 'fredag',
+        'lørdag', 'søndag']
+    day_abbreviations = ['', 'man', 'tir', 'ons', 'tor', 'fre', 'lør', 'søn']
+
+
+class MalayalamLocale(Locale):
+
+    names = ['ml']
+
+    past = '{0} മുമ്പ്'
+    future = '{0} ശേഷം'
+
+    timeframes = {
+        'now': 'ഇപ്പോൾ',
+        'seconds': 'സെക്കന്റ്‌',
+        'minute': 'ഒരു മിനിറ്റ്',
+        'minutes': '{0} മിനിറ്റ്',
+        'hour': 'ഒരു മണിക്കൂർ',
+        'hours': '{0} മണിക്കൂർ',
+        'day': 'ഒരു ദിവസം ',
+        'days': '{0} ദിവസം ',
+        'month': 'ഒരു മാസം ',
+        'months': '{0} മാസം ',
+        'year': 'ഒരു വർഷം ',
+        'years': '{0} വർഷം ',
+    }
+
+    meridians = {
+        'am': 'രാവിലെ',
+        'pm': 'ഉച്ചക്ക് ശേഷം',
+        'AM': 'രാവിലെ',
+        'PM': 'ഉച്ചക്ക് ശേഷം',
+    }
+
+    month_names = ['', 'ജനുവരി', 'ഫെബ്രുവരി', 'മാർച്ച്‌', 'ഏപ്രിൽ ', 'മെയ്‌ ', 'ജൂണ്‍', 'ജൂലൈ',
+                   'ഓഗസ്റ്റ്‌', 'സെപ്റ്റംബർ', 'ഒക്ടോബർ', 'നവംബർ', 'ഡിസംബർ']
+    month_abbreviations = ['', 'ജനു', 'ഫെബ് ', 'മാർ', 'ഏപ്രിൽ', 'മേയ്', 'ജൂണ്‍', 'ജൂലൈ', 'ഓഗസ്റ',
+                           'സെപ്റ്റ', 'ഒക്ടോ', 'നവം', 'ഡിസം']
+
+    day_names = ['', 'തിങ്കള്‍', 'ചൊവ്വ', 'ബുധന്‍', 'വ്യാഴം', 'വെള്ളി', 'ശനി', 'ഞായര്‍']
+    day_abbreviations = ['', 'തിങ്കള്‍', 'ചൊവ്വ', 'ബുധന്‍', 'വ്യാഴം', 'വെള്ളി', 'ശനി', 'ഞായര്‍']
+
+
+class HindiLocale(Locale):
+
+    names = ['hi']
+
+    past = '{0} पहले'
+    future = '{0} बाद'
+
+    timeframes = {
+        'now': 'अभी',
+        'seconds': 'सेकंड्',
+        'minute': 'एक मिनट ',
+        'minutes': '{0} मिनट ',
+        'hour': 'एक घंटा',
+        'hours': '{0} घंटे',
+        'day': 'एक दिन',
+        'days': '{0} दिन',
+        'month': 'एक माह ',
+        'months': '{0} महीने ',
+        'year': 'एक वर्ष ',
+        'years': '{0} साल ',
+    }
+
+    meridians = {
+        'am': 'सुबह',
+        'pm': 'शाम',
+        'AM': 'सुबह',
+        'PM': 'शाम',
+    }
+
+    month_names = ['', 'जनवरी', 'फरवरी', 'मार्च', 'अप्रैल ', 'मई', 'जून', 'जुलाई',
+                   'अगस्त', 'सितंबर', 'अक्टूबर', 'नवंबर', 'दिसंबर']
+    month_abbreviations = ['', 'जन', 'फ़र', 'मार्च', 'अप्रै', 'मई', 'जून', 'जुलाई', 'आग',
+                           'सित', 'अकत', 'नवे', 'दिस']
+
+    day_names = ['', 'सोमवार', 'मंगलवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार', 'रविवार']
+    day_abbreviations = ['', 'सोम', 'मंगल', 'बुध', 'गुरुवार', 'शुक्र', 'शनि', 'रवि']
+
+class CzechLocale(Locale):
+    names = ['cs', 'cs_cz']
+
+    timeframes = {
+        'now': 'Teď',
+        'seconds': {
+            'past': '{0} sekundami',
+            'future': ['{0} sekundy', '{0} sekund']
+        },
+        'minute': {'past': 'minutou', 'future': 'minutu', 'zero': '{0} minut'},
+        'minutes': {
+            'past': '{0} minutami',
+            'future': ['{0} minuty', '{0} minut']
+        },
+        'hour': {'past': 'hodinou', 'future': 'hodinu', 'zero': '{0} hodin'},
+        'hours': {
+            'past': '{0} hodinami',
+            'future': ['{0} hodiny', '{0} hodin']
+        },
+        'day': {'past': 'dnem', 'future': 'den', 'zero': '{0} dnů'},
+        'days': {
+            'past': '{0} dny',
+            'future': ['{0} dny', '{0} dnů']
+        },
+        'month': {'past': 'měsícem', 'future': 'měsíc', 'zero': '{0} měsíců'},
+        'months': {
+            'past': '{0} měsíci',
+            'future': ['{0} měsíce', '{0} měsíců']
+        },
+        'year': {'past': 'rokem', 'future': 'rok', 'zero': '{0} let'},
+        'years': {
+            'past': '{0} lety',
+            'future': ['{0} roky', '{0} let']
+        }
+    }
+
+    past = 'Před {0}'
+    future = 'Za {0}'
+
+    month_names = ['', 'leden', 'únor', 'březen', 'duben', 'květen', 'červen',
+        'červenec', 'srpen', 'září', 'říjen', 'listopad', 'prosinec']
+    month_abbreviations = ['', 'led', 'úno', 'bře', 'dub', 'kvě', 'čvn', 'čvc',
+        'srp', 'zář', 'říj', 'lis', 'pro']
+
+    day_names = ['', 'pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek',
+        'sobota', 'neděle']
+    day_abbreviations = ['', 'po', 'út', 'st', 'čt', 'pá', 'so', 'ne']
+
+
+    def _format_timeframe(self, timeframe, delta):
+        '''Czech aware time frame format function, takes into account
+        the differences between past and future forms.'''
+        form = self.timeframes[timeframe]
+        if isinstance(form, dict):
+            if delta == 0:
+                form = form['zero'] # And *never* use 0 in the singular!
+            elif delta > 0:
+                form = form['future']
+            else:
+                form = form['past']
+        delta = abs(delta)
+
+        if isinstance(form, list):
+            if 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20):
+                form = form[0]
+            else:
+                form = form[1]
+
+        return form.format(delta)
+
+
+class SlovakLocale(Locale):
+    names = ['sk', 'sk_sk']
+
+    timeframes = {
+        'now': 'Teraz',
+        'seconds': {
+            'past': 'pár sekundami',
+            'future': ['{0} sekundy', '{0} sekúnd']
+        },
+        'minute': {'past': 'minútou', 'future': 'minútu', 'zero': '{0} minút'},
+        'minutes': {
+            'past': '{0} minútami',
+            'future': ['{0} minúty', '{0} minút']
+        },
+        'hour': {'past': 'hodinou', 'future': 'hodinu', 'zero': '{0} hodín'},
+        'hours': {
+            'past': '{0} hodinami',
+            'future': ['{0} hodiny', '{0} hodín']
+        },
+        'day': {'past': 'dňom', 'future': 'deň', 'zero': '{0} dní'},
+        'days': {
+            'past': '{0} dňami',
+            'future': ['{0} dni', '{0} dní']
+        },
+        'month': {'past': 'mesiacom', 'future': 'mesiac', 'zero': '{0} mesiacov'},
+        'months': {
+            'past': '{0} mesiacmi',
+            'future': ['{0} mesiace', '{0} mesiacov']
+        },
+        'year': {'past': 'rokom', 'future': 'rok', 'zero': '{0} rokov'},
+        'years': {
+            'past': '{0} rokmi',
+            'future': ['{0} roky', '{0} rokov']
+        }
+    }
+
+    past = 'Pred {0}'
+    future = 'O {0}'
+
+    month_names = ['', 'január', 'február', 'marec', 'apríl', 'máj', 'jún',
+        'júl', 'august', 'september', 'október', 'november', 'december']
+    month_abbreviations = ['', 'jan', 'feb', 'mar', 'apr', 'máj', 'jún', 'júl',
+        'aug', 'sep', 'okt', 'nov', 'dec']
+
+    day_names = ['', 'pondelok', 'utorok', 'streda', 'štvrtok', 'piatok',
+        'sobota', 'nedeľa']
+    day_abbreviations = ['', 'po', 'ut', 'st', 'št', 'pi', 'so', 'ne']
+
+
+    def _format_timeframe(self, timeframe, delta):
+        '''Slovak aware time frame format function, takes into account
+        the differences between past and future forms.'''
+        form = self.timeframes[timeframe]
+        if isinstance(form, dict):
+            if delta == 0:
+                form = form['zero']  # And *never* use 0 in the singular!
+            elif delta > 0:
+                form = form['future']
+            else:
+                form = form['past']
+        delta = abs(delta)
+
+        if isinstance(form, list):
+            if 2 <= delta % 10 <= 4 and (delta % 100 < 10 or delta % 100 >= 20):
+                form = form[0]
+            else:
+                form = form[1]
+
+        return form.format(delta)
+
+
+class FarsiLocale(Locale):
+
+    names = ['fa', 'fa_ir']
+
+    past = '{0} قبل'
+    future = 'در {0}'
+
+    timeframes = {
+        'now': 'اکنون',
+        'seconds': 'ثانیه',
+        'minute': 'یک دقیقه',
+        'minutes': '{0} دقیقه',
+        'hour': 'یک ساعت',
+        'hours': '{0} ساعت',
+        'day': 'یک روز',
+        'days': '{0} روز',
+        'month': 'یک ماه',
+        'months': '{0} ماه',
+        'year': 'یک سال',
+        'years': '{0} سال',
+    }
+
+    meridians = {
+        'am': 'قبل از ظهر',
+        'pm': 'بعد از ظهر',
+        'AM': 'قبل از ظهر',
+        'PM': 'بعد از ظهر',
+    }
+
+    month_names = ['', 'January', 'February', 'March', 'April', 'May', 'June', 'July',
+        'August', 'September', 'October', 'November', 'December']
+    month_abbreviations = ['', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug',
+        'Sep', 'Oct', 'Nov', 'Dec']
+
+    day_names = ['', 'دو شنبه', 'سه شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه', 'یکشنبه']
+    day_abbreviations = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
+
+
+class MacedonianLocale(Locale):
+    names = ['mk', 'mk_mk']
+
+    past = 'пред {0}'
+    future = 'за {0}'
+
+    timeframes = {
+        'now': 'сега',
+        'seconds': 'секунди',
+        'minute': 'една минута',
+        'minutes': '{0} минути',
+        'hour': 'еден саат',
+        'hours': '{0} саати',
+        'day': 'еден ден',
+        'days': '{0} дена',
+        'month': 'еден месец',
+        'months': '{0} месеци',
+        'year': 'една година',
+        'years': '{0} години',
+    }
+
+    meridians = {
+        'am': 'дп',
+        'pm': 'пп',
+        'AM': 'претпладне',
+        'PM': 'попладне',
+    }
+
+    month_names = ['', 'Јануари', 'Февруари', 'Март', 'Април', 'Мај', 'Јуни', 'Јули', 'Август', 'Септември', 'Октомври',
+                   'Ноември', 'Декември']
+    month_abbreviations = ['', 'Јан.', ' Фев.', ' Мар.', ' Апр.', ' Мај', ' Јун.', ' Јул.', ' Авг.', ' Септ.', ' Окт.',
+                           ' Ноем.', ' Декем.']
+
+    day_names = ['', 'Понеделник', ' Вторник', ' Среда', ' Четврток', ' Петок', ' Сабота', ' Недела']
+    day_abbreviations = ['', 'Пон.', ' Вт.', ' Сре.', ' Чет.', ' Пет.', ' Саб.', ' Нед.']
+
+
+class HebrewLocale(Locale):
+
+    names = ['he', 'he_IL']
+
+    past = 'לפני {0}'
+    future = 'בעוד {0}'
+
+    timeframes = {
+        'now': 'הרגע',
+        'seconds': 'שניות',
+        'minute': 'דקה',
+        'minutes': '{0} דקות',
+        'hour': 'שעה',
+        'hours': '{0} שעות',
+        '2-hours': 'שעתיים',
+        'day': 'יום',
+        'days': '{0} ימים',
+        '2-days': 'יומיים',
+        'month': 'חודש',
+        'months': '{0} חודשים',
+        '2-months': 'חודשיים',
+        'year': 'שנה',
+        'years': '{0} שנים',
+        '2-years': 'שנתיים',
+    }
+
+    meridians = {
+        'am': 'לפנ"צ',
+        'pm': 'אחר"צ',
+        'AM': 'לפני הצהריים',
+        'PM': 'אחרי הצהריים',
+    }
+
+    month_names = ['', 'ינואר', 'פברואר', 'מרץ', 'אפריל', 'מאי', 'יוני', 'יולי',
+                   'אוגוסט', 'ספטמבר', 'אוקטובר', 'נובמבר', 'דצמבר']
+    month_abbreviations = ['', 'ינו׳', 'פבר׳', 'מרץ', 'אפר׳', 'מאי', 'יוני', 'יולי', 'אוג׳',
+                           'ספט׳', 'אוק׳', 'נוב׳', 'דצמ׳']
+
+    day_names = ['', 'שני', 'שלישי', 'רביעי', 'חמישי', 'שישי', 'שבת', 'ראשון']
+    day_abbreviations = ['', 'ב׳', 'ג׳', 'ד׳', 'ה׳', 'ו׳', 'ש׳', 'א׳']
+
+    def _format_timeframe(self, timeframe, delta):
+        '''Hebrew couple of <timeframe> aware'''
+        couple = '2-{0}'.format(timeframe)
+        if abs(delta) == 2 and couple in self.timeframes:
+            return self.timeframes[couple].format(abs(delta))
+        else:
+            return self.timeframes[timeframe].format(abs(delta))
+
+class MarathiLocale(Locale):
+
+    names = ['mr']
+
+    past = '{0} आधी'
+    future = '{0} नंतर'
+
+    timeframes = {
+        'now': 'सद्य',
+        'seconds': 'सेकंद',
+        'minute': 'एक मिनिट ',
+        'minutes': '{0} मिनिट ',
+        'hour': 'एक तास',
+        'hours': '{0} तास',
+        'day': 'एक दिवस',
+        'days': '{0} दिवस',
+        'month': 'एक महिना ',
+        'months': '{0} महिने ',
+        'year': 'एक वर्ष ',
+        'years': '{0} वर्ष ',
+    }
+
+    meridians = {
+        'am': 'सकाळ',
+        'pm': 'संध्याकाळ',
+        'AM': 'सकाळ',
+        'PM': 'संध्याकाळ',
+    }
+
+    month_names = ['', 'जानेवारी', 'फेब्रुवारी', 'मार्च', 'एप्रिल', 'मे', 'जून', 'जुलै',
+                   'अॉगस्ट', 'सप्टेंबर', 'अॉक्टोबर', 'नोव्हेंबर', 'डिसेंबर']
+    month_abbreviations = ['', 'जान', 'फेब्रु', 'मार्च', 'एप्रि', 'मे', 'जून', 'जुलै', 'अॉग',
+                           'सप्टें', 'अॉक्टो', 'नोव्हें', 'डिसें']
+
+    day_names = ['', 'सोमवार', 'मंगळवार', 'बुधवार', 'गुरुवार', 'शुक्रवार', 'शनिवार', 'रविवार']
+    day_abbreviations = ['', 'सोम', 'मंगळ', 'बुध', 'गुरु', 'शुक्र', 'शनि', 'रवि']
+
+def _map_locales():
+
+    locales = {}
+
+    for cls_name, cls in inspect.getmembers(sys.modules[__name__], inspect.isclass):
+        if issubclass(cls, Locale):
+            for name in cls.names:
+                locales[name.lower()] = cls
+
+    return locales
+
+class CatalanLocale(Locale):
+    names = ['ca', 'ca_es', 'ca_ad', 'ca_fr', 'ca_it']
+    past = 'Fa {0}'
+    future = 'En {0}'
+
+    timeframes = {
+        'now': 'Ara mateix',
+        'seconds': 'segons',
+        'minute': '1 minut',
+        'minutes': '{0} minuts',
+        'hour': 'una hora',
+        'hours': '{0} hores',
+        'day': 'un dia',
+        'days': '{0} dies',
+        'month': 'un mes',
+        'months': '{0} mesos',
+        'year': 'un any',
+        'years': '{0} anys',
+    }
+
+    month_names = ['', 'Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre']
+    month_abbreviations = ['', 'Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre']
+    day_names = ['', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte', 'Diumenge']
+    day_abbreviations = ['', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte', 'Diumenge']
+
+class BasqueLocale(Locale):
+    names = ['eu', 'eu_eu']
+    past = 'duela {0}'
+    future = '{0}' # I don't know what's the right phrase in Basque for the future.
+
+    timeframes = {
+        'now': 'Orain',
+        'seconds': 'segundu',
+        'minute': 'minutu bat',
+        'minutes': '{0} minutu',
+        'hour': 'ordu bat',
+        'hours': '{0} ordu',
+        'day': 'egun bat',
+        'days': '{0} egun',
+        'month': 'hilabete bat',
+        'months': '{0} hilabet',
+        'year': 'urte bat',
+        'years': '{0} urte',
+    }
+
+    month_names = ['', 'urtarrilak', 'otsailak', 'martxoak', 'apirilak', 'maiatzak', 'ekainak', 'uztailak', 'abuztuak', 'irailak', 'urriak', 'azaroak', 'abenduak']
+    month_abbreviations = ['', 'urt', 'ots', 'mar', 'api', 'mai', 'eka', 'uzt', 'abu', 'ira', 'urr', 'aza', 'abe']
+    day_names = ['', 'asteleehna', 'asteartea', 'asteazkena', 'osteguna', 'ostirala', 'larunbata', 'igandea']
+    day_abbreviations = ['', 'al', 'ar', 'az', 'og', 'ol', 'lr', 'ig']
+
+
+class HungarianLocale(Locale):
+
+    names = ['hu', 'hu_hu']
+
+    past = '{0} ezelőtt'
+    future = '{0} múlva'
+
+    timeframes = {
+        'now': 'éppen most',
+        'seconds': {
+            'past': 'másodpercekkel',
+            'future': 'pár másodperc'
+        },
+        'minute': {'past': 'egy perccel', 'future': 'egy perc'},
+        'minutes': {'past': '{0} perccel', 'future': '{0} perc'},
+        'hour': {'past': 'egy órával', 'future': 'egy óra'},
+        'hours': {'past': '{0} órával', 'future': '{0} óra'},
+        'day': {
+            'past': 'egy nappal',
+            'future': 'egy nap'
+        },
+        'days': {
+            'past': '{0} nappal',
+            'future': '{0} nap'
+        },
+        'month': {'past': 'egy hónappal', 'future': 'egy hónap'},
+        'months': {'past': '{0} hónappal', 'future': '{0} hónap'},
+        'year': {'past': 'egy évvel', 'future': 'egy év'},
+        'years': {'past': '{0} évvel', 'future': '{0} év'},
+    }
+
+    month_names = ['', 'január', 'február', 'március', 'április', 'május',
+                   'június', 'július', 'augusztus', 'szeptember',
+                   'október', 'november', 'december']
+    month_abbreviations = ['', 'jan', 'febr', 'márc', 'ápr', 'máj', 'jún',
+                           'júl', 'aug', 'szept', 'okt', 'nov', 'dec']
+
+    day_names = ['', 'hétfő', 'kedd', 'szerda', 'csütörtök', 'péntek',
+                 'szombat', 'vasárnap']
+    day_abbreviations = ['', 'hét', 'kedd', 'szer', 'csüt', 'pént',
+                         'szom', 'vas']
+
+    meridians = {
+        'am': 'de',
+        'pm': 'du',
+        'AM': 'DE',
+        'PM': 'DU',
+    }
+
+    def _format_timeframe(self, timeframe, delta):
+        form = self.timeframes[timeframe]
+
+        if isinstance(form, dict):
+            if delta > 0:
+                form = form['future']
+            else:
+                form = form['past']
+
+        return form.format(abs(delta))
+
+
+class EsperantoLocale(Locale):
+    names = ['eo', 'eo_xx']
+    past = 'antaŭ {0}'
+    future = 'post {0}'
+
+    timeframes = {
+        'now': 'nun',
+        'seconds': 'kelkaj sekundoj',
+        'minute': 'unu minuto',
+        'minutes': '{0} minutoj',
+        'hour': 'un horo',
+        'hours': '{0} horoj',
+        'day': 'unu tago',
+        'days': '{0} tagoj',
+        'month': 'unu monato',
+        'months': '{0} monatoj',
+        'year': 'unu jaro',
+        'years': '{0} jaroj',
+    }
+
+    month_names = ['', 'januaro', 'februaro', 'marto', 'aprilo', 'majo',
+                   'junio', 'julio', 'aŭgusto', 'septembro', 'oktobro',
+                   'novembro', 'decembro']
+    month_abbreviations = ['', 'jan', 'feb', 'mar', 'apr', 'maj', 'jun',
+                           'jul', 'aŭg', 'sep', 'okt', 'nov', 'dec']
+
+    day_names = ['', 'lundo', 'mardo', 'merkredo', 'ĵaŭdo', 'vendredo',
+                 'sabato', 'dimanĉo']
+    day_abbreviations = ['', 'lun', 'mar', 'mer', 'ĵaŭ', 'ven',
+                         'sab', 'dim']
+
+    meridians = {
+        'am': 'atm',
+        'pm': 'ptm',
+        'AM': 'ATM',
+        'PM': 'PTM',
+    }
+
+    ordinal_day_re = r'((?P<value>[1-3]?[0-9](?=a))a)'
+
+    def _ordinal_number(self, n):
+        return '{0}a'.format(n)
+
+
+class ThaiLocale(Locale):
+
+    names = ['th', 'th_th']
+
+    past = '{0}{1}ที่ผ่านมา'
+    future = 'ในอีก{1}{0}'
+
+    timeframes = {
+        'now': 'ขณะนี้',
+        'seconds': 'ไม่กี่วินาที',
+        'minute': '1 นาที',
+        'minutes': '{0} นาที',
+        'hour': '1 ชั่วโมง',
+        'hours': '{0} ชั่วโมง',
+        'day': '1 วัน',
+        'days': '{0} วัน',
+        'month': '1 เดือน',
+        'months': '{0} เดือน',
+        'year': '1 ปี',
+        'years': '{0} ปี',
+    }
+
+    month_names = ['', 'มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน',
+                   'พฤษภาคม', 'มิถุนายน', 'กรกฏาคม', 'สิงหาคม',
+                   'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม']
+    month_abbreviations = ['', 'ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.',
+                           'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.',
+                           'พ.ย.', 'ธ.ค.']
+
+    day_names = ['', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัสบดี', 'ศุกร์',
+                 'เสาร์', 'อาทิตย์']
+    day_abbreviations = ['', 'จ', 'อ', 'พ', 'พฤ', 'ศ', 'ส', 'อา']
+
+    meridians = {
+        'am': 'am',
+        'pm': 'pm',
+        'AM': 'AM',
+        'PM': 'PM',
+    }
+
+    BE_OFFSET = 543
+
+    def year_full(self, year):
+        '''Thai always use Buddhist Era (BE) which is CE + 543'''
+        year += self.BE_OFFSET
+        return '{0:04d}'.format(year)
+
+    def year_abbreviation(self, year):
+        '''Thai always use Buddhist Era (BE) which is CE + 543'''
+        year += self.BE_OFFSET
+        return '{0:04d}'.format(year)[2:]
+
+    def _format_relative(self, humanized, timeframe, delta):
+        '''Thai normally doesn't have any space between words'''
+        if timeframe == 'now':
+            return humanized
+        space = '' if timeframe == 'seconds' else ' '
+        direction = self.past if delta < 0 else self.future
+
+        return direction.format(humanized, space)
+
+
+
+class BengaliLocale(Locale):
+
+    names = ['bn', 'bn_bd', 'bn_in']
+
+    past = '{0} আগে'
+    future = '{0} পরে'
+
+    timeframes = {
+        'now': 'এখন',
+        'seconds': 'সেকেন্ড',
+        'minute': 'এক মিনিট',
+        'minutes': '{0} মিনিট',
+        'hour': 'এক ঘণ্টা',
+        'hours': '{0} ঘণ্টা',
+        'day': 'এক দিন',
+        'days': '{0} দিন',
+        'month': 'এক মাস',
+        'months': '{0} মাস ',
+        'year': 'এক বছর',
+        'years': '{0} বছর',
+    }
+
+    meridians = {
+        'am': 'সকাল',
+        'pm': 'বিকাল',
+        'AM': 'সকাল',
+        'PM': 'বিকাল',
+    }
+
+    month_names = ['', 'জানুয়ারি', 'ফেব্রুয়ারি', 'মার্চ', 'এপ্রিল', 'মে', 'জুন', 'জুলাই',
+                   'আগস্ট', 'সেপ্টেম্বর', 'অক্টোবর', 'নভেম্বর', 'ডিসেম্বর']
+    month_abbreviations = ['', 'জানু', 'ফেব', 'মার্চ', 'এপ্রি', 'মে', 'জুন', 'জুল',
+                           'অগা','সেপ্ট', 'অক্টো', 'নভে', 'ডিসে']
+
+    day_names = ['', 'সোমবার', 'মঙ্গলবার', 'বুধবার', 'বৃহস্পতিবার', 'শুক্রবার', 'শনিবার', 'রবিবার']
+    day_abbreviations = ['', 'সোম', 'মঙ্গল', 'বুধ', 'বৃহঃ', 'শুক্র', 'শনি', 'রবি']
+
+    def _ordinal_number(self, n):
+        if n > 10 or n == 0:
+            return '{0}তম'.format(n)
+        if n in [1, 5, 7, 8, 9, 10]:
+            return '{0}ম'.format(n)
+        if n in [2, 3]:
+            return '{0}য়'.format(n)
+        if n == 4:
+            return '{0}র্থ'.format(n)
+        if n == 6:
+            return '{0}ষ্ঠ'.format(n)
+
+
+class RomanshLocale(Locale):
+
+    names = ['rm', 'rm_ch']
+
+    past = 'avant {0}'
+    future = 'en {0}'
+
+    timeframes = {
+        'now': 'en quest mument',
+        'seconds': 'secundas',
+        'minute': 'ina minuta',
+        'minutes': '{0} minutas',
+        'hour': 'in\'ura',
+        'hours': '{0} ura',
+        'day': 'in di',
+        'days': '{0} dis',
+        'month': 'in mais',
+        'months': '{0} mais',
+        'year': 'in onn',
+        'years': '{0} onns',
+    }
+
+    month_names = [
+        '', 'schaner', 'favrer', 'mars', 'avrigl', 'matg', 'zercladur',
+        'fanadur', 'avust', 'settember', 'october', 'november', 'december'
+    ]
+
+    month_abbreviations = [
+        '', 'schan', 'fav', 'mars', 'avr', 'matg', 'zer', 'fan', 'avu',
+        'set', 'oct', 'nov', 'dec'
+    ]
+
+    day_names = [
+        '', 'glindesdi', 'mardi', 'mesemna', 'gievgia', 'venderdi',
+        'sonda', 'dumengia'
+    ]
+
+    day_abbreviations = [
+        '', 'gli', 'ma', 'me', 'gie', 've', 'so', 'du'
+    ]
+
+
+class SwissLocale(Locale):
+
+    names = ['de', 'de_ch']
+
+    past = 'vor {0}'
+    future = 'in {0}'
+
+    timeframes = {
+            'now': 'gerade eben',
+            'seconds':  'Sekunden',
+            'minute': 'einer Minute',
+            'minutes': '{0} Minuten',
+            'hour': 'einer Stunde',
+            'hours': '{0} Stunden',
+            'day': 'einem Tag',
+            'days': '{0} Tage',
+            'month': 'einem Monat',
+            'months': '{0} Monaten',
+            'year': 'einem Jahr',
+            'years': '{0} Jahren',
+        }
+
+    month_names = [
+            '', 'Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli',
+            'August', 'September', 'Oktober', 'November', 'Dezember'
+        ]
+
+    month_abbreviations = [
+            '', 'Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep',
+            'Okt', 'Nov', 'Dez'
+        ]
+
+    day_names = [
+            '', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag',
+            'Samstag', 'Sonntag'
+        ]
+
+    day_abbreviations = [
+            '', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'
+        ]
+
+
+class RomanianLocale(Locale):
+    names = ['ro', 'ro_ro']
+
+    past = '{0} în urmă'
+    future = 'peste {0}'
+
+    timeframes = {
+        'now': 'acum',
+        'seconds': 'câteva secunde',
+        'minute': 'un minut',
+        'minutes': '{0} minute',
+        'hour': 'o oră',
+        'hours': '{0} ore',
+        'day': 'o zi',
+        'days': '{0} zile',
+        'month': 'o lună',
+        'months': '{0} luni',
+        'year': 'un an',
+        'years': '{0} ani',
+    }
+
+    month_names = ['', 'ianuarie', 'februarie', 'martie', 'aprilie', 'mai', 'iunie', 'iulie',
+                   'august', 'septembrie', 'octombrie', 'noiembrie', 'decembrie']
+    month_abbreviations = ['', 'ian', 'febr', 'mart', 'apr', 'mai', 'iun', 'iul', 'aug', 'sept', 'oct', 'nov', 'dec']
+
+    day_names = ['', 'luni', 'marți', 'miercuri', 'joi', 'vineri', 'sâmbătă', 'duminică']
+    day_abbreviations = ['', 'Lun', 'Mar', 'Mie', 'Joi', 'Vin', 'Sâm', 'Dum']
+
+
+class SlovenianLocale(Locale):
+    names = ['sl', 'sl_si']
+
+    past = 'pred {0}'
+    future = 'čez {0}'
+
+    timeframes = {
+        'now': 'zdaj',
+        'seconds': 'sekund',
+        'minute': 'minuta',
+        'minutes': '{0} minutami',
+        'hour': 'uro',
+        'hours': '{0} ur',
+        'day': 'dan',
+        'days': '{0} dni',
+        'month': 'mesec',
+        'months': '{0} mesecev',
+        'year': 'leto',
+        'years': '{0} let',
+    }
+
+    meridians = {
+        'am': '',
+        'pm': '',
+        'AM': '',
+        'PM': '',
+    }
+
+    month_names = [
+        '', 'Januar', 'Februar', 'Marec', 'April', 'Maj', 'Junij', 'Julij',
+        'Avgust', 'September', 'Oktober', 'November', 'December'
+    ]
+
+    month_abbreviations = [
+        '', 'Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Avg',
+        'Sep', 'Okt', 'Nov', 'Dec'
+    ]
+
+    day_names = [
+        '', 'Ponedeljek', 'Torek', 'Sreda', 'Četrtek', 'Petek', 'Sobota', 'Nedelja'
+    ]
+
+    day_abbreviations = [
+        '', 'Pon', 'Tor', 'Sre', 'Čet', 'Pet', 'Sob', 'Ned'
+    ]
+
+
+_locales = _map_locales()
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow/parser.py
@@ -0,0 +1,328 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from datetime import datetime
+from dateutil import tz
+import re
+from arrow import locales
+
+
+class ParserError(RuntimeError):
+    pass
+
+
+class DateTimeParser(object):
+
+    _FORMAT_RE = re.compile('(YYY?Y?|MM?M?M?|Do|DD?D?D?|d?d?d?d|HH?|hh?|mm?|ss?|S+|ZZ?Z?|a|A|X)')
+    _ESCAPE_RE = re.compile('\[[^\[\]]*\]')
+
+    _ONE_OR_MORE_DIGIT_RE = re.compile('\d+')
+    _ONE_OR_TWO_DIGIT_RE = re.compile('\d{1,2}')
+    _FOUR_DIGIT_RE = re.compile('\d{4}')
+    _TWO_DIGIT_RE = re.compile('\d{2}')
+    _TZ_RE = re.compile('[+\-]?\d{2}:?(\d{2})?')
+    _TZ_NAME_RE = re.compile('\w[\w+\-/]+')
+
+
+    _BASE_INPUT_RE_MAP = {
+        'YYYY': _FOUR_DIGIT_RE,
+        'YY': _TWO_DIGIT_RE,
+        'MM': _TWO_DIGIT_RE,
+        'M': _ONE_OR_TWO_DIGIT_RE,
+        'DD': _TWO_DIGIT_RE,
+        'D': _ONE_OR_TWO_DIGIT_RE,
+        'HH': _TWO_DIGIT_RE,
+        'H': _ONE_OR_TWO_DIGIT_RE,
+        'hh': _TWO_DIGIT_RE,
+        'h': _ONE_OR_TWO_DIGIT_RE,
+        'mm': _TWO_DIGIT_RE,
+        'm': _ONE_OR_TWO_DIGIT_RE,
+        'ss': _TWO_DIGIT_RE,
+        's': _ONE_OR_TWO_DIGIT_RE,
+        'X': re.compile('\d+'),
+        'ZZZ': _TZ_NAME_RE,
+        'ZZ': _TZ_RE,
+        'Z': _TZ_RE,
+        'S': _ONE_OR_MORE_DIGIT_RE,
+    }
+
+    MARKERS = ['YYYY', 'MM', 'DD']
+    SEPARATORS = ['-', '/', '.']
+
+    def __init__(self, locale='en_us'):
+
+        self.locale = locales.get_locale(locale)
+        self._input_re_map = self._BASE_INPUT_RE_MAP.copy()
+        self._input_re_map.update({
+            'MMMM': self._choice_re(self.locale.month_names[1:], re.IGNORECASE),
+            'MMM': self._choice_re(self.locale.month_abbreviations[1:],
+                                   re.IGNORECASE),
+            'Do': re.compile(self.locale.ordinal_day_re),
+            'dddd': self._choice_re(self.locale.day_names[1:], re.IGNORECASE),
+            'ddd': self._choice_re(self.locale.day_abbreviations[1:],
+                                   re.IGNORECASE),
+            'd' : re.compile("[1-7]"),
+            'a': self._choice_re(
+                (self.locale.meridians['am'], self.locale.meridians['pm'])
+            ),
+            # note: 'A' token accepts both 'am/pm' and 'AM/PM' formats to
+            # ensure backwards compatibility of this token
+            'A': self._choice_re(self.locale.meridians.values())
+        })
+
+    def parse_iso(self, string):
+
+        has_time = 'T' in string or ' ' in string.strip()
+        space_divider = ' ' in string.strip()
+
+        if has_time:
+            if space_divider:
+                date_string, time_string = string.split(' ', 1)
+            else:
+                date_string, time_string = string.split('T', 1)
+            time_parts = re.split('[+-]', time_string, 1)
+            has_tz = len(time_parts) > 1
+            has_seconds = time_parts[0].count(':') > 1
+            has_subseconds = re.search('[.,]', time_parts[0])
+
+            if has_subseconds:
+                formats = ['YYYY-MM-DDTHH:mm:ss%sS' % has_subseconds.group()]
+            elif has_seconds:
+                formats = ['YYYY-MM-DDTHH:mm:ss']
+            else:
+                formats = ['YYYY-MM-DDTHH:mm']
+        else:
+            has_tz = False
+            # generate required formats: YYYY-MM-DD, YYYY-MM-DD, YYYY
+            # using various separators: -, /, .
+            l = len(self.MARKERS)
+            formats = [separator.join(self.MARKERS[:l-i])
+                        for i in range(l)
+                        for separator in self.SEPARATORS]
+
+        if has_time and has_tz:
+            formats = [f + 'Z' for f in formats]
+
+        if space_divider:
+            formats = [item.replace('T', ' ', 1) for item in formats]
+
+        return self._parse_multiformat(string, formats)
+
+    def parse(self, string, fmt):
+
+        if isinstance(fmt, list):
+            return self._parse_multiformat(string, fmt)
+
+        # fmt is a string of tokens like 'YYYY-MM-DD'
+        # we construct a new string by replacing each
+        # token by its pattern:
+        # 'YYYY-MM-DD' -> '(?P<YYYY>\d{4})-(?P<MM>\d{2})-(?P<DD>\d{2})'
+        tokens = []
+        offset = 0
+
+        # Extract the bracketed expressions to be reinserted later.
+        escaped_fmt = re.sub(self._ESCAPE_RE, "#" , fmt)
+        # Any number of S is the same as one.
+        escaped_fmt = re.sub('S+', 'S', escaped_fmt)
+        escaped_data = re.findall(self._ESCAPE_RE, fmt)
+
+        fmt_pattern = escaped_fmt
+
+        for m in self._FORMAT_RE.finditer(escaped_fmt):
+            token = m.group(0)
+            try:
+                input_re = self._input_re_map[token]
+            except KeyError:
+                raise ParserError('Unrecognized token \'{0}\''.format(token))
+            input_pattern = '(?P<{0}>{1})'.format(token, input_re.pattern)
+            tokens.append(token)
+            # a pattern doesn't have the same length as the token
+            # it replaces! We keep the difference in the offset variable.
+            # This works because the string is scanned left-to-right and matches
+            # are returned in the order found by finditer.
+            fmt_pattern = fmt_pattern[:m.start() + offset] + input_pattern + fmt_pattern[m.end() + offset:]
+            offset += len(input_pattern) - (m.end() - m.start())
+
+        final_fmt_pattern = ""
+        a = fmt_pattern.split("#")
+        b = escaped_data
+
+        # Due to the way Python splits, 'a' will always be longer
+        for i in range(len(a)):
+            final_fmt_pattern += a[i]
+            if i < len(b):
+                final_fmt_pattern += b[i][1:-1]
+
+        match = re.search(final_fmt_pattern, string, flags=re.IGNORECASE)
+        if match is None:
+            raise ParserError('Failed to match \'{0}\' when parsing \'{1}\''.format(final_fmt_pattern, string))
+        parts = {}
+        for token in tokens:
+            if token == 'Do':
+                value = match.group('value')
+            else:
+                value = match.group(token)
+            self._parse_token(token, value, parts)
+        return self._build_datetime(parts)
+
+    def _parse_token(self, token, value, parts):
+
+        if token == 'YYYY':
+            parts['year'] = int(value)
+        elif token == 'YY':
+            value = int(value)
+            parts['year'] = 1900 + value if value > 68 else 2000 + value
+
+        elif token in ['MMMM', 'MMM']:
+            parts['month'] = self.locale.month_number(value.lower())
+
+        elif token in ['MM', 'M']:
+            parts['month'] = int(value)
+
+        elif token in ['DD', 'D']:
+            parts['day'] = int(value)
+
+        elif token in ['Do']:
+            parts['day'] = int(value)
+
+        elif token.upper() in ['HH', 'H']:
+            parts['hour'] = int(value)
+
+        elif token in ['mm', 'm']:
+            parts['minute'] = int(value)
+
+        elif token in ['ss', 's']:
+            parts['second'] = int(value)
+
+        elif token == 'S':
+            # We have the *most significant* digits of an arbitrary-precision integer.
+            # We want the six most significant digits as an integer, rounded.
+            # FIXME: add nanosecond support somehow?
+            value = value.ljust(7, str('0'))
+
+            # floating-point (IEEE-754) defaults to half-to-even rounding
+            seventh_digit = int(value[6])
+            if seventh_digit == 5:
+                rounding = int(value[5]) % 2
+            elif seventh_digit > 5:
+                rounding = 1
+            else:
+                rounding = 0
+
+            parts['microsecond'] = int(value[:6]) + rounding
+
+        elif token == 'X':
+            parts['timestamp'] = int(value)
+
+        elif token in ['ZZZ', 'ZZ', 'Z']:
+            parts['tzinfo'] = TzinfoParser.parse(value)
+
+        elif token in ['a', 'A']:
+            if value in (
+                    self.locale.meridians['am'],
+                    self.locale.meridians['AM']
+            ):
+                parts['am_pm'] = 'am'
+            elif value in (
+                    self.locale.meridians['pm'],
+                    self.locale.meridians['PM']
+            ):
+                parts['am_pm'] = 'pm'
+
+    @staticmethod
+    def _build_datetime(parts):
+
+        timestamp = parts.get('timestamp')
+
+        if timestamp:
+            tz_utc = tz.tzutc()
+            return datetime.fromtimestamp(timestamp, tz=tz_utc)
+
+        am_pm = parts.get('am_pm')
+        hour = parts.get('hour', 0)
+
+        if am_pm == 'pm' and hour < 12:
+            hour += 12
+        elif am_pm == 'am' and hour == 12:
+            hour = 0
+
+        return datetime(year=parts.get('year', 1), month=parts.get('month', 1),
+            day=parts.get('day', 1), hour=hour, minute=parts.get('minute', 0),
+            second=parts.get('second', 0), microsecond=parts.get('microsecond', 0),
+            tzinfo=parts.get('tzinfo'))
+
+    def _parse_multiformat(self, string, formats):
+
+        _datetime = None
+
+        for fmt in formats:
+            try:
+                _datetime = self.parse(string, fmt)
+                break
+            except ParserError:
+                pass
+
+        if _datetime is None:
+            raise ParserError('Could not match input to any of {0} on \'{1}\''.format(formats, string))
+
+        return _datetime
+
+    @staticmethod
+    def _map_lookup(input_map, key):
+
+        try:
+            return input_map[key]
+        except KeyError:
+            raise ParserError('Could not match "{0}" to {1}'.format(key, input_map))
+
+    @staticmethod
+    def _try_timestamp(string):
+
+        try:
+            return float(string)
+        except:
+            return None
+
+    @staticmethod
+    def _choice_re(choices, flags=0):
+        return re.compile('({0})'.format('|'.join(choices)), flags=flags)
+
+
+class TzinfoParser(object):
+
+    _TZINFO_RE = re.compile('([+\-])?(\d\d):?(\d\d)?')
+
+    @classmethod
+    def parse(cls, string):
+
+        tzinfo = None
+
+        if string == 'local':
+            tzinfo = tz.tzlocal()
+
+        elif string in ['utc', 'UTC']:
+            tzinfo = tz.tzutc()
+
+        else:
+
+            iso_match = cls._TZINFO_RE.match(string)
+
+            if iso_match:
+                sign, hours, minutes = iso_match.groups()
+                if minutes is None:
+                    minutes = 0
+                seconds = int(hours) * 3600 + int(minutes) * 60
+
+                if sign == '-':
+                    seconds *= -1
+
+                tzinfo = tz.tzoffset(None, seconds)
+
+            else:
+                tzinfo = tz.gettz(string)
+
+        if tzinfo is None:
+            raise ParserError('Could not parse timezone expression "{0}"'.format(string))
+
+        return tzinfo
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/arrow/util.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import sys
+
+# python 2.6 / 2.7 definitions for total_seconds function.
+
+def _total_seconds_27(td): # pragma: no cover
+    return td.total_seconds()
+
+def _total_seconds_26(td):
+    return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 1e6) / 1e6
+
+
+# get version info and assign correct total_seconds function.
+
+version = '{0}.{1}.{2}'.format(*sys.version_info[:3])
+
+if version < '2.7': # pragma: no cover
+    total_seconds = _total_seconds_26
+else: # pragma: no cover
+    total_seconds = _total_seconds_27
+
+def is_timestamp(value):
+    if type(value) == bool:
+        return False
+    try:
+        float(value)
+        return True
+    except:
+        return False
+
+# python 2.7 / 3.0+ definitions for isstr function.
+
+try: # pragma: no cover
+    basestring
+
+    def isstr(s):
+        return isinstance(s, basestring)
+
+except NameError: #pragma: no cover
+
+    def isstr(s):
+        return isinstance(s, str)
+
+
+__all__ = ['total_seconds', 'is_timestamp', 'isstr']
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/docs/Makefile
@@ -0,0 +1,177 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = _build
+
+# User-friendly check for sphinx-build
+ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
+$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
+endif
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  xml        to make Docutils-native XML files"
+	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Arrow.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Arrow.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/Arrow"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Arrow"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+latexpdfja:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through platex and dvipdfmx..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."
+
+xml:
+	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
+	@echo
+	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
+
+pseudoxml:
+	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
+	@echo
+	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/docs/_themes/COPYING.txt
@@ -0,0 +1,15 @@
+Copyright (c) 2011 Vimalkumar Velayudhan
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>
+
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/docs/_themes/README.rst
@@ -0,0 +1,32 @@
+sphinx-themes
+=============
+
+These are some themes for Python `Sphinx <http://sphinx.pocoo.org/>`_
+documentation projects.
+
+Preview
+-------
+To see how these themes look, visit http://vimalkumar.in/sphinx-themes
+
+Download
+--------
+Released versions are available from https://github.com/vkvn/sphinx-themes/downloads
+
+You can also download this repository as a `zip archive <https://github.com/vkvn/sphinx-themes/zipball/master>`_
+
+Support
+-------
+If there are problems with any of these themes, you can file a bug report at
+https://github.com/vkvn/sphinx-themes/issues
+
+Themes are licensed under the
+`GNU General Public License <http://www.gnu.org/licenses/gpl.html>`_.
+
+
+.. raw:: html
+
+   <iframe style="border: 0; margin: 0; padding: 0;"
+           src="https://www.gittip.com/vkvn/widget.html"
+           width="48pt" height="22pt"></iframe>
+
+   <a href="http://coderwall.com/vkvn"><img alt="Endorse vkvn on Coderwall" src="http://api.coderwall.com/vkvn/endorsecount.png" /></a>
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/docs/_themes/f6/NEWS.txt
@@ -0,0 +1,7 @@
+News
+====
+
+1.0
+---
+* Release date: 2012-11-01
+* Initial release
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/docs/_themes/f6/README.rst
@@ -0,0 +1,31 @@
+f6 theme for Python Sphinx
+==========================
+
+f6?
+---
+A light theme for Python Sphinx documentation projects. Mostly white -> #ffffff -> f6
+
+Preview
+-------
+http://vimalkumar.in/sphinx-themes/f6
+
+Download
+--------
+Released versions are available from http://github.com/vkvn/sphinx-themes/downloads
+
+Installation
+------------
+#. Extract the archive.
+#. Modify ``conf.py`` of an existing Sphinx project or create new project using ``sphinx-quickstart``.
+#. Change the ``html_theme`` parameter to ``f6``.
+#. Change the ``html_theme_path`` to the location containing the extracted archive.
+
+License
+-------
+`GNU General Public License <http://www.gnu.org/licenses/gpl.html>`_
+
+Credits
+-------
+Modified from the default Sphinx theme -- Sphinxdoc
+
+Background pattern from http://subtlepatterns.com
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/docs/_themes/f6/layout.html
@@ -0,0 +1,41 @@
+{% extends "basic/layout.html" %}
+
+{%- block doctype -%}
+<!DOCTYPE html>
+{%- endblock -%}
+
+{%- block extrahead -%}
+<link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,300italic,400italic,600italic|Source+Code+Pro' rel='stylesheet' type='text/css'></link>
+<meta name="description" content="Arrow:  better dates and times for Python.  Arrow is a Python library that provides a sensible, intelligent way of creating, manipulating, formatting and converting dates and times."></meta>
+{%- endblock -%}
+
+{# put the sidebar before the body #}
+{% block sidebarlogo %}
+<a href="https://github.com/crsmithdev/arrow"><img style="position: absolute; top: 0; right: 0; border: 0;" 
+    src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
+
+<h2><a style="font-size: .75em;"href="https://github.com/crsmithdev/arrow">github.com/crsmithdev/arrow</a></h2>
+<iframe src="http://ghbtns.com/github-btn.html?user=crsmithdev&repo=arrow&type=watch&count=true&size=large"
+  allowtransparency="true" frameborder="0" scrolling="0" width="150" height="40"></iframe>
+
+{% endblock%}
+{% block sidebar1 %}{{ sidebar() }}{% endblock %}
+{% block sidebar2 %}{% endblock %}
+{%- block relbar1 %}{% endblock %}
+{%- block footer %}
+    <div class="footer">
+    {%- if show_copyright %}
+      {%- if hasdoc('copyright') %}
+        {% trans path=pathto('copyright'), copyright=copyright|e %}&copy; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
+      {%- else %}
+        {% trans copyright=copyright|e %}&copy; Copyright {{ copyright }}.{% endtrans %}
+      {%- endif %}
+    {%- endif %}
+    {%- if last_updated %}
+      {% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
+    {%- endif %}
+    {%- if show_sphinx %}
+    {% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx.pocoo.org/">Sphinx</a> {{ sphinx_version }}.Theme by <a href="http://github.com/vkvn">vkvn</a>{% endtrans %}
+    {%- endif %}
+    </div>
+{%- endblock %}
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..82cf2d4957123beb8d6fbcc4cc097155f96389e6
GIT binary patch
literal 85
zc%17D@N?(olHy`uVBq!ia0vp^%plCc1SD^IDZKzvGM+AuAsn*FKR!P{|NZ^_|NsA=
iXIRo8Bf#CE$iUFYE475nk@X@_9fPN<pUXO@geCxl%@~6K
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/docs/_themes/f6/static/f6.css
@@ -0,0 +1,389 @@
+/* f6.css
+ * Modified from sphinxdoc.css of the sphinxdoc theme.
+*/
+
+@import url("basic.css");
+
+/* -- page layout ----------------------------------------------------------- */
+
+body {
+    font-family: 'Source Sans Pro', sans-serif;
+    font-size: 16px;
+    line-height: 150%;
+    text-align: center;
+    color: #4d4d4c;
+    padding: 0;
+    margin: 0px 80px 0px 80px;
+    min-width: 740px;
+    border: 1px solid #d6d6d6;
+    background: url("brilliant.png") repeat;
+    margin: 0px 200px 0px 200px;
+}
+
+div.document {
+    text-align: left;
+    background-repeat: repeat-x;
+}
+
+div.bodywrapper {
+    margin: 0 240px 0 0;
+    border-right: 1px dotted #d6d6d6;
+}
+
+div.body {
+    background-color: white;
+    margin: 0;
+    padding: 0.5em 20px 20px 20px;
+}
+
+div.related {
+    font-size: 1em;
+    background-color: #efefef;
+    color: #4d4d4c;
+    padding: 5px 0px;
+	border-bottom: 1px solid #d6d6d6;
+}
+
+div.related ul {
+    height: 2em;
+    margin: 2px;
+}
+
+div.related ul li {
+    margin: 0;
+    padding: 0;
+    height: 2em;
+    float: left;
+}
+
+div.related ul li.right {
+    float: right;
+    margin-right: 5px;
+}
+
+div.related ul li a {
+    margin: 0;
+    padding: 2px 5px;
+    line-height: 2em;
+    text-decoration: none;
+    color: #4d4d4c;
+}
+
+div.related ul li a:hover {
+	color: #4271ae;
+    -webkit-border-radius: 2px;
+    -moz-border-radius: 2px;
+    border-radius: 2px;
+}
+
+div.sphinxsidebarwrapper {
+    padding: 0;
+}
+
+div.sphinxsidebar {
+    margin: 0;
+    padding: 0.5em 15px 15px 0;
+    width: 210px;
+    float: right;
+    font-size: 0.9em;
+    text-align: left;
+}
+
+div.sphinxsidebar h3, div.sphinxsidebar h4 {
+    font-size: 1em;
+    padding: 0.5em 0 0.5em 0;
+	text-transform: uppercase;
+}
+
+div.sphinxsidebar h3 a {
+    color: #4271ae;
+}
+
+div.sphinxsidebar ul {
+    padding-left: 1.5em;
+    margin-top: 7px;
+    padding: 0;
+    line-height: 150%;
+    color: #4d4d4c;
+}
+
+div.sphinxsidebar ul li {
+	color: #8e908c;
+}
+
+div.sphinxsidebar ul ul {
+    margin-left: 1em;
+}
+
+div.sphinxsidebar input {
+   border: 1px solid #efefef;
+   font-family: inherit;
+}
+
+div.sphinxsidebar #searchbox input[type="submit"] {
+	color: white;
+	background-color: #4271ae;
+}
+
+div.footer {
+    color: #4d4d4c;
+    padding: 3px 8px 3px 0;
+    clear: both;
+    font-size: 0.8em;
+}
+
+div.footer a {
+    color: #4d4d4c;
+    text-decoration: none;
+	border-bottom: 1px dotted #4271ae;
+}
+
+div.footer a:hover {
+    color: #4271ae;
+    text-decoration: none;
+	border-bottom: 1px dotted #4271ae;
+}
+
+/* -- body styles ----------------------------------------------------------- */
+
+p {
+    margin: 0.8em 0 0.5em 0;
+}
+
+div.body a, div.sphinxsidebarwrapper a {
+    color: #4d4d4c;
+    text-decoration: none;
+	border-bottom: 1px dotted #4271ae;
+}
+
+div.body a:hover, div.sphinxsidebarwrapper a:hover {
+	color: #4271ae;
+    border-bottom: 1px dotted #4271ae;
+}
+
+h1, h2, h3, h4, h5, h6 {
+    font-family: "Source Sans Pro", sans-serif;
+    font-weight: 400;
+	text-shadow: #efefef 0.1em 0.1em 0.1em;
+}
+
+h1 {
+    margin: 0;
+    padding: 0.7em 0 0.3em 0;
+    line-height: 1.2em;
+    text-shadow: #efefef 0.1em 0.1em 0.1em;
+}
+
+h2 {
+    margin: 1.3em 0 0.2em 0;
+    padding: 0 0 10px 0;
+}
+
+h3 {
+    margin: 1em 0 -0.3em 0;
+    padding-bottom: 5px;
+}
+
+h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor {
+    display: none;
+    margin: 0 0 0 0.3em;
+    padding: 0 0.2em 0 0.2em;
+    color: #aaa!important;
+}
+
+h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor,
+h5:hover a.anchor, h6:hover a.anchor {
+    display: inline;
+}
+
+h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover,
+h5 a.anchor:hover, h6 a.anchor:hover {
+    color: #777;
+    background-color: #eee;
+}
+
+a.headerlink {
+    color: #4d4d4c!important;
+    font-size: 1em;
+    margin-left: 6px;
+    padding: 0 4px 0 4px;
+    text-decoration: none!important;
+}
+
+a.headerlink:hover {
+    background-color: #efefef;
+    color: white!important;
+}
+
+
+cite, code, tt {
+    font-family: 'Source Code Pro', monospace;
+	font-size: 0.9em;
+    letter-spacing: 0.01em;
+    background-color: #fbfbfb;
+    font-style: normal;
+	border: 1px dotted #efefef;
+	border-radius: 2px;
+	padding: 0 2px;
+}
+
+hr {
+    border: 1px solid #d6d6d6;
+    margin: 2em;
+}
+
+.highlight {
+    -webkit-border-radius: 2px;
+    -moz-border-radius: 2px;
+    border-radius: 2px;
+    background: #f0f0f0 !important;
+}
+
+.highlighted {
+	background-color: #4271ae;
+	color: white;
+	padding: 0 0.3em;
+}
+
+pre {
+    font-family: 'Source Code Pro', monospace;
+    font-style: normal;
+	font-size: 0.9em;
+    letter-spacing: 0.015em;
+    line-height: 130%;
+    padding: 0.7em;
+	white-space: pre-wrap;       /* css-3 */
+	white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
+	white-space: -pre-wrap;      /* Opera 4-6 */
+	white-space: -o-pre-wrap;    /* Opera 7 */
+	word-wrap: break-word;       /* Internet Explorer 5.5+ */
+}
+
+pre a {
+    color: inherit;
+    text-decoration: underline;
+}
+
+td.linenos pre {
+    padding: 0.5em 0;
+}
+
+div.quotebar {
+    background-color: #f8f8f8;
+    max-width: 250px;
+    float: right;
+    padding: 2px 7px;
+    border: 1px solid #ccc;
+}
+
+div.topic {
+    background-color: #f8f8f8;
+}
+
+table {
+    border-collapse: collapse;
+    margin: 0 -0.5em 0 -0.5em;
+}
+
+table.docutils {
+    width: 100% !important;
+    margin-left: 2px;
+    border: 0;
+}
+
+table.docutils tbody tr td {
+    padding-top: 5px;
+    padding-bottom: 5px;
+    border: 0;
+}
+
+table.docutils thead tr th {
+    padding-top: 5px;
+    padding-bottom: 5px;
+    border: 0;
+}
+
+.row-even {
+    background-color: #F0F0F0;
+}
+
+table td, table th {
+    padding: 0.2em 0.5em 0.2em 0.5em;
+}
+
+div.admonition {
+    font-size: 0.9em;
+    margin: 1em 0 1em 0;
+    background-color: #fdfdfd;
+    padding: 0;
+    -moz-box-shadow: 0px 8px 6px -8px #d6d6d6;
+    -webkit-box-shadow: 0px 8px 6px -8px #d6d6d6;
+    box-shadow: 0px 8px 6px -8px #d6d6d6;
+}
+
+div.admonition p {
+    margin: 0.5em 1em 0.5em 1em;
+    padding: 0.2em;
+}
+
+div.admonition pre {
+    margin: 0.4em 1em 0.4em 1em;
+}
+
+div.admonition p.admonition-title
+{
+    margin: 0;
+    padding: 0.2em 0 0.2em 0.6em;
+    background-color: white;
+    border-bottom: 1px solid #4271ae;
+    font-weight: 600;
+	font-size: 1.2em;
+    color: #4271ae;
+	text-transform: uppercase;
+}
+
+div.note p.admonition-title
+{
+	color: #4271ae;
+	border-bottom: 1px solid #4271ae;
+}
+
+div.warning p.admonition-title,
+div.important p.admonition-title {
+    color: #f5871f;
+	border-bottom: 1px solid #f5871f;
+}
+
+div.hint p.admonition-title,
+div.tip p.admonition-title {
+    color: #718c00;
+	border-bottom: 1px solid #718c00;
+}
+
+div.caution p.admonition-title,
+div.attention p.admonition-title,
+div.danger p.admonition-title,
+div.error p.admonition-title {
+    color: #c82829;
+	border-bottom: 1px solid #c82829;
+}
+
+div.admonition ul, div.admonition ol {
+    margin: 0.1em 0.5em 0.5em 3em;
+    padding: 0;
+}
+
+div.versioninfo {
+    margin: 1em 0 0 0;
+    border: 1px solid #eee;
+    background-color: #DDEAF0;
+    padding: 8px;
+    line-height: 1.3em;
+    font-size: 0.9em;
+}
+
+div.viewcode-block:target {
+    background-color: #f4debf;
+    border-top: 1px solid #eee;
+    border-bottom: 1px solid #eee;
+}
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/docs/_themes/f6/theme.conf
@@ -0,0 +1,3 @@
+[theme]
+inherit = basic
+stylesheet = f6.css
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/docs/conf.py
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+#
+# Arrow documentation build configuration file, created by
+# sphinx-quickstart on Mon May  6 15:25:39 2013.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('../'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Arrow'
+copyright = u'2013, Chris Smith'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.4.4'
+# The full version, including alpha/beta/rc tags.
+release = '0.4.4'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_build', '_themes']
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+#keep_warnings = False
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'f6'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+html_theme_path = ['_themes']
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+html_use_index = False
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+html_show_sourcelink = False
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Arrowdoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'Arrow.tex', u'Arrow Documentation',
+   u'Chris Smith', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'arrow', u'Arrow Documentation',
+     [u'Chris Smith'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'Arrow', u'Arrow Documentation',
+   u'Chris Smith', 'Arrow', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+#texinfo_no_detailmenu = False
+
+autodoc_member_order = 'bysource'
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/docs/index.rst
@@ -0,0 +1,476 @@
+=========================================
+Arrow:  better dates and times for Python
+=========================================
+
+-----
+What?
+-----
+
+Arrow is a Python library that offers a sensible, human-friendly approach to creating, manipulating, formatting and converting dates, times, and timestamps.  It implements and updates the datetime type, plugging gaps in functionality, and provides an intelligent module API that supports many common creation scenarios.  Simply put, it helps you work with dates and times with fewer imports and a lot less code.
+
+Arrow is heavily inspired by `moment.js <https://github.com/timrwood/moment>`_ and `requests <https://github.com/kennethreitz/requests>`_.
+
+----
+Why?
+----
+Python's standard library and some other low-level modules have near-complete date, time and time zone functionality but don't work very well from a usability perspective:
+
+- Too many modules:  datetime, time, calendar, dateutil, pytz and more
+- Too many types:  date, time, datetime, tzinfo, timedelta, relativedelta, etc.
+- Time zones and timestamp conversions are verbose and unpleasant
+- Time zone naivety is the norm
+- Gaps in functionality:  ISO-8601 parsing, time spans, humanization
+
+--------
+Features
+--------
+
+- Fully implemented, drop-in replacement for datetime
+- Supports Python 2.6, 2.7, 3.3, 3.4 and 3.5
+- Time zone-aware & UTC by default
+- Provides super-simple creation options for many common input scenarios
+- Updated .replace method with support for relative offsets, including weeks
+- Formats and parses strings automatically
+- Partial ISO-8601 support
+- Timezone conversion
+- Timestamp available as a property
+- Generates time spans, ranges, floors and ceilings in time frames from year to microsecond
+- Humanizes and supports a growing list of contributed locales
+- Extensible for your own Arrow-derived types
+
+----------
+Quickstart
+----------
+
+.. code-block:: bash
+
+    $ pip install arrow
+
+.. code-block:: python
+
+    >>> import arrow
+    >>> utc = arrow.utcnow()
+    >>> utc
+    <Arrow [2013-05-11T21:23:58.970460+00:00]>
+
+    >>> utc = utc.replace(hours=-1)
+    >>> utc
+    <Arrow [2013-05-11T20:23:58.970460+00:00]>
+
+    >>> local = utc.to('US/Pacific')
+    >>> local
+    <Arrow [2013-05-11T13:23:58.970460-07:00]>
+
+    >>> arrow.get('2013-05-11T21:23:58.970460+00:00')
+    <Arrow [2013-05-11T21:23:58.970460+00:00]>
+
+    >>> local.timestamp
+    1368303838
+
+    >>> local.format()
+    '2013-05-11 13:23:58 -07:00'
+
+    >>> local.format('YYYY-MM-DD HH:mm:ss ZZ')
+    '2013-05-11 13:23:58 -07:00'
+
+    >>> local.humanize()
+    'an hour ago'
+
+    >>> local.humanize(locale='ko_kr')
+    '1시간 전'
+
+------------
+User's Guide
+------------
+
+Creation
+========
+
+Get 'now' easily:
+
+.. code-block:: python
+
+    >>> arrow.utcnow()
+    <Arrow [2013-05-07T04:20:39.369271+00:00]>
+
+    >>> arrow.now()
+    <Arrow [2013-05-06T21:20:40.841085-07:00]>
+
+    >>> arrow.now('US/Pacific')
+    <Arrow [2013-05-06T21:20:44.761511-07:00]>
+
+Create from timestamps (ints or floats, or strings that convert to a float):
+
+.. code-block:: python
+
+    >>> arrow.get(1367900664)
+    <Arrow [2013-05-07T04:24:24+00:00]>
+
+    >>> arrow.get('1367900664')
+    <Arrow [2013-05-07T04:24:24+00:00]>
+
+    >>> arrow.get(1367900664.152325)
+    <Arrow [2013-05-07T04:24:24.152325+00:00]>
+
+    >>> arrow.get('1367900664.152325')
+    <Arrow [2013-05-07T04:24:24.152325+00:00]>
+
+Use a naive or timezone-aware datetime, or flexibly specify a time zone:
+
+.. code-block:: python
+
+    >>> arrow.get(datetime.utcnow())
+    <Arrow [2013-05-07T04:24:24.152325+00:00]>
+
+    >>> arrow.get(datetime.now(), 'US/Pacific')
+    <Arrow [2013-05-06T21:24:32.736373-07:00]>
+
+    >>> from dateutil import tz
+    >>> arrow.get(datetime.now(), tz.gettz('US/Pacific'))
+    <Arrow [2013-05-06T21:24:41.129262-07:00]>
+
+    >>> arrow.get(datetime.now(tz.gettz('US/Pacific')))
+    <Arrow [2013-05-06T21:24:49.552236-07:00]>
+
+Parse from a string:
+
+.. code-block:: python
+
+    >>> arrow.get('2013-05-05 12:30:45', 'YYYY-MM-DD HH:mm:ss')
+    <Arrow [2013-05-05T12:30:45+00:00]>
+
+Search a date in a string:
+
+.. code-block:: python
+
+    >>> arrow.get('June was born in May 1980', 'MMMM YYYY')
+    <Arrow [1980-05-01T00:00:00+00:00]>
+
+Some ISO-8601 compliant strings are recognized and parsed without a format string:
+
+    >>> arrow.get('2013-09-30T15:34:00.000-07:00')
+    <Arrow [2013-09-30T15:34:00-07:00]>
+
+Arrow objects can be instantiated directly too, with the same arguments as a datetime:
+
+.. code-block:: python
+
+    >>> arrow.get(2013, 5, 5)
+    <Arrow [2013-05-05T00:00:00+00:00]>
+
+    >>> arrow.Arrow(2013, 5, 5)
+    <Arrow [2013-05-05T00:00:00+00:00]>
+
+Properties
+==========
+
+Get a datetime or timestamp representation:
+
+.. code-block:: python
+
+    >>> a = arrow.utcnow()
+    >>> a.datetime
+    datetime.datetime(2013, 5, 7, 4, 38, 15, 447644, tzinfo=tzutc())
+
+    >>> a.timestamp
+    1367901495
+
+Get a naive datetime, and tzinfo:
+
+.. code-block:: python
+
+    >>> a.naive
+    datetime.datetime(2013, 5, 7, 4, 38, 15, 447644)
+
+    >>> a.tzinfo
+    tzutc()
+
+Get any datetime value:
+
+.. code-block:: python
+
+    >>> a.year
+    2013
+
+Call datetime functions that return properties:
+
+.. code-block:: python
+
+    >>> a.date()
+    datetime.date(2013, 5, 7)
+
+    >>> a.time()
+    datetime.time(4, 38, 15, 447644)
+
+Replace & shift
+===============
+
+Get a new :class:`Arrow <arrow.arrow.Arrow>` object, with altered attributes, just as you would with a datetime:
+
+.. code-block:: python
+
+    >>> arw = arrow.utcnow()
+    >>> arw
+    <Arrow [2013-05-12T03:29:35.334214+00:00]>
+
+    >>> arw.replace(hour=4, minute=40)
+    <Arrow [2013-05-12T04:40:35.334214+00:00]>
+
+Or, get one with attributes shifted forward or backward:
+
+.. code-block:: python
+
+    >>> arw.replace(weeks=+3)
+    <Arrow [2013-06-02T03:29:35.334214+00:00]>
+
+
+Format
+======
+
+.. code-block:: python
+
+    >>> arrow.utcnow().format('YYYY-MM-DD HH:mm:ss ZZ')
+    '2013-05-07 05:23:16 -00:00'
+
+Convert
+=======
+
+Convert to timezones by name or tzinfo:
+
+.. code-block:: python
+
+    >>> utc = arrow.utcnow()
+    >>> utc
+    <Arrow [2013-05-07T05:24:11.823627+00:00]>
+
+    >>> utc.to('US/Pacific')
+    <Arrow [2013-05-06T22:24:11.823627-07:00]>
+
+    >>> utc.to(tz.gettz('US/Pacific'))
+    <Arrow [2013-05-06T22:24:11.823627-07:00]>
+
+Or using shorthand:
+
+.. code-block:: python
+
+    >>> utc.to('local')
+    <Arrow [2013-05-06T22:24:11.823627-07:00]>
+
+    >>> utc.to('local').to('utc')
+    <Arrow [2013-05-07T05:24:11.823627+00:00]>
+
+
+Humanize
+========
+
+Humanize relative to now:
+
+.. code-block:: python
+
+    >>> past = arrow.utcnow().replace(hours=-1)
+    >>> past.humanize()
+    'an hour ago'
+
+Or another Arrow, or datetime:
+
+.. code-block:: python
+
+    >>> present = arrow.utcnow()
+    >>> future = present.replace(hours=2)
+    >>> future.humanize(present)
+    'in 2 hours'
+
+Support for a growing number of locales (see `locales.py` for supported languages):
+
+.. code-block:: python
+
+    >>> future = arrow.utcnow().replace(hours=1)
+    >>> future.humanize(a, locale='ru')
+    'через 2 час(а,ов)'
+
+
+Ranges & spans
+==============
+
+Get the time span of any unit:
+
+.. code-block:: python
+
+    >>> arrow.utcnow().span('hour')
+    (<Arrow [2013-05-07T05:00:00+00:00]>, <Arrow [2013-05-07T05:59:59.999999+00:00]>)
+
+Or just get the floor and ceiling:
+
+.. code-block:: python
+
+    >>> arrow.utcnow().floor('hour')
+    <Arrow [2013-05-07T05:00:00+00:00]>
+
+    >>> arrow.utcnow().ceil('hour')
+    <Arrow [2013-05-07T05:59:59.999999+00:00]>
+
+You can also get a range of time spans:
+
+.. code-block:: python
+
+    >>> start = datetime(2013, 5, 5, 12, 30)
+    >>> end = datetime(2013, 5, 5, 17, 15)
+    >>> for r in arrow.Arrow.span_range('hour', start, end):
+    ...     print r
+    ...
+    (<Arrow [2013-05-05T12:00:00+00:00]>, <Arrow [2013-05-05T12:59:59.999999+00:00]>)
+    (<Arrow [2013-05-05T13:00:00+00:00]>, <Arrow [2013-05-05T13:59:59.999999+00:00]>)
+    (<Arrow [2013-05-05T14:00:00+00:00]>, <Arrow [2013-05-05T14:59:59.999999+00:00]>)
+    (<Arrow [2013-05-05T15:00:00+00:00]>, <Arrow [2013-05-05T15:59:59.999999+00:00]>)
+    (<Arrow [2013-05-05T16:00:00+00:00]>, <Arrow [2013-05-05T16:59:59.999999+00:00]>)
+
+Or just iterate over a range of time:
+
+.. code-block:: python
+
+    >>> start = datetime(2013, 5, 5, 12, 30)
+    >>> end = datetime(2013, 5, 5, 17, 15)
+    >>> for r in arrow.Arrow.range('hour', start, end):
+    ...     print repr(r)
+    ...
+    <Arrow [2013-05-05T12:30:00+00:00]>
+    <Arrow [2013-05-05T13:30:00+00:00]>
+    <Arrow [2013-05-05T14:30:00+00:00]>
+    <Arrow [2013-05-05T15:30:00+00:00]>
+    <Arrow [2013-05-05T16:30:00+00:00]>
+
+.. toctree::
+   :maxdepth: 2
+
+Factories
+=========
+
+Use factories to harness Arrow's module API for a custom Arrow-derived type.  First, derive your type:
+
+.. code-block:: python
+
+    >>> class CustomArrow(arrow.Arrow):
+    ...
+    ...     def days_till_xmas(self):
+    ...
+    ...         xmas = arrow.Arrow(self.year, 12, 25)
+    ...
+    ...         if self > xmas:
+    ...             xmas = xmas.replace(years=1)
+    ...
+    ...         return (xmas - self).days
+
+
+Then get and use a factory for it:
+
+.. code-block:: python
+
+    >>> factory = arrow.ArrowFactory(CustomArrow)
+    >>> custom = factory.utcnow()
+    >>> custom
+    >>> <CustomArrow [2013-05-27T23:35:35.533160+00:00]>
+
+    >>> custom.days_till_xmas()
+    >>> 211
+
+Tokens
+======
+
+Use the following tokens in parsing and formatting.  Note that they're not the same as the tokens for `strptime(3) <https://www.gnu.org/software/libc/manual/html_node/Low_002dLevel-Time-String-Parsing.html#index-strptime>`_:
+
++--------------------------------+--------------+-------------------------------------------+
+|                                |Token         |Output                                     |
++================================+==============+===========================================+
+|**Year**                        |YYYY          |2000, 2001, 2002 ... 2012, 2013            |
++--------------------------------+--------------+-------------------------------------------+
+|                                |YY            |00, 01, 02 ... 12, 13                      |
++--------------------------------+--------------+-------------------------------------------+
+|**Month**                       |MMMM          |January, February, March ... [#t1]_        |
++--------------------------------+--------------+-------------------------------------------+
+|                                |MMM           |Jan, Feb, Mar ... [#t1]_                   |
++--------------------------------+--------------+-------------------------------------------+
+|                                |MM            |01, 02, 03 ... 11, 12                      |
++--------------------------------+--------------+-------------------------------------------+
+|                                |M             |1, 2, 3 ... 11, 12                         |
++--------------------------------+--------------+-------------------------------------------+
+|**Day of Year**                 |DDDD          |001, 002, 003 ... 364, 365                 |
++--------------------------------+--------------+-------------------------------------------+
+|                                |DDD           |1, 2, 3 ... 4, 5                           |
++--------------------------------+--------------+-------------------------------------------+
+|**Day of Month**                |DD            |01, 02, 03 ... 30, 31                      |
++--------------------------------+--------------+-------------------------------------------+
+|                                |D             |1, 2, 3 ... 30, 31                         |
++--------------------------------+--------------+-------------------------------------------+
+|                                |Do            |1st, 2nd, 3rd ... 30th, 31st               |
++--------------------------------+--------------+-------------------------------------------+
+|**Day of Week**                 |dddd          |Monday, Tuesday, Wednesday ... [#t2]_      |
++--------------------------------+--------------+-------------------------------------------+
+|                                |ddd           |Mon, Tue, Wed ... [#t2]_                   |
++--------------------------------+--------------+-------------------------------------------+
+|                                |d             |1, 2, 3 ... 6, 7                           |
++--------------------------------+--------------+-------------------------------------------+
+|**Hour**                        |HH            |00, 01, 02 ... 23, 24                      |
++--------------------------------+--------------+-------------------------------------------+
+|                                |H             |0, 1, 2 ... 23, 24                         |
++--------------------------------+--------------+-------------------------------------------+
+|                                |hh            |01, 02, 03 ... 11, 12                      |
++--------------------------------+--------------+-------------------------------------------+
+|                                |h             |1, 2, 3 ... 11, 12                         |
++--------------------------------+--------------+-------------------------------------------+
+|**AM / PM**                     |A             |AM, PM, am, pm [#t1]_                      |
++--------------------------------+--------------+-------------------------------------------+
+|                                |a             |am, pm [#t1]_                              |
++--------------------------------+--------------+-------------------------------------------+
+|**Minute**                      |mm            |00, 01, 02 ... 58, 59                      |
++--------------------------------+--------------+-------------------------------------------+
+|                                |m             |0, 1, 2 ... 58, 59                         |
++--------------------------------+--------------+-------------------------------------------+
+|**Second**                      |ss            |00, 01, 02 ... 58, 59                      |
++--------------------------------+--------------+-------------------------------------------+
+|                                |s             |0, 1, 2 ... 58, 59                         |
++--------------------------------+--------------+-------------------------------------------+
+|**Sub-second**                  |S...          |0, 02, 003, 000006, 123123123123... [#t3]_ |
++--------------------------------+--------------+-------------------------------------------+
+|**Timezone**                    |ZZZ           |Asia/Baku, Europe/Warsaw, GMT ... [#t4]_   |
++--------------------------------+--------------+-------------------------------------------+
+|                                |ZZ            |-07:00, -06:00 ... +06:00, +07:00          |
++--------------------------------+--------------+-------------------------------------------+
+|                                |Z             |-0700, -0600 ... +0600, +0700              |
++--------------------------------+--------------+-------------------------------------------+
+|**Timestamp**                   |X             |1381685817                                 |
++--------------------------------+--------------+-------------------------------------------+
+
+.. rubric:: Footnotes
+
+.. [#t1] localization support for parsing and formatting
+.. [#t2] localization support only for formatting
+.. [#t3] the result is truncated to microseconds, with `half-to-even rounding <https://en.wikipedia.org/wiki/IEEE_floating_point#Roundings_to_nearest>`_.
+.. [#t4] timezone names from `tz database <https://www.iana.org/time-zones>`_  provided via dateutil package
+
+---------
+API Guide
+---------
+
+arrow.arrow
+===========
+
+.. automodule:: arrow.arrow
+    :members:
+
+arrow.factory
+=============
+
+.. automodule:: arrow.factory
+    :members:
+
+arrow.api
+=========
+
+.. automodule:: arrow.api
+    :members:
+
+arrow.locale
+============
+
+.. automodule:: arrow.locales
+    :members:
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/setup.cfg
@@ -0,0 +1,19 @@
+[nosetests]
+where = tests
+verbosity = 2
+all-modules = true
+with-coverage = true
+cover-min-percentage = 100
+cover-package = 
+	arrow
+	tests
+cover-erase = true
+
+[bdist_wheel]
+universal = 1
+
+[egg_info]
+tag_build = 
+tag_date = 0
+tag_svn_revision = 0
+
new file mode 100644
--- /dev/null
+++ b/lib/python/vendor/arrow-0.10.0/setup.py
@@ -0,0 +1,55 @@
+import codecs
+import os.path
+import re
+
+try:
+    from setuptools import setup
+except ImportError:
+    from distutils.core import setup
+
+
+
+def fpath(name):
+    return os.path.join(os.path.dirname(__file__), name)
+
+
+def read(fname):
+    return codecs.open(fpath(fname), encoding='utf-8').read()
+
+
+def grep(attrname):
+    pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
+    strval, = re.findall(pattern, file_text)
+    return strval
+
+
+file_text = read(fpath('arrow/__init__.py'))
+
+setup(
+    name='arrow',
+    version=grep('__version__'),
+    description='Better dates and times for Python',
+    long_description=read(fpath('README.rst')),
+    url='https://github.com/crsmithdev/arrow/',
+    author='Chris Smith',
+    author_email="crsmithdev@gmail.com",
+    license='Apache 2.0',
+    packages=['arrow'],
+    zip_safe=False,
+    install_requires=[
+        'python-dateutil'
+    ],
+    test_suite="tests",
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: Apache Software License',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: 3.5',
+        'Topic :: Software Development :: Libraries :: Python Modules'
+    ]
+)
+
--- a/lib/python/vendor/balrogclient-0.0.1/balrogclient/__init__.py
+++ b/lib/python/vendor/balrogclient-0.0.1/balrogclient/__init__.py
@@ -1,3 +1,3 @@
-from balrogclient.api import is_csrf_token_expired, SingleLocale, Release, Rule
+from balrogclient.api import is_csrf_token_expired, SingleLocale, Release, Rule, ScheduledRuleChange
 
-__all__ = [ 'is_csrf_token_expired', 'SingleLocale', 'Release', 'Rule' ]
\ No newline at end of file
+__all__ = [ 'is_csrf_token_expired', 'SingleLocale', 'Release', 'Rule', 'ScheduledRuleChange' ]
--- a/lib/python/vendor/balrogclient-0.0.1/balrogclient/api.py
+++ b/lib/python/vendor/balrogclient-0.0.1/balrogclient/api.py
@@ -231,8 +231,23 @@ class Rule(API):
     def __init__(self, rule_id, **kwargs):
         super(Rule, self).__init__(**kwargs)
         self.rule_id = rule_id
         self.url_template_vars = dict(rule_id=rule_id)
 
     def update_rule(self, **rule_data):
         """wrapper for self.request"""
         return self.request(method='POST', data=rule_data)
+
+
+class ScheduledRuleChange(API):
+    """Update Balrog rules"""
+    url_template = '/scheduled_changes/rules'
+    prerequest_url_template = '/rules/%(rule_id)s'
+
+    def __init__(self, rule_id, **kwargs):
+        super(ScheduledRuleChange, self).__init__(**kwargs)
+        self.rule_id = rule_id
+        self.url_template_vars = dict(rule_id=rule_id)
+
+    def add_scheduled_rule_change(self, **rule_data):
+        """wrapper for self.request"""
+        return self.request(method='POST', data=rule_data)
--- a/lib/python/vendorlibs.pth
+++ b/lib/python/vendorlibs.pth
@@ -7,8 +7,9 @@ vendor/Jinja2-2.7.3/
 vendor/MarkupSafe-0.23/
 vendor/redo-1.4.1/
 vendor/jsonmerge-1.1.0/
 vendor/jsonschema-2.5.1/
 vendor/functools32-3.2.3-2/
 vendor/repoze.lru-ef418de/
 vendor/balrogclient-0.0.1/
 vendor/certifi-2016.9.26/
+vendor/arrow-0.10.0/
--- a/scripts/build-promotion/balrog-release-shipper.py
+++ b/scripts/build-promotion/balrog-release-shipper.py
@@ -2,39 +2,52 @@
 
 from os import path
 import logging
 import sys
 
 # Use explicit version of python-requests
 sys.path.insert(0, path.join(path.dirname(__file__),
                              "../../lib/python/vendor/requests-2.7.0"))
+sys.path.insert(0, path.join(path.dirname(__file__),
+                             "../../lib/python/vendor/arrow-0.10.0"))
 sys.path.insert(0, path.join(path.dirname(__file__), "../../lib/python"))
 
-from balrog.submitter.cli import ReleasePusher
+from balrog.submitter.cli import ReleasePusher, ReleaseScheduler
 
 if __name__ == '__main__':
 
     from argparse import ArgumentParser
     parser = ArgumentParser()
     parser.add_argument("-a", "--api-root", dest="api_root",required=True)
     parser.add_argument("-c", "--credentials-file", dest="credentials_file", required=True)
     parser.add_argument("-u", "--username", dest="username", required=True)
     parser.add_argument("-V", "--version", dest="version", required=True)
     parser.add_argument("-p", "--product", dest="product_name", required=True)
     parser.add_argument("-b", "--build-number", dest="build_number", required=True)
     parser.add_argument("-R", "--rules", dest="rule_ids", action="append", required=True)
+    parser.add_argument("-s", "--schedule-at", dest="schedule_at", default=None)
+    parser.add_argument("-B", "--background-rate", dest="backgroundRate", default=None)
     parser.add_argument("-v", "--verbose", dest="verbose", action="store_true")
     args = parser.parse_args()
 
     logging_level = logging.INFO
     if args.verbose:
         logging_level = logging.DEBUG
     logging.basicConfig(stream=sys.stdout, level=logging_level,
                         format="%(message)s")
 
     credentials = {}
     execfile(args.credentials_file, credentials)
     auth = (args.username, credentials['balrog_credentials'][args.username])
 
-    pusher = ReleasePusher(args.api_root, auth)
-    pusher.run(args.product_name.capitalize(), args.version,
-               args.build_number, args.rule_ids)
+    if args.schedule_at:
+        scheduler = ReleaseScheduler(args.api_root, auth)
+        if args.backgroundRate:
+            scheduler.run(args.product_name.capitalize(), args.version,
+                          args.build_number, args.rule_ids, args.schedule_at, args.backgroundRate)
+        else:
+            scheduler.run(args.product_name.capitalize(), args.version,
+                          args.build_number, args.rule_ids, args.schedule_at)
+    else:
+        pusher = ReleasePusher(args.api_root, auth)
+        pusher.run(args.product_name.capitalize(), args.version,
+                args.build_number, args.rule_ids, args.backgroundRate)