version travis coveralls license

A drop-in replacement for native datetimes that embraces UTC


  • All datetime objects converted and stored as UTC.
  • Parses ISO8601 formatted strings and POSIX timestamps by default.
  • Timezone representation applied only during string output formatting or when casting to native datetime object.
  • Drop-in replacement for native datetime objects.
  • Python 3.4+


Install using pip:

pip3 install zulu
import zulu

# <Zulu [2016-07-25T19:33:18.137493+00:00]>

dt = zulu.parse('2016-07-25T19:33:18.137493+00:00')
# <Zulu [2016-07-25T19:33:18.137493+00:00]>

dt = zulu.create(2016, 7, 25, 19, 33, 18, 137493)
# <Zulu [2016-07-25T19:33:18.137493+00:00]>

# '2016-07-25T19:33:18.137493+00:00'

# 1469475198.137493

# datetime.datetime(2016, 7, 25, 19, 33, 18, 137493)

# datetime.datetime(2016, 7, 25, 19, 33, 18, 137493, tzinfo=<UTC>)

# 2016-07-25

# 2016-07-25

dt.format("E, MMM d, ''YY")
# "Mon, Jul 25, '16"

dt.format("E, MMM d, ''YY", locale='de')
# "Mo., Juli 25, '16"

dt.format("E, MMM d, ''YY", locale='fr')
# "lun., juil. 25, '16"

dt.shift(hours=-5, minutes=10)
# <Zulu [2016-07-25T14:43:18.137493+00:00]>

dt.replace(hour=14, minute=43)
# <Zulu [2016-07-25T14:43:18.137493+00:00]>

# <Zulu [2016-07-25T00:00:00+00:00]>

# <Zulu [2016-07-25T23:59:59.999999+00:00]>

# (<Zulu [2016-07-25T19:00:00+00:00]>, <Zulu [2016-07-25T19:59:59.999999+00:00]>)

# '4 hours ago'

# 'in 4 hours'

list(zulu.range('hour', dt, dt.shift(hours=4)))
# [Zulu [2016-07-25T19:33:18.137493+00:00]>,
#  Zulu [2016-07-25T20:33:18.137493+00:00]>,
#  Zulu [2016-07-25T21:33:18.137493+00:00]>,
#  Zulu [2016-07-25T22:33:18.137493+00:00]>]

list(zulu.span_range('minute', dt, dt.shift(minutes=4)))
# [(Zulu [2016-07-25T19:33:00+00:00]>, Zulu [2016-07-25T19:33:59.999999+00:00]>),
#  (Zulu [2016-07-25T19:34:00+00:00]>, Zulu [2016-07-25T19:34:59.999999+00:00]>),
#  (Zulu [2016-07-25T19:35:00+00:00]>, Zulu [2016-07-25T19:35:59.999999+00:00]>),
#  (Zulu [2016-07-25T19:36:00+00:00]>, Zulu [2016-07-25T19:36:59.999999+00:00]>)]

zulu.parse_delta('1w 3d 2h 32m')
# <Delta [10 days, 2:32:00]>

# <Delta [2 days, 4:13:02.266000]>

zulu.parse_delta('2 days, 5 hours, 34 minutes, 56 seconds')
# <Delta [2 days, 5:34:56]>

Why Zulu?

Why zulu instead of native datetimes:

  • Zulu has extended datetime features such as parse(), format(), shift(), and python-dateutil timezone support.
  • Parses ISO8601 and timestamps by default without any extra arguments.
  • Easier to reason about Zulu objects since they are only ever UTC datetimes.
  • Clear delineation between UTC and other time zones where timezone representation is only applicable for display or conversion to native datetime.
  • Supports more string parsing/formatting options using Unicode date patterns as well as strptime/strftime directives.

Why zulu instead of Arrow:

  • Zulu is a drop-in replacement for native datetimes (inherits from datetime.datetime). No need to convert using arrow.datetime when you need a datetime (zulu is always a datetime).
  • Stricter parsing to avoid silent errors. For example, one might expect arrow.get('02/08/1987', 'MM/DD/YY') to fail (input does not match format) but it gladly returns <Arrow [2019-02-08T00:00:00+00:00) whereas zulu.parse('02/08/1987', '%m/%d/%y') throws zulu.parser.ParseError: Value "02/08/1987" does not match any format in ['%m/%d/%y'].
  • Avoids timezone/DST shifting bugs by only dealing with UTC datetimes when applying timedeltas or performing other calculations.
  • Supports strptime/strftime as well as Unicode date patterns for string parsing/formatting.

Special Thanks

Special thanks goes out to the authors/contributors of the following libraries that have made it possible for zulu to exist:

For the full documentation, please visit https://zulu.readthedocs.io.

Indices and Tables