Skip to content

Instantly share code, notes, and snippets.

@nigelmcnie
Last active August 29, 2015 14:07
Show Gist options
  • Save nigelmcnie/ae856b5804fa9713f54e to your computer and use it in GitHub Desktop.
Save nigelmcnie/ae856b5804fa9713f54e to your computer and use it in GitHub Desktop.
Quantum datetime library for python - demo
# Create a quantum, which represents an exact point in time. Internally, the point of time is stored
# as UTC
> t1 = quantum.now()
<Quantum(2014-10-01 20:23:57.745648, no timezone)>
# We can ask for that point of time "at" a certain timezone. Relevant for datemath and display
> t1.at('Pacific/Auckland')
<Quantum(2014-10-01 20:23:57.745648, Pacific/Auckland)>
# We can get a naive datetime out of it at any TZ
> t1.at('UTC').as_local()
datetime.datetime(2014, 10, 1, 20, 23, 57, 745648)
> t1.at('Pacific/Auckland').as_local()
datetime.datetime(2014, 10, 2, 9, 23, 57, 745648)
# And format said TZs (there's a strftime of course, format_short is a simple helper)
> t1.at('UTC').format_short()
'1 Oct 2014 20:23'
> t1.at('Pacific/Auckland').format_short()
'2 Oct 2014 09:23'
# Datemath. It cannot be done unless you specify a timezone. If you add "6 months" naively to the UTC
# version, _then_ convert to timezone, chances are your '10am today' is actually '9am' or '11am' in
# six months thanks to a DST change - this is bad, so we disallow it.
> t1.add(months=6)
---------------------------------------------------------------------------
QuantumException Traceback (most recent call last)
<ipython-input-9-5513b97edf69> in <module>()
----> 1 t1.add(months=6)
/home/nigel/src/dca/dca2/trex/support/quantum.pyc in add(self, years, months, days, hours, minutes, seconds, microseconds)
376 def add(self, years=0, months=0, days=0, hours=0, minutes=0, seconds=0, microseconds=0):
377 if self.tz is None:
--> 378 raise QuantumException("Can't manipulate a Quantum that has no timezone set")
379 rd = dateutil.relativedelta.relativedelta(years=years, months=months, days=days, hours=hours, minutes=minutes, seconds=seconds, microseconds=microseconds)
380 local_dt = self.as_local()
QuantumException: Can't manipulate a Quantum that has no timezone set
# Once you specify a tz, it's fine. We just went through a DST transition, so 1 month ago is NOT
# the same hour in UTC:
> t1.at('Pacific/Auckland')
<Quantum(2014-10-01 20:23:57.745648, Pacific/Auckland)>
> t1.at('Pacific/Auckland').subtract(months=1)
<Quantum(2014-09-01 21:23:57.745648, Pacific/Auckland)>
# And when formatted, we can verify that we took 1 month off, through a DST change, and it's still
# the right hour of the day:
> t1.at('Pacific/Auckland').format_short()
'2 Oct 2014 09:23'
> t1.at('Pacific/Auckland').subtract(months=1).format_short()
'2 Sep 2014 09:23'
@nigelmcnie
Copy link
Author

The code is available here: https://github.com/shoptime/trex/blob/master/support/quantum.py

We wrote it initially as a helper for a webapp we were building, where we were handling the scheduling of appointments, and someone asking for an appointment "same time next week" was a complete nightmare through a DST change with existing libs. The core lib doesn't make this easy, and arrow can't do it either:

>>> import arrow, datetime

>>> just_before = arrow.get(datetime.datetime(2013, 03, 31, 1, 50, 45), "Europe/Paris").ceil("hour")
>>> just_before
<Arrow [2013-03-31T01:59:59.999999+01:00]>

>>> just_after = just_before.replace(microseconds=+1)
>>> just_after
<Arrow [2013-03-31T02:00:00+02:00]>
>>> # That is not right... It should be either 2 AM UTC+1 or preferably  3AM UTC +2

Quantum can, because it refuses to do any date math without knowing what timezone you're in:

> just_before = quantum.parse('2013-03-31T01:59:59', timezone='Europe/Paris')
<Quantum(2013-03-31 00:59:59, Europe/Paris)>

# The timestamp shown in __repr__ is UTC, not the local time
> just_before.add(seconds=1)
<Quantum(2013-03-31 01:00:00, Europe/Paris)>

# Just before the DST change, formatted as local time
> just_before.format_short()
'31 Mar 2013 01:59'

# And just after
> just_before.add(seconds=1).format_short()
'31 Mar 2013 03:00'

You can't call add() or subtract() unless you've told quantum what timezone it should be using. This means that questions like "this time next week" are never fooled by DST changes. You simply go:

quantum.now('Pacific/Auckland').add(weeks=1)

And it doesn't matter if there's a DST change in the middle - if it's 10am right now, it'll mean 10am next week too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment