User’s Guide#
Zulu’s main type is zulu.Zulu
which represents a fixed UTC datetime object.
import zulu
dt = zulu.parse('2016-07-25T19:33:18.137493+00:00')
# <Zulu [2016-07-25T19:33:18.137493+00:00]>
assert isinstance(dt, zulu.Zulu)
from datetime import datetime
assert isinstance(dt, datetime)
It’s a drop-in replacement for native datetime objects (it inherits from datetime.datetime
) but deals only with UTC time zones internally.
Basic Data Access#
All the attributes and methods from datetime are available along with a few new ones:
assert dt.year == 2016
assert dt.month == 7
assert dt.day == 25
assert dt.hour == 19
assert dt.minute == 33
assert dt.second == 18
assert dt.microsecond == 137493
assert dt.tzname() == 'UTC'
dt.utcoffset()
# datetime.timedelta(0)
dt.dst()
# datetime.timedelta(0)
dt.isoformat()
# '2016-07-25T19:33:18.137493+00:00'
dt.weekday()
# 0
dt.isoweekday()
# 1
dt.isocalendar()
# (2016, 30, 1)
dt.ctime()
# 'Mon Jul 25 19:33:18 2016'
dt.toordinal()
# 736170
dt.timetuple()
# time.struct_time(tm_year=2016, tm_mon=7, tm_mday=25, tm_hour=19, tm_min=33, tm_sec=18,
# tm_wday=0, tm_yday=207, tm_isdst=0)
dt.utctimetuple()
# time.struct_time(tm_year=2016, tm_mon=7, tm_mday=25, tm_hour=19, tm_min=33, tm_sec=18,
# tm_wday=0, tm_yday=207, tm_isdst=0)
dt.timestamp()
# 1469475198.137493
dt.date()
# datetime.date(2016, 7, 25)
dt.time()
# datetime.time(19, 33, 18, 137493)
dt.timetz()
# datetime.time(19, 33, 18, 137493, tzinfo=<UTC>)
Along with a few new ones:
dt.naive
# datetime.datetime(2016, 7, 25, 19, 33, 18, 137493)
dt.datetime
# datetime.datetime(2016, 7, 25, 19, 33, 18, 137493, tzinfo=<UTC>)
dt.is_leap_year()
# True
dt.days_in_month()
# 31
tuple(dt)
# (2016, 7, 25, 19, 33, 18, 137493, <UTC>)
Parsing and Formatting#
By default, zulu.parse
will look for either an ISO8601 formatted string or a POSIX timestamp while assuming a UTC timezone when no explicit timezone found in the string:
zulu.parse('2016-07-25 15:33:18-0400')
# <Zulu [2016-07-25T19:33:18+00:00]>
zulu.parse('2016-07-25 15:33:18-0400', zulu.ISO8601)
# <Zulu [2016-07-25T19:33:18+00:00]>
zulu.parse('2016-07-25')
# <Zulu [2016-07-25T00:00:00+00:00]>
zulu.parse('2016-07-25 19:33')
# <Zulu [2016-07-25T19:33:00+00:00]>
zulu.parse(1469475198.0)
# <Zulu [2016-07-25T19:33:18+00:00]>
zulu.parse(1469475198.0, zulu.TIMESTAMP)
# <Zulu [2016-07-25T19:33:18+00:00]>
Multiple formats can be supplied and zulu.parse
will try them all:
zulu.parse('3/2/1992', 'ISO8601')
# zulu.parser.ParseError: Value "3/2/1992" does not match any format in "ISO8601"
# (Unable to parse date string '3/2/1992')
dt = zulu.parse('3/2/1992', ['ISO8601', 'MM/dd/YYYY'])
# <Zulu [1992-03-02T00:00:00+00:00]>
As shown above, special parse format keywords are supported. See Keyword Parse Formats for details.
Other time zones can be substituted for naive datetimes by setting default_tz
:
zulu.parse('2016-07-25', default_tz='US/Eastern')
# <Zulu [2016-07-25T04:00:00+00:00]>
zulu.parse('2016-07-25', default_tz='local')
# <Zulu [2016-07-25T04:00:00+00:00]>
The default timezone is ignored when the input has it set:
zulu.parse('2016-07-25T15:33:18-0700', default_tz='US/Eastern')
# <Zulu [2016-07-25T22:33:18+00:00]>
String parsing/formatting in Zulu
supports both strftime/strptime directives and Unicode date patterns.
dt.format('%Y-%m-%d %H:%M:%S%z')
# '2016-07-25 19:33:18+0000'
dt.format('YYYY-MM-dd HH:mm:ssZ')
# '2016-07-25 19:33:18+0000'
dt.format('%Y-%m-%d %H:%M:%S%z', tz='US/Eastern')
# '2016-07-25 15:33:18-0400'
dt.format('%Y-%m-%d %H:%M:%S%z', tz='local')
# '2016-07-25 15:33:18-0400'
zulu.parse('2016-07-25 15:33:18-0400', '%Y-%m-%d %H:%M:%S%z')
# <Zulu [2016-07-25T19:33:18+00:00]>
You can even use zulu.parser.format_datetime
with native datetimes:
from zulu.parser import UTC, format_datetime
native = datetime(2016, 7, 25, 19, 33, 18, 137493, tzinfo=UTC)
format_datetime(native, '%Y-%m-%d %H:%M:%S%z')
# '2016-07-25 19:33:18+0000'
format_datetime(native, 'YYYY-MM-dd HH:mm:ssZ')
# '2016-07-25 19:33:18+0000'
dt = Zulu.fromdatetime(native)
format_datetime(dt, 'YYYY-MM-dd HH:mm:ssZ')
# '2016-07-25 19:33:18+0000'
Keyword Parse Formats#
The following keywords can be supplied to zulu.parse
in place of a format directive or pattern:
zulu.parse(1469475198, 'timestamp')
# <Zulu [2016-07-25T19:33:18+00:00]>
Keyword |
Description |
Sample Input |
---|---|---|
ISO8601 |
Parse ISO8601 string |
|
timestamp |
Parse POSIX timestamp |
|
Format Tokens#
Zulu supports two different styles of string parsing/formatting tokens:
A subset of Unicode date patterns
Either style can be used during parsing:
dt = zulu.parse('07/25/16 15:33:18 -0400', '%m/%d/%y %H:%M:%S %z')
# <Zulu [2016-07-25T19:33:18+00:00]>
dt = zulu.parse('07/25/16 15:33:18 -0400', 'MM/dd/YY HH:mm:ss Z')
# <Zulu [2016-07-25T19:33:18+00:0
and formatting:
dt.format('%m/%d/%y %H:%M:%S %z')
# '07/25/16 19:33:18 +0000'
dt.format('MM/dd/YY HH:mm:ss Z')
'07/25/16 19:33:18 +0000'
Format Directives#
All directives from https://docs.python.org/3.5/library/datetime.html#strftime-and-strptime-behavior are supported.
Date Patterns#
A subset of patterns from http://www.unicode.org/reports/tr35/tr35-19.html#Date_Field_Symbol_Table are supported for parsing while _all_ patterns are supported for formatting:
Attribute |
Style |
Pattern |
Examples |
---|---|---|---|
Year |
4-digit |
YYYY |
2000, 2001, 2002 … 2015, 2016 |
Year |
2-digit |
YY |
00, 01, 02 … 15, 16 |
Month |
full name |
MMMM |
January, February, March |
Month |
abbr name |
MMM |
Jan, Feb, Mar … Nov, Dec |
Month |
int, padded |
MM |
01, 02, 03 … 11, 12 |
Month |
int, no padding |
M |
1, 2, 3 … 11, 12 |
Day of Month |
int, padded |
dd |
01, 02, 03 … 30, 31 |
Day of Month |
int, no padding |
d |
1, 2, 3 … 30, 31 |
Day of Year |
int, padded |
DDD |
001, 002, 003 … 054, 055 … 364, 365 |
Day of Year |
int, padded |
DD |
01, 02, 03 … 54, 55 … 364, 365 |
Day of Year |
int, no padding |
D |
1, 2, 3 … 54, 55 … 364, 365 |
Weekday |
full name |
EEEE |
Sunday, Monday, Tuesday … Friday, Saturday |
Weekday |
abbr name |
EEE |
Sun, Mon, Tue … Fri, Sat |
Weekday |
abbr name |
EE |
Sun, Mon, Tue … Fri, Sat |
Weekday |
abbr name |
E |
Sun, Mon, Tue … Fri, Sat |
Weekday |
abbr name |
eee |
Sun, Mon, Tue … Fri, Sat |
Weekday |
int, padded |
ee |
01, 02, 03 … 06, 07 |
Weekday |
int, no padding |
e |
1, 2, 3 … 6, 7 |
Hour |
24h, padded |
HH |
00, 01, 02 … 22, 23 |
Hour |
24h, no padding |
H |
0, 1, 2 … 22, 23 |
Hour |
12h, padded |
hh |
00, 01, 02 … 11, 12 |
Hour |
12h, no padding |
h |
0, 1, 2, … 11, 12 |
AM / PM |
upper case |
a |
AM, PM |
Minute |
int, padded |
mm |
00, 01, 02 … 58, 59 |
Minute |
int, no padding |
m |
0, 1, 2 … 58, 59 |
Second |
int, padded |
ss |
00, 01, 02 … 58, 59 |
Second |
int, no padding |
s |
0, 1, 2 … 58, 59 |
Microsecond |
int, padded |
SSSSSS |
000000, 000001 … 999998, 999999 |
Microsecond |
int, truncated |
SSSSS |
00000, 00001 … 99998, 99999 |
Microsecond |
int, truncated |
SSSS |
0000, 0001 … 9998, 9999 |
Microsecond |
int, truncated |
SSS |
000, 001 … 998, 999 |
Microsecond |
int, truncated |
SS |
00, 01 … 98, 99 |
Microsecond |
int, truncated |
S |
0, 1 … 8, 9 |
Timezone |
w/o separator |
Z |
-1100, -1000 … +0000 … +1100, +1200 |
Humanization#
You can humanize the difference between two Zulu
objects with Zulu.time_from
and Zulu.time_to
:
dt
# <Zulu [2016-07-25T19:33:18.137493+00:00]>
dt.time_from(dt.end_of_day())
# '4 hours ago'
dt.time_to(dt.end_of_day())
# 'in 4 hours'
dt.time_from(dt.start_of_day())
# 'in 20 hours'
dt.time_to(dt.start_of_day())
# '20 hours ago'
zulu.now()
# <Zulu [2016-08-12T04:16:17.007335+00:00]>
dt.time_from_now()
# 2 weeks ago
dt.time_to_now()
# in 2 weeks
Time Zone Handling#
Time zones other than UTC are not expressable within a Zulu
instance. Other time zones are only ever applied when either converting a Zulu
object to a native datetime (via Zulu.astimezone
) or during string formatting (via Zulu.format
). Zulu
understands both tzinfo
objects and IANA Timezone Database string names (also known as the Olson database).
local = dt.astimezone()
# same as doing dt.astimezone('local')
# datetime.datetime(2016, 7, 25, 15, 33, 18, 137493,
# tzinfo=tzlocal())
pacific = dt.astimezone('US/Pacific')
# datetime.datetime(2016, 7, 25, 12, 33, 18, 137493,
# tzinfo=tzfile('/usr/share/zoneinfo/US/Pacific'))
import pytz
mountain = dt.astimezone(pytz.timezone('US/Mountain'))
# datetime.datetime(2016, 7, 25, 13, 33, 18, 137493,
# tzinfo=<DstTzInfo 'US/Mountain' MDT-1 day, 18:00:00 DST>)
Shifting, Replacing, and Copying#
Zulu can easily apply timedelta’s using the shift
method:
shifted = dt.shift(hours=-5, minutes=10)
# <Zulu [2016-07-25T14:43:18.137493+00:00]>
assert shifted is not dt
And add and subtract with the add
and subtract
methods:
shifted = dt.subtract(hours=5).add(minutes=10)
# <Zulu [2016-07-25T14:43:18.137493+00:00]>
# First argument to subtract() can be a timedelta or dateutil.relativedelta
shifted = dt.subtract(timedelta(hours=5))
# <Zulu [2016-07-25T14:33:18+00:00]>
# First argument to subtract() can also be another datetime object
dt.subtract(shifted)
# datetime.timedelta(0, 18000)
# First argument to add() can be a timedelta or dateutil.relativedelta
dt.add(timedelta(minutes=10))
# <Zulu [2016-07-25T19:43:18+00:00]>
Or replace datetime attributes:
replaced = dt.replace(hour=14, minute=43)
# <Zulu [2016-07-25T14:43:18.137493+00:00]>
assert replaced is not dt
Or even make a copy:
copied = dt.copy()
# <Zulu [2016-07-25T19:33:18.137493+00:00]>
assert copied is not dt
assert copied == dt
Note
Since Zulu
is meant to be immutable, both shift
, replace
, and copy
return new Zulu
instances while leaving the original instance unchanged.
Spans, Ranges, Starts, and Ends#
You can get the span across a time frame:
dt = Zulu(2015, 4, 4, 12, 30, 37, 651839)
dt.span('century')
# (<Zulu [2000-01-01T00:00:00+00:00]>, <Zulu [2099-12-31T23:59:59.999999+00:00]>)
dt.span('decade')
# (<Zulu [2010-01-01T00:00:00+00:00]>, <Zulu [2019-12-31T23:59:59.999999+00:00]>)
dt.span('year')
# (<Zulu [2015-01-01T00:00:00+00:00]>, <Zulu [2015-12-31T23:59:59.999999+00:00]>)
dt.span('month')
# (<Zulu [2015-04-01T00:00:00+00:00]>, <Zulu [2015-04-30T23:59:59.999999+00:00]>)
dt.span('week')
# (<Zulu [2015-03-30T00:00:00+00:00]>, <Zulu [2015-04-05T23:59:59.999999+00:00]>)
dt.span('day')
# (<Zulu [2015-04-04T00:00:00+00:00]>, <Zulu [2015-04-04T23:59:59.999999+00:00]>)
dt.span('hour')
# (<Zulu [2015-04-04T12:00:00+00:00]>, <Zulu [2015-04-04T12:59:59.999999+00:00]>)
dt.span('minute')
# (<Zulu [2015-04-04T12:30:00+00:00]>, <Zulu [2015-04-04T12:30:59.999999+00:00]>)
dt.span('second')
# (<Zulu [2015-04-04T12:30:37+00:00]>, <Zulu [2015-04-04T12:30:37.999999+00:00]>)
dt.span('century', count=3)
# (<Zulu [2000-01-01T00:00:00+00:00]>, <Zulu [2299-12-31T23:59:59.999999+00:00]>)
dt.span('decade', count=3)
# (<Zulu [2010-01-01T00:00:00+00:00]>, <Zulu [2039-12-31T23:59:59.999999+00:00]>)
Or you can get just the start or end of a time frame:
dt.start_of('day') # OR dt.start_of_day()
# <Zulu [2015-04-04T00:00:00+00:00]>
dt.end_of('day') # OR dt.end_of_day()
# <Zulu [2015-04-04T23:59:59.999999+00:00]>
dt.end_of('year', count=3) # OR dt.end_of_year()
# <Zulu [2017-12-31T23:59:59.999999+00:00]>
Note
Supported time frames are century
, decade
, year
, month
, week
, day
, hour
, minute
, second
and are accessible both from start_of(frame)
/end_of(frame)
and start_of_<frame>()
/end_of_<frame>
.
You can get a range of time spans:
start = Zulu(2015, 4, 4, 12, 30)
end = Zulu(2015, 4, 4, 16, 30)
for span in zulu.span_range('hour', start, end):
print(span)
# (<Zulu [2015-04-04T12:00:00+00:00]>, <Zulu [2015-04-04T12:59:59.999999+00:00]>)
# (<Zulu [2015-04-04T13:00:00+00:00]>, <Zulu [2015-04-04T13:59:59.999999+00:00]>)
# (<Zulu [2015-04-04T14:00:00+00:00]>, <Zulu [2015-04-04T14:59:59.999999+00:00]>)
# (<Zulu [2015-04-04T15:00:00+00:00]>, <Zulu [2015-04-04T15:59:59.999999+00:00]>)
Or you can iterate over a range of datetimes:
start = Zulu(2015, 4, 4, 12, 30)
end = Zulu(2015, 4, 4, 16, 30)
for dt in zulu.range('hour', start, end):
print(dt)
# <Zulu [2015-04-04T12:30:00+00:00]>
# <Zulu [2015-04-04T13:30:00+00:00]>
# <Zulu [2015-04-04T14:30:00+00:00]>
Note
Supported range/span time frames are century
, decade
, year
, month
, week
, day
, hour
, minute
, second
.
Time Deltas#
In addition to having a drop-in replacement for datetime
, zulu also has a drop-in replacement for timedelta
:
delta = zulu.parse_delta('1w 3d 2h 32m')
# <Delta [10 days, 2:32:00]>
assert isinstance(delta, zulu.Delta)
from datetime import timedelta
assert isinstance(delta, timedelta)
zulu.parse_delta('2:04:13:02.266')
# <Delta [2 days, 4:13:02.266000]>
zulu.parse_delta('2 days, 5 hours, 34 minutes, 56 seconds')
# <Delta [2 days, 5:34:56]>
Other formats that zulu.parse_delta
can parse are:
32m
2h32m
3d2h32m
1w3d2h32m
1w 3d 2h 32m
1 w 3 d 2 h 32 m
4:13
4:13:02
4:13:02.266
2:04:13:02.266
2 days, 4:13:02
(uptime
format)2 days, 4:13:02.266
5hr34m56s
5 hours, 34 minutes, 56 seconds
5 hrs, 34 mins, 56 secs
2 days, 5 hours, 34 minutes, 56 seconds
1.2 m
1.2 min
1.2 mins
1.2 minute
1.2 minutes
172 hours
172 hr
172 h
172 hrs
172 hour
1.24 days
5 d
5 day
5 days
5.6 wk
5.6 week
5.6 weeks
Similar to Zulu.time_to/from
, Delta
objects can be humanized with the Delta.format
method:
delta = zulu.parse_delta('2h 32m')
# <Delta [2:32:00]>
delta.format()
# '3 hours'
delta.format(add_direction=True)
# 'in 3 hours'
zulu.parse_delta('-2h 32m').format(add_direction=True)
# '3 hours ago'
delta.format(granularity='day')
# '1 day'
delta.format(locale='de')
# '3 Stunden'
delta.format(locale='fr', add_direction=True)
# 'dans 3 heures'
delta.format(threshold=0)
# '0 years'
delta.format(threshold=0.1)
# '0 days'
delta.format(threshold=0.2)
# '3 hours'
delta.format(threshold=5)
# '152 minutes'
delta.format(threshold=155)
# '9120 seconds'
delta.format(threshold=155, granularity='minute')
# '152 minutes'
delta.format(style='long')
# '3 hours'
delta.format(style='short')
# '3 hr'
delta.format(style='narrow')
# '3h'
Utilities#
zulu.to_seconds#
Easily convert time units from microseconds to weeks into seconds:
zulu.to_seconds(seconds=5, minutes=2, hours=3, days=2, weeks=1)
# 788525
zulu.to_seconds(milliseconds=25300, seconds=5, minutes=2)
# 150.3
zulu.Timer#
A timer object that can be used for keeping track of elapsed time or as a coutdown timer.
As a timer:
timer = zulu.Timer()
timer.start()
# is the same as...
timer = zulu.Timer().start()
timer.started()
# True
timer.stopped()
# False
timer.elapsed()
# 0.0003867149353027344
timer.stop()
timer.elapsed()
# 0.0009307861328125
Can be used as a context manager to track the duration of blocks of code:
timer = zulu.Timer()
with timer:
time.sleep(1)
# is the same as ...
# with zulu.Timer() as timer:
# ...
timer.elapsed()
# 1.001131534576416
# can be used multiple times to accumulate durations
with timer:
time.sleep(2)
timer.elapsed()
# 3.0032811164855957
# reset the timer
timer.reset()
timer.started()
# False
timer.elapsed()
# 0
And as a countdown timer:
# timer that runs out after 15 seconds
timer = zulu.Timer(timeout=15)
timer.start()
timer.done()
# False
timer.remaining()
# 14.999720811843872
time.sleep(5)
timer.remaining()
# 9.99406123161316
time.sleep(10)
timer.done()
# True
timer.remaining()
# -0.01725912094116211
# restart the timer by calling start() again
timer.start()
timer.done()
# False