Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up time tutorial #6920

Merged
merged 5 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/6920.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improved the time tutorial.
245 changes: 37 additions & 208 deletions docs/tutorial/time.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,245 +4,74 @@
Times
*****

Working with times and time ranges is a standard task in solar data analysis and as such
sunpy strives to provide convenient and easy methods to do the simple stuff. Python
already provides an object for a time or date through `datetime.datetime`.
However, `datetime.datetime` does not provide support for common time formats used in
solar physics nor leap seconds. To alleviate this, we use `astropy.time.Time` internally
which allows us to provide a superior user experience.
In this section of the tutorial, you will learn how times are represented and manipulated in sunpy.
sunpy makes extensive use of the `astropy.time` module for this task.
By the end of this section of the tutorial, you will learn how to parse times from different formats as well as construct and inspect time ranges.

.. _parse-time:

Parsing Times
=============

Solar data is associated with a number of different time formats. sunpy provides a simple
parsing function which can deal with most every format that a user may encounter. Called
`sunpy.time.parse_time()`, this function can take a variety of inputs.

Strings
-------

The most commonly used are strings and we support a selection of formats
which are matched using regrex. We currently support the following style of string formats::

"2007-05-04T21:08:12.999999"
"2007/05/04T21:08:12.999999"
"2007-05-04T21:08:12.999Z"
"2007-05-04T21:08:12"
"2007/05/04T21:08:12"
"20070504T210812.999999"
"20070504T210812"
"2007/05/04 21:08:12"
"2007/05/04 21:08"
"2007/05/04 21:08:12.999999"
"2007-05-04 21:08:12.999999"
"2007-05-04 21:08:12"
"2007-05-04 21:08"
"2007-May-04 21:08:12"
"2007-May-04 21:08"
"2007-May-04"
"2007-05-04"
"2007/05/04"
"04-May-2007"
"04-May-2007 21:08:12.999999"
"20070504_210812"
"2012:124:21:08:12"
"2012:124:21:08:12.999999"
"20140101000001"
"2016.05.04_21:08:12_TAI"

If we pass some of these strings into `sunpy.time.parse_time()`::
Solar data is associated with a number of different time formats.
To handle all these formats, sunpy has :meth:`sunpy.time.parse_time` that accepts a variety of inputs, and returns a consistent `~astropy.time.Time` object.
You might have come across another way of storing time that's built into Python itself, `datetime.datetime`.
`~datetime.datetime` does not provide support for common time formats used in solar physics or leap seconds, hence the use of `astropy.time.Time` throughout sunpy.

Here's a few examples of using `~sunpy.time.parse_time` to create `~astropy.time.Time` objects::

>>> from sunpy.time import parse_time
>>> parse_time('2007-05-04T21:08:12')
<Time object: scale='utc' format='isot' value=2007-05-04T21:08:12.000>
>>> parse_time('2007/05/04T21:08:12')
<Time object: scale='utc' format='isot' value=2007-05-04T21:08:12.000>
>>> parse_time('20070504T210812')
<Time object: scale='utc' format='isot' value=2007-05-04T21:08:12.000>
>>> parse_time('2007-May-04 21:08:12')
<Time object: scale='utc' format='isot' value=2007-05-04T21:08:12.000>
>>> parse_time('20070504_210812')
<Time object: scale='utc' format='isot' value=2007-05-04T21:08:12.000>

Each of the above returns the same `~astropy.time.Time` object ``<Time object: scale='utc' format='isot' value=2007-05-04T21:08:12.000>``.

We also support ``utime``, which is the amount of seconds from 1979-01-01 00:00:00 UTC.
Same as Unix time but this starts 9 years later. The parse_time function also accepts this as input, e.g.::

>>> parse_time(894316092.00000000, format='utime')
<Time object: scale='utc' format='utime' value=894316092.0>

Other formats
-------------

`sunpy.time.parse_time()` understands more than just a string.
For example::

>>> import datetime
>>> parse_time(datetime.datetime(2007, 5, 4, 21, 8, 12)) # datetime.datetime
<Time object: scale='utc' format='datetime' value=2007-05-04 21:08:12>
>>> parse_time(datetime.date(2007, 5, 4)) # datetime.date
<Time object: scale='utc' format='iso' value=2007-05-04 00:00:00.000>

>>> parse_time((2007, 5, 4, 21, 8, 12)) # tuple of numbers
<Time object: scale='utc' format='isot' value=2007-05-04T21:08:12.000>

>>> import pandas
>>> parse_time(pandas.Timestamp('2007-05-04T21:08:12')) # pandas.Timestamp
<Time object: scale='utc' format='datetime64' value=2007-05-04T21:08:12.000000000>

>>> time_ranges = [datetime.datetime(2007, 5, i) for i in range(1, 3)]
>>> parse_time(pandas.Series(time_ranges)) # pandas.Series
<Time object: scale='utc' format='datetime' value=[datetime.datetime(2007, 5, 1, 0, 0) datetime.datetime(2007, 5, 2, 0, 0)]>

>>> parse_time(pandas.DatetimeIndex(time_ranges)) # pandas.DatetimeIndex
<Time object: scale='utc' format='datetime' value=[datetime.datetime(2007, 5, 1, 0, 0) datetime.datetime(2007, 5, 2, 0, 0)]>

>>> import numpy as np
>>> parse_time(np.datetime64('2007-05-04T00')) # np.datetime64
<Time object: scale='utc' format='isot' value=2007-05-04T00:00:00.000>
>>> parse_time(np.arange('2007-05-03', '2007-05-04', dtype='datetime64[D]')) # np.ndarray
<Time object: scale='utc' format='isot' value=['2007-05-03T00:00:00.000']>

`astropy.time.Time` API comparison
----------------------------------

`sunpy.time.parse_time` is a wrapper around `astropy.time.Time`. The API is
nearly identical as `~astropy.time.Time` but supports more time input formats.
You can specify the format, scale, precision, location and other arguments just
as you would do with `~astropy.time.Time`. An example::

>>> times = ['1999-01-01T00:00:00.123456789', '2010-01-01T00:00:00']
>>> parse_time(times, format='isot', scale='tai')
<Time object: scale='tai' format='isot' value=['1999-01-01T00:00:00.123' '2010-01-01T00:00:00.000']>

Please be aware that all sunpy functions which require time as an input sanitize the input using `~sunpy.time.parse_time`.
See the documentation for `sunpy.time.parse_time` for a full list of allowed arguments.

Time Ranges
===========

A very standard task in data analysis is to have to deal with pairs of times or time
ranges. This occurs very often with plotting or when searching for data. To deal with
time ranges sunpy provides the `sunpy.time.TimeRange` object. A TimeRange object can be created
very easily by providing it with two time strings, a start time and an end time: ::
Another standard task in data analysis is dealing with pairs of times or time ranges.
To deal with time ranges sunpy provides the `sunpy.time.TimeRange` object.
A `sunpy.time.TimeRange` object can be created by providing a start time and an end time::

>>> from sunpy.time import TimeRange
>>> time_range = TimeRange('2010/03/04 00:10', '2010/03/04 00:20')

You can also pass the start and end times as a tuple: ::

>>> time_range = TimeRange(('2010/03/04 00:10', '2010/03/04 00:20'))

This object makes use of parse_time() so it can accept a wide variety of time formats.
A time range object can also be created by providing a start time and a duration.
The duration must be provided as a `~astropy.time.TimeDelta` or
time-equivalent `astropy.units.Quantity` or `datetime.timedelta` object
example: ::
`~sunpy.time.TimeRange` makes use of :meth:`sunpy.time.parse_time` so it can accept a wide variety of time formats.
Alternatively, you can specify a start time and a duration::

>>> import astropy.units as u
>>> time_range = TimeRange('2010/03/04 00:10', 400 * u.second)

or: ::

>>> import astropy.units as u
>>> from astropy.time import TimeDelta
>>> time_range = TimeRange('2010/03/04 00:10', TimeDelta(400 * u.second))

or: ::

>>> from datetime import timedelta
>>> time_range = TimeRange('2010/03/04 00:10', timedelta(0, 400))

The time range objects provides a number of useful functions. For example, you can easily
get the time at the center of your interval or the length of your interval in minutes
or days or seconds: ::
The time range objects provides a number of useful functions.
For example, you can easily get the time at the center of your interval or the length of the interval::

>>> time_range.center
<Time object: scale='utc' format='isot' value=2010-03-04T00:13:20.000>
>>> time_range.minutes
<Quantity 6.66666667 min>
>>> time_range.days
<Quantity 0.00462963 d>
>>> time_range.seconds
<Quantity 400. s>

It also makes it easy to create new time ranges. The functions next() and previous()
do an inplace update to the object by either adding or subtracting the same time interval
. This could be useful if you need to step through a number of time ranges. For example,
if you needed time ranges that spanned 30 minutes over a period of 4 hours you could do: ::

>>> for a in range(8):
... print(time_range.next()) # doctest: +IGNORE_OUTPUT
Start: 2010-03-04 00:16:40
End: 2010-03-04 00:23:20
Center:2010-03-04 00:20:00
Duration:0.00462962962963 days or
0.111111111111 hours or
6.66666666667 minutes or
400.0 seconds
<BLANKLINE>
Start: 2010-03-04 00:23:20
End: 2010-03-04 00:30:00
Center:2010-03-04 00:26:40
Duration:0.00462962962963 days or
0.111111111111 hours or
6.66666666667 minutes or
400.0 seconds
<BLANKLINE>
Start: 2010-03-04 00:30:00
End: 2010-03-04 00:36:40
Center:2010-03-04 00:33:20
Duration:0.00462962962963 days or
0.111111111111 hours or
6.66666666667 minutes or
400.0 seconds
<BLANKLINE>
Start: 2010-03-04 00:36:40
End: 2010-03-04 00:43:20
Center:2010-03-04 00:40:00
Duration:0.00462962962963 days or
0.111111111111 hours or
6.66666666667 minutes or
400.0 seconds
<BLANKLINE>
Start: 2010-03-04 00:43:20
End: 2010-03-04 00:50:00
Center:2010-03-04 00:46:40
Duration:0.00462962962963 days or
0.111111111111 hours or
6.66666666667 minutes or
400.0 seconds
<BLANKLINE>
Start: 2010-03-04 00:50:00
End: 2010-03-04 00:56:40
Center:2010-03-04 00:53:20
Duration:0.00462962962963 days or
0.111111111111 hours or
6.66666666667 minutes or
400.0 seconds
<BLANKLINE>
Start: 2010-03-04 00:56:40
End: 2010-03-04 01:03:20
Center:2010-03-04 01:00:00
Duration:0.00462962962963 days or
0.111111111111 hours or
6.66666666667 minutes or
400.0 seconds
<BLANKLINE>
Start: 2010-03-04 01:03:20
End: 2010-03-04 01:10:00
Center:2010-03-04 01:06:40
Duration:0.00462962962963 days or
0.111111111111 hours or
6.66666666667 minutes or
400.0 seconds
<BLANKLINE>

A time range can also be easily split into sub-intervals of equal length, for example to
split a TimeRange object into two new TimeRange objects: ::

time_range.split(2)
split a TimeRange object into two new TimeRange objects::

>>> time_range.split(2)
[ <sunpy.time.timerange.TimeRange object ...>
Start: 2010-03-04 00:10:00
End: 2010-03-04 00:13:20
Center:2010-03-04 00:11:40
Duration:0.002314814814814825 days or
0.0555555555555558 hours or
3.333333333333348 minutes or
200.00000000000088 seconds
, <sunpy.time.timerange.TimeRange object ...>
Start: 2010-03-04 00:13:20
End: 2010-03-04 00:16:40
Center:2010-03-04 00:15:00
Duration:0.002314814814814825 days or
0.0555555555555558 hours or
3.333333333333348 minutes or
200.00000000000088 seconds
]

Check out the code reference for the `sunpy.time.TimeRange` object for more information.
7 changes: 4 additions & 3 deletions sunpy/time/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,14 @@ def parse_time(time_string, *, format=None, **kwargs):

format : `str`, optional
Specifies the format user has provided the time_string in.
We support the same formats of `astropy.time.Time`.

The allowed values for ``format`` can be listed with::
We support the same formats as `astropy.time.Time`, which are::

>>> list(astropy.time.Time.FORMATS)
{astropy_time_formats}

**kwargs :
Additional keyword arguments are passed to `astropy.time.Time`

Returns
-------
`astropy.time.Time`
Expand Down