|
| 1 | +# About |
| 2 | + |
| 3 | +_"Dates and times are something we teach to young children. How hard can it be?"_ |
| 4 | + |
| 5 | +Many programmers have made that mistake, and the subsequent experience tends to be negative to their health and happiness. |
| 6 | + |
| 7 | +Anyone doing non-trivial programming with dates and times should at least be prepared to understand and mitigate potential problems. |
| 8 | + |
| 9 | +## The `datetime` module |
| 10 | + |
| 11 | +In python, a wide range of date and time functionality is collected in the [`datetime`][datetime] module. |
| 12 | +This can be supplemented by other libraries, but `datetime` is central and often sufficient. |
| 13 | + |
| 14 | +There are five major classes within `datetime`: |
| 15 | + - `datetime.date` for simple dates |
| 16 | + - `datetime.time` for simple times |
| 17 | + - `datetime.datetime` combines date, time and optionally timezone information |
| 18 | + - `datetime.timedelta` for intervals |
| 19 | + - `datetime.timezone` to handle the reality that few people use UTC |
| 20 | + |
| 21 | + ___Notation detail:___ A `datetime.time` or `datetime.datetime` object that includes timezone information is said to be _aware_, otherwise it is _naive_. |
| 22 | + A `datetime.date` object is always naive. |
| 23 | + |
| 24 | +As `datetime` is a large module with many methods and attributes, only some of the most common will be discussed here. |
| 25 | + |
| 26 | +You are encouraged to explore the [full documentation][datetime]. |
| 27 | +Dates and times are complex but important, so the Python developers have put many years of effort into trying to support most use cases. |
| 28 | + |
| 29 | +Perhaps the most frequent needs are: |
| 30 | + |
| 31 | +- Parse some appropriate input format to construct a `datetime` object. |
| 32 | +This often uses [`strptime()`][strptime-strftime]. |
| 33 | +- Get the required numerical or string format from a `datetime` object. |
| 34 | +String output often uses [`strftime()`][strptime-strftime]. |
| 35 | +- Apply an offset to a `date`, `time` or `datetime` to create a new object (of the same type). |
| 36 | +- Calculate the interval between two such objects. |
| 37 | +- Get the current date and/or time. |
| 38 | +This will be obtained from the host computer and converted to a Python object. |
| 39 | + |
| 40 | + |
| 41 | +### Date and time formats |
| 42 | + |
| 43 | +There are many ways to write dates and times, which tend to be culturally-specific. |
| 44 | +All-number dates such as "7/6/23" are ambiguous, confusing, and have led to many expensive mistakes in multinational organizations. |
| 45 | + |
| 46 | +The international standard is defined in [`ISO 8601`][ISO8601], with two main advantages: |
| 47 | +- Parsing is quick and unambiguous. |
| 48 | +- Sorting is easy, as the datetime can be treated as text. |
| 49 | + |
| 50 | +An example: |
| 51 | + |
| 52 | +```python |
| 53 | +>>> from datetime import datetime |
| 54 | +>>> datetime.now(timezone.utc).isoformat() |
| 55 | +'2023-12-04T17:54:13.014513+00:00' |
| 56 | +``` |
| 57 | + |
| 58 | +This is built up from various parts, with only the date fields required: |
| 59 | +- `YYYY-MM-DD` |
| 60 | +- Optionally, `Thh:mm:ss` |
| 61 | +- Optionally, microseconds after the decimal point. |
| 62 | +- Optionally, timezone offset from UTC with a sign and `hh:mm` value. |
| 63 | + |
| 64 | +Internally, `date`, `time` and `datetime` are stored as Python objects with separate attributes for year, month, etc. |
| 65 | +Examples of this will be shown below, when each class is discussed. |
| 66 | + |
| 67 | +Most computer operating systems use POSIX timestamps: the number of seconds since `1970-01-01T00:00:00+00.00`. |
| 68 | +The `datetime` module makes it easy to import these. |
| 69 | + |
| 70 | +For code which interacts mainly with computers rather than humans, it may be worth investigating the separate [`time`][time] module, which provides more complete support for POSIX timestamps. |
| 71 | + |
| 72 | +## The [`datetime.date`][datetime-date] class |
| 73 | + |
| 74 | +[`datetime.date`][datetime-date] is a relatively small and simple date-only class, with no understanding of times or timezones. |
| 75 | + |
| 76 | +```python |
| 77 | +>>> from datetime import date |
| 78 | +>>> date.today() |
| 79 | +datetime.date(2023, 12, 4) |
| 80 | +>>> date.today().isoformat() |
| 81 | +'2023-12-04' |
| 82 | +``` |
| 83 | + |
| 84 | +The default display has the same `date(year, month, day)` syntax as the default constructor. |
| 85 | +A `date` object can also be created from an ISO 8601 date string or a POSIX timestamp. |
| 86 | + |
| 87 | +```python |
| 88 | +>>> date(1969, 7, 20) |
| 89 | +datetime.date(1969, 7, 20) |
| 90 | + |
| 91 | +>>> date.fromisoformat('1969-07-20') |
| 92 | +datetime.date(1969, 7, 20) |
| 93 | + |
| 94 | +>>> date.fromisoformat('1969-07-20') == date(1969, 7, 20) |
| 95 | +True |
| 96 | +``` |
| 97 | + |
| 98 | +Individual parts of the date can be accessed as instance attributes: |
| 99 | + |
| 100 | +```python |
| 101 | +>>> date.today().month # in December |
| 102 | +12 |
| 103 | +``` |
| 104 | + |
| 105 | +There are a number of other methods, mostly related to output formats. |
| 106 | +See the [class documentation][datetime-date] for details. |
| 107 | + |
| 108 | +`datetime.date` is designed to be fairly minimalist, to keep simple applications simple. |
| 109 | + |
| 110 | +If your application is ever likely to need times or timezones, it may be better to use `datetime.datetime` from the start. |
| 111 | + |
| 112 | +For more complex date-only applications, compare `datetime.date` with [`calendar`][calendar] and decide which better fits your needs. |
| 113 | + |
| 114 | +## The [`datetime.time`][datetime-time] class |
| 115 | + |
| 116 | +`datetime.time` is the basic time-only class. |
| 117 | +It has no understanding of dates: times automatically roll over to `time(0, 0, 0)` at midnight. |
| 118 | + |
| 119 | +Timezone information can optionally be included. |
| 120 | + |
| 121 | +The full constructor format is `timezone.time(hour, min, sec, microsec, timezone)`. |
| 122 | + |
| 123 | +All the parameters are optional: numerical values will default to `0`, timezone to `None`. |
| 124 | + |
| 125 | +```python |
| 126 | +>>> from datetime import time |
| 127 | +>>> time() |
| 128 | +datetime.time(0, 0) |
| 129 | +>>> time(14, 30, 23) |
| 130 | +datetime.time(14, 30, 23) |
| 131 | +``` |
| 132 | + |
| 133 | +Starting from an ISO 8601 format may be more readable in some cases: |
| 134 | + |
| 135 | +```python |
| 136 | +>>> time.fromisoformat('15:17:01-07:00') # mid-afternoon in Arizona |
| 137 | +datetime.time(15, 17, 1, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=61200))) |
| 138 | +``` |
| 139 | + |
| 140 | +Timezones will be discussed in more detail below. |
| 141 | + |
| 142 | +Arithmetic is not possible with `datetime.time` objects, but they do support comparisons. |
| 143 | + |
| 144 | +```python |
| 145 | +>>> time1 = time(14, 45) |
| 146 | +>>> time2 = time(16, 21, 30) |
| 147 | +>>> time1 > time2 |
| 148 | +False |
| 149 | +``` |
| 150 | + |
| 151 | +As with `date`, individual parts are available as instance attributes: |
| 152 | + |
| 153 | +```python |
| 154 | +>>> time(16, 21, 30).hour |
| 155 | +16 |
| 156 | +``` |
| 157 | + |
| 158 | +For other methods and properties, see the [class documentation][datetime-time]. |
| 159 | +Much of it relates to working with timezones. |
| 160 | + |
| 161 | +## The [`datetime.datetime`][datetime-datetime] class |
| 162 | + |
| 163 | +`datetime.datetime` combines most of the features of the `date` and `time` classes and adds some extras. |
| 164 | + |
| 165 | +It is the most versatile of these three classes, at the cost of some additional complexity. |
| 166 | + |
| 167 | +```python |
| 168 | +>>> from datetime import datetime |
| 169 | + |
| 170 | +>>> datetime.now() |
| 171 | +datetime.datetime(2023, 12, 4, 15, 45, 50, 66178) |
| 172 | + |
| 173 | +>>> datetime.now().isoformat() |
| 174 | +'2023-12-04T15:46:30.311480' |
| 175 | +``` |
| 176 | + |
| 177 | +As with `date`, the default constructor has the same syntax as the default display. |
| 178 | + |
| 179 | +The year, month and day parameters are required. Time parameters default to `0`. Timezone defaults to `None`, as in the example above. |
| 180 | + |
| 181 | +Keeping all these parameters straight can be a challenge, so the ISO format may be preferable: |
| 182 | + |
| 183 | +```python |
| 184 | +>>> datetime.fromisoformat('2023-12-04T15:53+05:30') # Delhi time |
| 185 | +datetime.datetime(2023, 12, 4, 15, 53, tzinfo=datetime.timezone(datetime.timedelta(seconds=19800))) |
| 186 | +``` |
| 187 | + |
| 188 | +Much of the functionality in `datetime.datetime` will be familar from `date` and time. |
| 189 | + |
| 190 | +One addition that may be useful is `combine(date, time)` which constructs a `datetime` instance from a `date` and a `time` instance (and optionally a timezone). |
| 191 | + |
| 192 | +```python |
| 193 | +>>> today = date.today() |
| 194 | +>>> current_time = time(4, 5) |
| 195 | + |
| 196 | +>>> datetime.combine(today, current_time) |
| 197 | +datetime.datetime(2023, 12, 4, 4, 5) |
| 198 | + |
| 199 | +>>> datetime.combine(today, current_time).isoformat() |
| 200 | +'2023-12-04T04:05:00' |
| 201 | +``` |
| 202 | + |
| 203 | +For other methods and properties, see the [class documentation][datetime-time]. |
| 204 | +Much of it relates to working with timezones. |
| 205 | + |
| 206 | +## The [`datetime.timedelta`][datetime-timedelta] class |
| 207 | + |
| 208 | +A `timedelta` is an interval of time, the difference between two `datetime` instances. |
| 209 | + |
| 210 | +Be careful with the constructor. |
| 211 | +The parameters are in an order you may not expect: |
| 212 | + |
| 213 | +`datetime.timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)` |
| 214 | + |
| 215 | +Thus, it is generally safer to treat these as keyword rather than positional parameters: |
| 216 | + |
| 217 | +```python |
| 218 | +>>> from datetime import timedelta |
| 219 | +>>> timedelta(weeks=3, hours=5) |
| 220 | +datetime.timedelta(days=21, seconds=18000) |
| 221 | +``` |
| 222 | + |
| 223 | +This illustrates the reason for the strange sequence: only days, seconds and microseconds are stored internally. |
| 224 | +Other parameters are provided as a convenience for the programmer, but will be converted. |
| 225 | + |
| 226 | +Similarly, floating-point input values will be converted to integer days, seconds and microseconds. |
| 227 | + |
| 228 | +With a `datetime` and a `timedelta` it is possible to add and subtract them: |
| 229 | + |
| 230 | +```python |
| 231 | +>>> now = datetime.now() |
| 232 | +>>> now.isoformat() |
| 233 | +'2023-12-04T16:24:07.242274' |
| 234 | + |
| 235 | +>>> later = now + timedelta(hours = 2.5) # 2.5 hours later |
| 236 | +>>> later.isoformat() |
| 237 | +'2023-12-04T18:54:07.242274' |
| 238 | +``` |
| 239 | + |
| 240 | +Alternatively, substract one `datetime` from another to get the `timedelta`: |
| 241 | + |
| 242 | +```python |
| 243 | +>>> dt1 = datetime.fromisoformat('2023-12-04T16:45') |
| 244 | +>>> dt2 = datetime.fromisoformat('2023-07-23T09:16') |
| 245 | + |
| 246 | +>>> dt1 - dt2 |
| 247 | +datetime.timedelta(days=134, seconds=26940) |
| 248 | + |
| 249 | +>>> str(dt2 - dt1) # formatted string output |
| 250 | +'-135 days, 16:31:00' |
| 251 | +``` |
| 252 | + |
| 253 | +Other arithmetic operations are supported, including: |
| 254 | +- Multiplying or dividing a `timedelta` by and `int` or `float` |
| 255 | +- Dividing one `timedelta` by another to get a `float` |
| 256 | +- Integer division (with `//`) and modulus (with `%`) with two `timedelta` instances. |
| 257 | + |
| 258 | +See the [class documentation][datetime-timedelta] for details. |
| 259 | + |
| 260 | + |
| 261 | +## The [`datetime.tzinfo`][datetime-tzinfo] class and its sub-classes |
| 262 | + |
| 263 | +Dealing with timezones can be challanging. |
| 264 | +Quoting the Python documentation: |
| 265 | + |
| 266 | +_"The rules for time adjustment across the world are more political than rational, change frequently, and there is no standard suitable for every application aside from UTC."_ |
| 267 | + |
| 268 | +Python provides various libraries to help deal with this situation. |
| 269 | +A brief summary is given below, but anyone wishing to write reliable timezone-aware software cannot avoid reading the full documentation. |
| 270 | + |
| 271 | +The [`datetime.tzinfo`][datetime-tzinfo] class is an [`abstract base class`][ABC]. |
| 272 | +ABCs are a relatively advanced topic, but the essential point is that `tzinfo` cannot be instantiated directly. |
| 273 | + |
| 274 | +Instead, this ABC provides a starting point for timezone-related subclasses to derive from. |
| 275 | + |
| 276 | +[`datetime.timezone`][datetime-timezone] is a simple, concrete subclass for situations with a fixed offset from UTC. |
| 277 | +A limitation of `timezone` is that it has no understanding of Daylight Savings Time (DST) adjustments, it just stores a constant `timedelta` in seconds. |
| 278 | + |
| 279 | +```python |
| 280 | +>>> datetime.fromisoformat('2023-12-04T15:53+03:00') |
| 281 | +datetime.datetime(2023, 12, 4, 15, 53, tzinfo=datetime.timezone(datetime.timedelta(seconds=10800))) |
| 282 | +``` |
| 283 | + |
| 284 | +[`zoneinfo.ZoneInfo`][zoneinfo] is a more sophisticated subclass, available in the standard library though not part of `datetime`. |
| 285 | +By linking to the [`tzdata`][tzdata] database, `ZoneInfo` understands DST issues worldwide. |
| 286 | +Multiple updates per year try, as far as possible, to remain up to date with unexpected changes. |
| 287 | + |
| 288 | +`ZoneInfo`, via `tzdata`, has access to the [`IANA`][IANA] timezone database, and so can work with timezone names in a `region/city` format. |
| 289 | +The full list of [`tznames`][IANA-names] also includes many shorter aliases. |
| 290 | + |
| 291 | +```python |
| 292 | +>>> from zoneinfo import ZoneInfo |
| 293 | + |
| 294 | +>>> dt = datetime(2020, 10, 31, 12, tzinfo=ZoneInfo("Europe/Helsinki")) |
| 295 | +>>> print(dt) |
| 296 | +2020-10-31 12:00:00+02:00 # 2h ahead of UTC |
| 297 | +>>> dt.tzname() |
| 298 | +'EET' # Eastern European Time |
| 299 | + |
| 300 | +>>> dt_subtract = dt - timedelta(days=7) # previous week |
| 301 | +>>> print(dt_subtract) |
| 302 | +2023-10-24 12:00:00+03:00 # now 3h ahead of UTC |
| 303 | +>>> dt_subtract.tzname() |
| 304 | +'EEST' # Eastern European Summer Time |
| 305 | +``` |
| 306 | + |
| 307 | +## The [`strptime()` and `strftime()`][strptime-strftime] methods |
| 308 | + |
| 309 | +The `datetime.datetime` class supports a complementary pair of methods: |
| 310 | +- `strptime()` parses a string representation to a `datetime` object. |
| 311 | +- `strftime()` outputs a string representation of a `datetime` object. |
| 312 | + |
| 313 | +Only `strftime()` is available in `datetime.date` and `datetime.time`. |
| 314 | + |
| 315 | +A wide variety of format codes is available. |
| 316 | +Some of the common ones are shown in the examples below, but see the [official documentation][strptime-strftime] for the full list. |
| 317 | +These format codes are copied directly from C, and may be familiar to programmers who have worked in other languages. |
| 318 | + |
| 319 | +```python |
| 320 | +>>> date_string = '14/10/23 23:59:59.999999' |
| 321 | +>>> format_string = '%d/%m/%y %H:%M:%S.%f' |
| 322 | +>>> dt = datetime.strptime(date_string, format_string) |
| 323 | +>>> dt |
| 324 | +datetime.datetime(2023, 10, 14, 23, 59, 59, 999999) |
| 325 | + |
| 326 | +>>> dt.strftime('%a %d %b %Y, %I:%M%p') |
| 327 | +'Sat 14 Oct 2023, 11:59PM' |
| 328 | +``` |
| 329 | + |
| 330 | +## Related modules |
| 331 | + |
| 332 | +This Concept has concentrated on the [`datetime`][datetime] module. |
| 333 | + |
| 334 | +Python has other modules which work with dates and times. |
| 335 | + |
| 336 | +### The [`time`][time] module |
| 337 | +Optimized for working with computer timestanps, for example in software logs. |
| 338 | + |
| 339 | +Not to be confused with `datetime.time`, a completely separate class. |
| 340 | + |
| 341 | +### The [`calendar`][calendar] module |
| 342 | +An alternative to `datetime.date`, `calendar` is more sophisticated in dealing with dates across a wide span of historical and future time. |
| 343 | + |
| 344 | +It also has CSS methods to halp with displaying calendars. |
| 345 | + |
| 346 | + |
| 347 | +### The [`zoneinfo`][zoneinfo] module |
| 348 | +Mainly consisting of the `ZoneInfo` class, a subclass of `datetime.tzinfo` which supports the [IANA database][IANA] and automatic DST adjustments. |
| 349 | + |
| 350 | + |
| 351 | + |
| 352 | +[ISO8601]: https://en.wikipedia.org/wiki/ISO_8601 |
| 353 | +[datetime]: https://docs.python.org/3/library/datetime.html |
| 354 | +[datetime-date]: https://docs.python.org/3/library/datetime.html#date-objects |
| 355 | +[datetime-time]: https://docs.python.org/3/library/datetime.html#time-objects |
| 356 | +[datetime-datetime]: https://docs.python.org/3/library/datetime.html#datetime-objects |
| 357 | +[datetime-timedelta]: https://docs.python.org/3/library/datetime.html#timedelta-objects |
| 358 | +[datetime-tzinfo]: https://docs.python.org/3/library/datetime.html#tzinfo-objects |
| 359 | +[datetime-timezone]: https://docs.python.org/3/library/datetime.html#timezone-objects |
| 360 | +[strptime-strftime]: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior |
| 361 | +[time]: https://docs.python.org/3/library/time.html |
| 362 | +[calendar]: https://docs.python.org/3/library/calendar.html |
| 363 | +[ABC]: https://docs.python.org/3/library/abc.html |
| 364 | +[zoneinfo]: https://docs.python.org/3/library/zoneinfo.html |
| 365 | +[tzdata]: https://peps.python.org/pep-0615/ |
| 366 | +[IANA]: https://en.wikipedia.org/wiki/Tz_database |
| 367 | +[IANA-names]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones |
| 368 | + |
| 369 | + |
0 commit comments