1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
|
# udatetime: Fast RFC3339 compliant date-time library
Handling date-times is a painful act because of the sheer endless amount
of formats used by people. Luckily there are a couple of specified standards
out there like [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601). But even
ISO 8601 leaves to many options on how to define date and time. That's why I
encourage using the [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) specified
date-time format.
`udatetime` offers on average 76% faster `datetime` object instantiation,
serialization and deserialization of RFC3339 date-time strings. `udatetime` is
using Python's [datetime class](https://docs.python.org/2/library/datetime.html)
under the hood and code already using `datetime` should be able to easily
switch to `udatetime`. All `datetime` objects created by `udatetime` are
timezone-aware. The timezones that `udatetime` uses are fixed-offset timezones,
meaning that they don't observe daylight savings time (DST), and thus return a
fixed offset from UTC all year round.
| | Support | Performance optimized | Implementation |
| -------- |:------------------:|:---------------------:| -------------- |
| Python 2 | :heavy_check_mark: | :heavy_check_mark: | C |
| Python 3 | :heavy_check_mark: | :heavy_check_mark: | C |
| PyPy | :heavy_check_mark: | :heavy_check_mark: | Pure Python |
```python
>>> udatetime.from_string("2016-07-15T12:33:20.123000+02:00")
datetime.datetime(2016, 7, 15, 12, 33, 20, 123000, tzinfo=+02:00)
>>> dt = udatetime.from_string("2016-07-15T12:33:20.123000+02:00")
>>> udatetime.to_string(dt)
'2016-07-15T12:33:20.123000+02:00'
>>> udatetime.now()
datetime.datetime(2016, 7, 29, 10, 15, 24, 472467, tzinfo=+02:00)
>>> udatetime.utcnow()
datetime.datetime(2016, 7, 29, 8, 15, 36, 184762, tzinfo=+00:00)
>>> udatetime.now_to_string()
'2016-07-29T10:15:46.641233+02:00'
>>> udatetime.utcnow_to_string()
'2016-07-29T08:15:56.129798+00:00'
>>> udatetime.to_string(udatetime.utcnow() - timedelta(hours=6))
'2016-07-29T02:16:05.770358+00:00'
>>> udatetime.fromtimestamp(time.time())
datetime.datetime(2016, 7, 30, 17, 45, 1, 536586, tzinfo=+02:00)
>>> udatetime.utcfromtimestamp(time.time())
datetime.datetime(2016, 8, 1, 10, 14, 53, tzinfo=+00:00)
```
## Installation
Currently only **POSIX** compliant systems are supported.
Working on cross-platform support.
```
$ pip install udatetime
```
You might need to install the header files of your Python installation and
some essential tools to execute the build like a C compiler.
**Python 2**
```
$ sudo apt-get install python-dev build-essential
```
or
```
$ sudo yum install python-devel gcc
```
**Python 3**
```
$ sudo apt-get install python3-dev build-essential
```
or
```
$ sudo yum install python3-devel gcc
```
## Benchmark
The benchmarks compare the performance of equivalent code of `datetime` and
`udatetime`. The benchmark measures the time needed for 1 million executions
and takes the `min` of 3 repeats. You can run the benchmark yourself and see
the results on your machine by executing the `bench_udatetime.py` script.

### Python 2.7
```
$ python scripts/bench_udatetime.py
Executing benchmarks ...
============ benchmark_parse
datetime_strptime 10.6306970119
udatetime_parse 1.109801054
udatetime is 9.6 times faster
============ benchmark_format
datetime_strftime 2.08363199234
udatetime_format 0.654432058334
udatetime is 3.2 times faster
============ benchmark_utcnow
datetime_utcnow 0.485884904861
udatetime_utcnow 0.237742185593
udatetime is 2.0 times faster
============ benchmark_now
datetime_now 1.37059998512
udatetime_now 0.235424041748
udatetime is 5.8 times faster
============ benchmark_utcnow_to_string
datetime_utcnow_to_string 2.56599593163
udatetime_utcnow_to_string 0.685483932495
udatetime is 3.7 times faster
============ benchmark_now_to_string
datetime_now_to_string 3.68989396095
udatetime_now_to_string 0.687911987305
udatetime is 5.4 times faster
============ benchmark_fromtimestamp
datetime_fromtimestamp 1.38640713692
udatetime_fromtimestamp 0.287910938263
udatetime is 4.8 times faster
============ benchmark_utcfromtimestamp
datetime_utcfromtimestamp 0.533131837845
udatetime_utcfromtimestamp 0.279694080353
udatetime is 1.9 times faster
```
### Python 3.5
```
$ python scripts/bench_udatetime.py
Executing benchmarks ...
============ benchmark_parse
datetime_strptime 9.90670353400003
udatetime_parse 1.1668808249999074
udatetime is 8.5 times faster
============ benchmark_format
datetime_strftime 3.0286041580000074
udatetime_format 0.7153575119999687
udatetime is 4.2 times faster
============ benchmark_utcnow
datetime_utcnow 0.5638177430000724
udatetime_utcnow 0.2548112540000602
udatetime is 2.2 times faster
============ benchmark_now
datetime_now 1.457822759999999
udatetime_now 0.26195338699994863
udatetime is 5.6 times faster
============ benchmark_utcnow_to_string
datetime_utcnow_to_string 3.5347913849999486
udatetime_utcnow_to_string 0.750341161999927
udatetime is 4.7 times faster
============ benchmark_now_to_string
datetime_now_to_string 4.854975383999999
udatetime_now_to_string 0.7411948169999505
udatetime is 6.6 times faster
============ benchmark_fromtimestamp
datetime_fromtimestamp 1.4233373309999706
udatetime_fromtimestamp 0.31758270299997093
udatetime is 4.5 times faster
============ benchmark_utcfromtimestamp
datetime_utcfromtimestamp 0.5633522409999614
udatetime_utcfromtimestamp 0.305099536000057
udatetime is 1.8 times faster
```
### PyPy 5.3.1
```
$ python scripts/bench_udatetime.py
Executing benchmarks ...
============ benchmark_parse
datetime_strptime 2.31050491333
udatetime_parse 0.838973045349
udatetime is 2.8 times faster
============ benchmark_format
datetime_strftime 0.957178115845
udatetime_format 0.162060976028
udatetime is 5.9 times faster
============ benchmark_utcnow
datetime_utcnow 0.149839878082
udatetime_utcnow 0.149217844009
udatetime is as fast as datetime
============ benchmark_now
datetime_now 0.967023134232
udatetime_now 0.15003991127
udatetime is 6.4 times faster
============ benchmark_utcnow_to_string
datetime_utcnow_to_string 1.24988698959
udatetime_utcnow_to_string 0.632546901703
udatetime is 2.0 times faster
============ benchmark_now_to_string
datetime_now_to_string 2.13041496277
udatetime_now_to_string 0.607964038849
udatetime is 3.5 times faster
============ benchmark_fromtimestamp
datetime_fromtimestamp 0.903736114502
udatetime_fromtimestamp 0.0907990932465
udatetime is 10.0 times faster
============ benchmark_utcfromtimestamp
datetime_utcfromtimestamp 0.0890419483185
udatetime_utcfromtimestamp 0.0907027721405
udatetime is as fast as datetime
```
## Why RFC3339
The RFC3339 specification has the following advantages:
- Defined date, time, timezone, date-time format
- 4 digit year
- Fractional seconds
- Human readable
- No redundant information like weekday name
- Simple specification, easily machine readable
### RFC3339 format specification
```
date-fullyear = 4DIGIT
date-month = 2DIGIT ; 01-12
date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
; month/year
time-hour = 2DIGIT ; 00-23
time-minute = 2DIGIT ; 00-59
time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
; rules
time-secfrac = "." 1*DIGIT
time-numoffset = ("+" / "-") time-hour ":" time-minute
time-offset = "Z" / time-numoffset
partial-time = time-hour ":" time-minute ":" time-second [time-secfrac]
full-date = date-fullyear "-" date-month "-" date-mday
full-time = partial-time time-offset
date-time = full-date "T" full-time
```
`udatetime` specific format addons:
- time-secfrac from 1DIGIT up to 6DIGIT
- time-secfrac will be normalized to microseconds
- time-offset is optional. Missing time-offset will be treated as UTC.
- spaces will be eliminated
## Why in C?
The Python `datetime` library is flexible but painfully slow, when it comes to
parsing and formating. High performance applications like web services or
logging benefit from fast underlying libraries. Restricting the input format
to one standard allows for optimization and results in speed improvements.
|