1Exercises
2=========
3
4It is often useful to work through some examples in order to understand how a module works; on this page, there are several exercises of varying difficulty that you can use to learn how to use ``dateutil``.
5
6If you are interested in helping improve the documentation of ``dateutil``, it is recommended that you attempt to complete these exercises with no resources *other than dateutil's documentation*. If you find that the documentation is not clear enough to allow you to complete these exercises, open an issue on the `dateutil issue tracker <https://github.com/dateutil/dateutil/issues>`_ to let the developers know what part of the documentation needs improvement.
7
8
9.. contents:: Table of Contents
10    :backlinks: top
11    :local:
12
13
14.. _mlk-day-exercise:
15
16Martin Luther King Day
17--------------------------------
18
19
20    `Martin Luther King, Jr Day <https://en.wikipedia.org/wiki/Martin_Luther_King_Jr._Day>`_ is a US holiday that occurs every year on the third Monday in January?
21
22    How would you generate a `recurrence rule <../rrule.html>`_ that generates Martin Luther King Day, starting from its first observance in 1986?
23
24
25**Test Script**
26
27To solve this exercise, copy-paste this script into a document, change anything between the ``--- YOUR CODE ---`` comment blocks.
28
29.. raw:: html
30
31    <details>
32
33.. code-block:: python3
34
35    # ------- YOUR CODE -------------#
36    from dateutil import rrule
37
38    MLK_DAY = <<YOUR CODE HERE>>
39
40    # -------------------------------#
41
42    from datetime import datetime
43    MLK_TEST_CASES = [
44        ((datetime(1970, 1, 1), datetime(1980, 1, 1)),
45         []),
46        ((datetime(1980, 1, 1), datetime(1989, 1, 1)),
47         [datetime(1986, 1, 20),
48          datetime(1987, 1, 19),
49          datetime(1988, 1, 18)]),
50        ((datetime(2017, 2, 1), datetime(2022, 2, 1)),
51         [datetime(2018, 1, 15, 0, 0),
52          datetime(2019, 1, 21, 0, 0),
53          datetime(2020, 1, 20, 0, 0),
54          datetime(2021, 1, 18, 0, 0),
55          datetime(2022, 1, 17, 0, 0)]
56         ),
57    ]
58
59    def test_mlk_day():
60        for (between_args, expected) in MLK_TEST_CASES:
61            assert MLK_DAY.between(*between_args) == expected
62
63    if __name__ == "__main__":
64        test_mlk_day()
65        print('Success!')
66
67.. raw:: html
68
69    </details>
70
71A solution to this problem is provided :doc:`here <solutions/mlk-day-rrule>`.
72
73
74Next Monday meeting
75-------------------
76
77    A team has a meeting at 10 AM every Monday and wants a function that tells them, given a ``datetime.datetime`` object, what is the date and time of the *next* Monday meeting? This is probably best accomplished using a `relativedelta <../relativedelta.html>`_.
78
79**Test Script**
80
81To solve this exercise, copy-paste this script into a document, change anything between the ``--- YOUR CODE ---`` comment blocks.
82
83.. raw:: html
84
85    <details>
86
87
88.. code-block:: python3
89
90    # --------- YOUR CODE -------------- #
91    from dateutil import relativedelta
92
93    def next_monday(dt):
94        <<YOUR CODE HERE>>
95
96    # ---------------------------------- #
97
98    from datetime import datetime
99    from dateutil import tz
100
101    NEXT_MONDAY_CASES = [
102        (datetime(2018, 4, 11, 14, 30, 15, 123456),
103         datetime(2018, 4, 16, 10, 0)),
104        (datetime(2018, 4, 16, 10, 0),
105         datetime(2018, 4, 16, 10, 0)),
106        (datetime(2018, 4, 16, 10, 30),
107         datetime(2018, 4, 23, 10, 0)),
108        (datetime(2018, 4, 14, 9, 30, tzinfo=tz.gettz('America/New_York')),
109         datetime(2018, 4, 16, 10, 0, tzinfo=tz.gettz('America/New_York'))),
110    ]
111
112    def test_next_monday_1():
113        for dt_in, dt_out in NEXT_MONDAY_CASES:
114            assert next_monday(dt_in) == dt_out
115
116    if __name__ == "__main__":
117        test_next_monday_1()
118        print('Success!')
119
120.. raw:: html
121
122    </details>
123
124
125Parsing a local tzname
126----------------------
127
128    Three-character time zone abbreviations are *not* unique in that they do not explicitly map to a time zone. A list of time zone abbreviations in use can be found `here <https://www.timeanddate.com/time/zones/>`_. This means that parsing a datetime string such as ``'2018-01-01 12:30:30 CST'`` is ambiguous without context. Using `dateutil.parser <../parser.html>`_ and `dateutil.tz <../tz.html>`_, it is possible to provide a context such that these local names are converted to proper time zones.
129
130Problem 1
131*********
132    Given the context that you will only be parsing dates coming from the continental United States, India and Japan, write a function that parses a datetime string and returns a timezone-aware ``datetime`` with an IANA-style timezone attached.
133
134    Note: For the purposes of the experiment, you may ignore the portions of the United States like Arizona and parts of Indiana that do not observe daylight saving time.
135
136**Test Script**
137
138To solve this exercise, copy-paste this script into a document, change anything between the ``--- YOUR CODE ---`` comment blocks.
139
140.. raw:: html
141
142    <details>
143
144
145.. code-block:: python3
146
147    # --------- YOUR CODE -------------- #
148    from dateutil.parser import parse
149    from dateutil import tz
150
151    def parse_func_us_jp_ind():
152        <<YOUR CODE HERE>>
153
154    # ---------------------------------- #
155
156    from dateutil import tz
157    from datetime import datetime
158
159
160    PARSE_TZ_TEST_DATETIMES = [
161        datetime(2018, 1, 1, 12, 0),
162        datetime(2018, 3, 20, 2, 0),
163        datetime(2018, 5, 12, 3, 30),
164        datetime(2014, 9, 1, 23)
165    ]
166
167    PARSE_TZ_TEST_ZONES = [
168        tz.gettz('America/New_York'),
169        tz.gettz('America/Chicago'),
170        tz.gettz('America/Denver'),
171        tz.gettz('America/Los_Angeles'),
172        tz.gettz('Asia/Kolkata'),
173        tz.gettz('Asia/Tokyo'),
174    ]
175
176    def test_parse():
177        for tzi in PARSE_TZ_TEST_ZONES:
178            for dt in PARSE_TZ_TEST_DATETIMES:
179                dt_exp = dt.replace(tzinfo=tzi)
180                dtstr = dt_exp.strftime('%Y-%m-%d %H:%M:%S %Z')
181
182                dt_act = parse_func_us_jp_ind(dtstr)
183                assert dt_act == dt_exp
184                assert dt_act.tzinfo is dt_exp.tzinfo
185
186    if __name__ == "__main__":
187        test_parse()
188        print('Success!')
189
190.. raw:: html
191
192    </details>
193
194
195Problem 2
196*********
197    Given the context that you will *only* be passed dates from India or Ireland, write a function that correctly parses all *unambiguous* time zone strings to aware datetimes localized to the correct IANA zone, and for *ambiguous* time zone strings default to India.
198
199**Test Script**
200
201To solve this exercise, copy-paste this script into a document, change anything between the ``--- YOUR CODE ---`` comment blocks.
202
203
204.. raw:: html
205
206    <details>
207
208.. code-block:: python3
209
210    # --------- YOUR CODE -------------- #
211    from dateutil.parser import parse
212    from dateutil import tz
213
214    def parse_func_ind_ire():
215        <<YOUR CODE HERE>>
216
217    # ---------------------------------- #
218    ISRAEL = tz.gettz('Asia/Jerusalem')
219    INDIA = tz.gettz('Asia/Kolkata')
220    PARSE_IXT_TEST_CASE = [
221        ('2018-02-03 12:00 IST+02:00', datetime(2018, 2, 3, 12, tzinfo=ISRAEL)),
222        ('2018-06-14 12:00 IDT+03:00', datetime(2018, 6, 14, 12, tzinfo=ISRAEL)),
223        ('2018-06-14 12:00 IST', datetime(2018, 6, 14, 12, tzinfo=INDIA)),
224        ('2018-06-14 12:00 IST+05:30', datetime(2018, 6, 14, 12, tzinfo=INDIA)),
225        ('2018-02-03 12:00 IST', datetime(2018, 2, 3, 12, tzinfo=INDIA)),
226    ]
227
228
229    def test_parse_ixt():
230        for dtstr, dt_exp in PARSE_IXT_TEST_CASE:
231            dt_act = parse_func_ind_ire(dtstr)
232            assert dt_act == dt_exp, (dt_act, dt_exp)
233            assert dt_act.tzinfo is dt_exp.tzinfo, (dt_act, dt_exp)
234
235    if __name__ == "__main__":
236        test_parse_ixt()
237        print('Success!')
238
239.. raw:: html
240
241    </details>
242
243