Dates and times#
We’ll get to the thorny issue of dates in a moment, but first let’s look at a little timer function to time your code.
Click here to open an interactive version of this notebook.
Timing#
The most basic form of profiling (as covered in the previous tutorial) is just timing how long different parts of your code take. It’s not too hard to do this in Python:
[1]:
import time
import numpy as np
n = 5_000
start = time.time()
zeros = np.zeros((n,n))
zeros_time = time.time()
rand = np.random.rand(n,n)
rand_time = time.time()
print(f'Time to make zeros: {(zeros_time - start):n} s')
print(f'Time to make random numbers: {(rand_time - zeros_time):n} s')
Time to make zeros: 0.000112772 s
Time to make random numbers: 0.238331 s
As you probably could’ve guessed, in Sciris there’s an easier way, inspired by Matlab’s tic and toc:
[2]:
import sciris as sc
T = sc.timer()
T.tic()
zeros = np.zeros((n,n))
T.toc('Time to make zeros')
T.tic()
rand = np.random.rand(n,n)
T.toc('Time to make random numbers')
Time to make zeros: 0.116 ms
Time to make random numbers: 0.267 s
We can simplify this even further: we often call toc()
followed by tic()
, so instead we can just call toctic()
or tt()
for short; we can also omit the first tic()
:
[3]:
T = sc.timer()
zeros = np.zeros((n,n))
T.tt('Time to make zeros')
rand = np.random.rand(n,n)
T.tt('Time to make random numbers')
Time to make zeros: 0.496 ms
Time to make random numbers: 0.222 s
You can also use sc.timer()
in a with
block, which is perhaps most intuitive of all:
[4]:
with sc.timer('Time to make zeros'):
zeros = np.zeros((n,n))
with sc.timer('Time to make random numbers'):
rand = np.random.rand(n,n)
Time to make zeros: 0.126 ms
Time to make random numbers: 0.221 s
If we have multiple timings, we can also do statistics on them or plot the results:
[5]:
T = sc.timer()
for i in range(5):
rnd = np.random.rand(int((i+1)*np.random.rand()*1e6))
T.tt(f'Generating {len(rnd):,} numbers')
print('mean', T.mean())
print('std', T.std())
print('min', T.min())
print('max', T.max())
T.plot();
Generating 912,538 numbers: 9.02 ms
Generating 962,779 numbers: 9.63 ms
Generating 955,250 numbers: 8.42 ms
Generating 3,586,531 numbers: 31.6 ms
Generating 2,420,965 numbers: 22.0 ms
mean 0.016143369674682616
std 0.009242714415095155
min 0.008424043655395508
max 0.031635284423828125
Sleeping#
For completeness, let’s talk about Sciris’ two sleep functions. Both are related to time.sleep()
.
The first is sc.timedsleep()
. If called directly it acts just like time.sleep()
. But you can also use it in a for loop to take into account the rest of the time taken by the other operations in the loop so that each loop iteration takes exactly the desired amount of time:
[6]:
import numpy as np
for i in range(5):
sc.timedsleep('start') # Initialize
n = int(np.random.rand()*1e6) # Variable computation time
for j in range(n):
tmp = np.random.rand()
sc.timedsleep(0.3, verbose=True) # Wait for 0.3 seconds per iteration including computation time
Pausing for 0.298346 s
Pausing for 0.0877296 s
Pausing for 0.276121 s
Pausing for 1e-12 s
Pausing for 0.142089 s
The other is sc.randsleep()
, which as the name suggests, will sleep for a random amount of time:
[7]:
for i in range(4):
with sc.timer(f'Run {i}', unit='ms'):
sc.randsleep(0.2) # Sleep for an average of 0.2 s, but with range 0-0.4 s
Run 0: 231 ms
Run 1: 322 ms
Run 2: 147 ms
Run 3: 278 ms
Dates#
There are lots of different common date formats in Python, which probably arose through a process like this. Python’s built-in one is datetime.datetime. This format has the basics, but is hard to work with for things like plotting. NumPy made their own, called datetime64, which addresses some of these issues, but isn’t compatible with anything else. Then pandas introduced their own Timestamp, which is kind of like a combination of both.
You will probably be relieved to know that Sciris does not introduce a new datetime format, but instead tries to make it easier to work with the other formats, particularly by being able to easily interconvert them. Sciris provides shortcuts to the three common ways of getting the current datetime:
[8]:
sc.time() # Equivalent to time.time()
[8]:
1727216509.3330975
[9]:
sc.now() # Equivalent to datetime.datetime.now()
[9]:
datetime.datetime(2024, 9, 24, 22, 21, 49, 339764)
[10]:
sc.getdate() # Equivalent to datetime.datetime.now().strftime('%Y-%b-%d %H:%M:%S')
[10]:
'2024-Sep-24 22:21:49'
Sciris’ main utility for converting between date formats is called sc.date()
. It works like this:
[11]:
sc.date('2022-03-04')
[11]:
datetime.date(2022, 3, 4)
It can interpret lots of different strings, although needs help with month-day-year or day-month-year formats:
[12]:
d1 = sc.date('04-03-2022', format='mdy')
d2 = sc.date('04-03-2022', format='dmy')
print(d1)
print(d2)
2022-04-03
2022-03-04
You can create an array of dates, either as strings or datetime objects:
[13]:
dates = sc.daterange('2022-02-02', '2022-03-04')
sc.pp(dates)
['2022-02-02',
'2022-02-03',
'2022-02-04',
'2022-02-05',
'2022-02-06',
'2022-02-07',
'2022-02-08',
'2022-02-09',
'2022-02-10',
'2022-02-11',
'2022-02-12',
'2022-02-13',
'2022-02-14',
'2022-02-15',
'2022-02-16',
'2022-02-17',
'2022-02-18',
'2022-02-19',
'2022-02-20',
'2022-02-21',
'2022-02-22',
'2022-02-23',
'2022-02-24',
'2022-02-25',
'2022-02-26',
'2022-02-27',
'2022-02-28',
'2022-03-01',
'2022-03-02',
'2022-03-03',
'2022-03-04']
And you can also do math on dates, even if they’re just strings:
[14]:
newdates = sc.datedelta(dates, months=10) # Add 10 months
sc.pp(newdates)
['2022-12-02',
'2022-12-03',
'2022-12-04',
'2022-12-05',
'2022-12-06',
'2022-12-07',
'2022-12-08',
'2022-12-09',
'2022-12-10',
'2022-12-11',
'2022-12-12',
'2022-12-13',
'2022-12-14',
'2022-12-15',
'2022-12-16',
'2022-12-17',
'2022-12-18',
'2022-12-19',
'2022-12-20',
'2022-12-21',
'2022-12-22',
'2022-12-23',
'2022-12-24',
'2022-12-25',
'2022-12-26',
'2022-12-27',
'2022-12-28',
'2023-01-01',
'2023-01-02',
'2023-01-03',
'2023-01-04']