Times and Dates
Time is an essential component of nearly all geoscience data. Timescales span orders of magnitude from microseconds for lightning, hours for a supercell thunderstorm, days for a global weather model, millenia and beyond for the earth’s climate. To properly analyze geoscience data, you must have a firm understanding of how to handle time in Python. In this notebook, we will examine the Python Standard Library for handling dates and times. We will also briefly make use of the pytz module to handle some thorny time zone issues in Python.
Time
Versus Datetime
Modules and Some Core Concepts
Python comes with time and datetime modules. Unfortunately, Python can be initially disorienting because of the heavily overlapping terminology concerning dates and times:
datetime
module has adatetime
classdatetime
module has atime
classdatetime
module has adate
classtime
module has atime
function which returns (almost always) Unix timedatetime
class has adate
method which returns adate
objectdatetime
class has atime
method which returns atime
object
This confusion can be partially alleviated by aliasing our imported modules:
import datetime as dt
# we can now reference the datetime module (alaised to 'dt') and datetime
# object unambiguously
pisecond = dt.datetime(2016, 3, 14, 15, 9, 26)
print(pisecond)
import time as tm
now = tm.time()
print(now)
time
module
The time
module is well-suited for measuring Unix time. For example, when you are calculating how long it takes a Python function to run (so-called “benchmarking”), you can employ the time()
function from the time
module to obtain Unix time before and after the function completes and take the difference of those two times.
import time as tm
start = tm.time()
tm.sleep(1) # The sleep function will stop the program for n seconds
end = tm.time()
diff = end - start
print(f"The benchmark took {diff} seconds")
(For more accurate benchmarking, see the timeit module.)
datetime
module
The datetime
module handles time with the Gregorian calendar (the calendar we are all familiar with) and is independent of Unix time. The datetime
module has an object-oriented approach with the date
, time
, datetime
, timedelta
, and tzinfo
classes.
date
class represents the day, month and yeartime
class represents the time of daydatetime
class is a combination of thedate
andtime
classestimedelta
class represents a time durationtzinfo
(abstract) class represents time zones
The datetime
module is effective for:
- performing date and time arithmetic and calculating time duration
- reading and writing date and time strings in a particular format
- handling time zones (with the help of third-party libraries)
The time
and datetime
modules overlap in functionality, but in your geoscientific work, you will probably be using the datetime
module more than the time
module.
What is Unix Time?
Unix time is an example of system time which is the computer’s notion of passing time. It is measured in seconds from the the start of the epoch which is January 1, 1970 00:00 UTC. It is represented “under the hood” as a floating point number which is how computers represent real (ℝ) numbers .
The Thirty Second Introduction to Object-Oriented Programming
We have been talking about object-oriented (OO) programming by mentioning terms like “class”, “object”, and “method”, but never really explaining what they mean. A class is a collection of related variables, similar to a struct, in the C programming language or even a tuple in Python) coupled with functions, or “methods” in OO parlance, that can act on those variables. An object is a concrete example of a class.
For example, if you have a Coord
class that represents an earth location with latitude, and longitude, you may have a method that returns the distance between two locations, distancekm()
in this example.
import math
class Coord:
"""Earth location identified by (latitude, longitude) coordinates.
distancekm -- distance between two points in kilometers
"""
def __init__(self, latitude=0.0, longitude=0.0):
self.lat = latitude
self.lon = longitude
def distancekm(self, p):
"""Distance between two points in kilometers."""
DEGREES_TO_RADIANS = math.pi / 180.0
EARTH_RADIUS = 6373 # in KMs
phi1 = (90.0 - self.lat) * DEGREES_TO_RADIANS
phi2 = (90.0 - p.lat) * DEGREES_TO_RADIANS
theta1 = self.lon * DEGREES_TO_RADIANS
theta2 = p.lon * DEGREES_TO_RADIANS
cos = (math.sin(phi1) * math.sin(phi2) *
math.cos(theta1 - theta2) + math.cos(phi1) * math.cos(phi2))
arc = math.acos(cos)
return arc * EARTH_RADIUS
To create a concrete example of a class, also known as an object, initialize the object with data:
timbuktu = Coord(16.77, 3.00)
Here, timbuktu
is an object of the class Coord
initialized with a latitude of 16.77
and a longitude of 3.00
.
Next, we create two Coord
objects: ny
and paris
. We will invoke the distancekm()
method on the ny
object and pass the paris
object as an argument to determine the distance between New York and Paris in kilometers.
ny = Coord(40.71, 74.01)
paris = Coord(48.86, 2.35)
distance = ny.distancekm(paris)
print(f"The distance from New York to Paris is {distance:.1f} kilometers.")
The old joke about OO programming is that they simply moved the struct that the function takes as an argument and put it first because it is special. So instead of having distancekm(ny, paris)
, you have ny.distancekm(paris)
. We have not talked about inheritance or polymorphism but that is OO in a nutshell.
Reading and Writing Dates and Times
Parsing Lightning Data Timestamps with the datetime.strptime
Method
Suppose you want to analyze US NLDN lightning data. Here is a sample row of data:
06/27/07 16:18:21.898 18.739 -88.184 0.0 kA 0 1.0 0.4 2.5 8 1.2 13 G
Part of the task involves parsing the 06/27/07 16:18:21.898
time string into a datetime
object. (The full description of the data are described here.) In order to parse this string or others that follow the same format, you will employ the datetime.strptime() method from the datetime
module. This method takes two arguments: the first is the date time string you wish to parse, the second is the format which describes exactly how the date and time are arranged. The full range of format options is described in the Python documentation. In reality, the format will take some degree of experimentation to get right. This is a situation where Python shines as you can quickly try out different solutions in the IPython interpreter. Beyond the official documentation, Google and Stack Overflow are your friends in this process. Eventually, after some trial and error, you will find the '%m/%d/%y %H:%M:%S.%f'
format will properly parse the date and time.
import datetime as dt
strike_time = dt.datetime.strptime('06/27/07 16:18:21.898',
'%m/%d/%y %H:%M:%S.%f')
# print strike_time to see if we have properly parsed our time
print(strike_time)