A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>“Eppur si muove!”* – Dealing with Timezones in Python (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="http://lucumr.pocoo.org/2011/7/15/eppur-si-muove/">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. “Eppur si muove!”* – Dealing with Timezones in Python (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="http://lucumr.pocoo.org/2011/7/15/eppur-si-muove/">Source originale du contenu</a></h3>
  445. <p>As a result of our world not being a flat disc but a rotating geoid and
  446. our solar system only having one sun, we have different time of days at
  447. different parts at precisely the same time. Everybody learns that in
  448. school these days and is well aware of the effects on human life (“Call
  449. your aunt over sea and she will pick up at an odd time”, jetlag etc.).
  450. But unfortunately that whole timezone thing is only partially based on
  451. constraints our world gave us and in computing we have to deal with these
  452. oddities as well.</p>
  453. <p><small><p>* “<a class="reference external" href="http://en.wikipedia.org/wiki/E_pur_si_muove!">and yet it moves</a>” is
  454. what people say Galileo Galilei uttered upon leaving the courtyard after
  455. being forced to recant his belief that the Earth rotates around the Sun.
  456. Which unfortunately is the case and gives us these wonderful timezone
  457. problems.</p>
  458. <p>What does this article have to do with Galileo? Not really much I am
  459. afraid because even if the world would be in the center of the universe
  460. you would still have timezones. Consider the title a mistake on my part
  461. which I cannot correct now, can I :-)</p>
  462. </small><div class="section" id="what-s-a-timezone">
  463. <h2>What's a Timezone?</h2>
  464. <p>What's your timezone? If you respond with “UTC+X” that will be correct
  465. for this very moment, but not necessarily true over time. If you look at
  466. the timezone info database you will find that Berlin and Vienna, even
  467. though they are both in “UTC+1” will have a different timezone
  468. (Europe/Berlin vs Europe/Vienna). Why that? The reason are differences
  469. in daylight saving time and historical dates. Even if those two countries
  470. and cities nowadays have the same DST configurations, a hundred years ago
  471. that was not the case. Both Austria and Germany for instance used to not
  472. have DST over periods of time. Austria stopped in 1920, Germany did in
  473. 1918. During WWII both countries unsurprisingly had the same DST
  474. configuration, but afterwards there are a few unsynchronized years again.
  475. Germany abolished DST in 1949 and reintroduced DST in 1979, Austria
  476. abolished it in 1948 and reintroduced it in 1980. What's worse is that
  477. they did not even select the same date for the switch.</p>
  478. <p>And this pattern is quite common all around the world. For computing DST
  479. is a huge problem. The reason for that is that we're usually assuming
  480. that time has a monotonic advancing. With daylight saving time, during
  481. that one hour of enabling/disabling each year we either get an hour twice
  482. or we skip an entire hour. Results are log entries that appear out of
  483. order if you log with local time for instance.</p>
  484. <p>To quote the pytz documentation:</p>
  485. <blockquote>
  486. For example, 1:30am on 27th Oct 2002 happened twice in the US/Eastern
  487. timezone when the clocks where put back at the end of Daylight Savings
  488. Time, similarly, 2:30am on 7th April 2002 never happened at all in the
  489. US/Eastern timezone, as the clocks where put forward at 2:00am
  490. skipping the entire hour</blockquote>
  491. <p>But timezones have more than just DST settings. Some countries are
  492. switching the means of time measuring altogether, in some cases even
  493. without entering or leaving DST. For example, in 1915 Warsaw switched
  494. from Warsaw time to Central European time. So at the stroke of midnight on
  495. August 5th 1915 the clocks were wound back 24 minutes. In neither case
  496. was DST active.</p>
  497. <p>Much fun can be had with timezones in general. There was at least one
  498. country that at one point had a timezone that differed per day because
  499. they synchronized 0:00 with the time of the sunrise.</p>
  500. </div>
  501. <div class="section" id="where-is-the-sanity">
  502. <h2>Where is the Sanity?</h2>
  503. <p>The sanity right now is called UTC. UTC is a timezone without daylight
  504. saving time and still a timezone without configuration changes in the
  505. past. However because our world is again this rotating geoid and
  506. something we don't really have under control, the problem of leap seconds
  507. will at one point show up. If UTC will then take leap seconds into
  508. account (which are irregular and with that problem for computing) or not
  509. (and each timezone will have sub-minute differences to UTC) is, as far as
  510. I know, nothing that was decided for sure yet.</p>
  511. <p>However right now, UTC is the safest bet. From UTC you can convert into
  512. any local time, however of course the reverse is not true due to what was
  513. shown above.</p>
  514. <p>So here the rule of thumb which never shall be broken:</p>
  515. <blockquote>
  516. <strong>Always measure and store time in UTC</strong>. If you need to record where
  517. the time was taken, store that separately. Do not store the local
  518. time + timezone information!</blockquote>
  519. </div>
  520. <div class="section" id="where-is-the-problem">
  521. <h2>Where is the Problem?</h2>
  522. <p>Now in theory that blog post should end here and we all go on with our
  523. lives. Unfortunately in Python there are a couple of more things to keep
  524. in mind due to some design decisions that were made a long ago that were
  525. not thought well through. The motivation was sound, the implications
  526. however were not.</p>
  527. <p>At one time the following decisions were apparently made for the datetime
  528. module in the standard library:</p>
  529. <ol class="arabic simple">
  530. <li>the datetime module should not ship timezone information because
  531. timeszones change too often.</li>
  532. <li>the datetime module however should provide an API to attach timezone
  533. information to a datetime object.</li>
  534. <li>It should provide these objects: date, time, date+time, timedelta</li>
  535. </ol>
  536. <p>Unfortunately a few things went wrong. The biggest problem is that a
  537. datetime object with timezone information attached and a datetime object
  538. without that timezone information don't work at all together:</p>
  539. <div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">import</span> <span class="nn">pytz</span><span class="o">,</span> <span class="nn">datetime</span>
  540. <span class="gp">&gt;&gt;&gt; </span><span class="n">a</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">utcnow</span><span class="p">()</span>
  541. <span class="gp">&gt;&gt;&gt; </span><span class="n">b</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">utcnow</span><span class="p">()</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="n">tzinfo</span><span class="o">=</span><span class="n">pytz</span><span class="o">.</span><span class="n">utc</span><span class="p">)</span>
  542. <span class="gp">&gt;&gt;&gt; </span><span class="n">a</span> <span class="o">&lt;</span> <span class="n">b</span>
  543. <span class="gt">Traceback (most recent call last):</span>
  544. File <span class="nb">&quot;&lt;stdin&gt;&quot;</span>, line <span class="m">1</span>, in <span class="n">&lt;module&gt;</span>
  545. <span class="gr">TypeError</span>: <span class="n">can&#39;t compare offset-naive and offset-aware datetimes</span>
  546. </pre></div>
  547. <p>Ignoring the horrible API you have to use to attach a timezone information
  548. to a datetime object this leads to quite a few problems. If you are
  549. dealing with datetime objects in Python you will sooner or later start
  550. attaching and removing tzinfo objects all over the place.</p>
  551. <p>Another problem is that there are two ways to create a datetime object for
  552. the current time in Python:</p>
  553. <div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">utcnow</span><span class="p">()</span>
  554. <span class="go">datetime.datetime(2011, 7, 15, 8, 30, 55, 375010)</span>
  555. <span class="gp">&gt;&gt;&gt; </span><span class="n">datetime</span><span class="o">.</span><span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
  556. <span class="go">datetime.datetime(2011, 7, 15, 10, 30, 57, 70767)</span>
  557. </pre></div>
  558. <p>One gives the time in UTC, the other in local time. However it will not
  559. tell you what local time is (because it does not have a timezone
  560. information object, at least before 3.3), and it does not give you way to
  561. know which one was UTC.</p>
  562. <p>If you convert from a UNIX timestamp into a datetime object you also have
  563. to be very careful to use the <cite>datetime.datetime.utcfromtimestamp</cite> method
  564. because the normal one will assume the timestamp is in local time.</p>
  565. <p>On top of that, the library provides a <cite>time</cite> object and a <cite>date</cite> object,
  566. both of which are close to being useless when timezones are involved. The
  567. former cannot be shifted to other timezones because that would require the
  568. date component. The date itself also only makes any sense local to a
  569. timezone because what's today for me, could be tomorrow or yesterday for
  570. you thanks to the wonderful world of timezones.</p>
  571. </div>
  572. <div class="section" id="what-s-the-best-practice">
  573. <h2>What's the Best Practice?</h2>
  574. <p>Now we know where the culprits are. What should we do? If we ignore
  575. theoretical problems that won't show up anyways unless we deal with
  576. history times there are a few best practices that make your life easier.
  577. If you ever have the problem with historic dates, there is an alternative
  578. module called <a class="reference external" href="http://www.egenix.com/products/python/mxBase/mxDateTime/">mxDateTime</a> which
  579. generally follows a better design and supports multiple calendars as well
  580. (Gregorian and Julian).</p>
  581. <div class="section" id="internally-use-utc">
  582. <h3>Internally use UTC</h3>
  583. <p>This should be a given. When you take the current time, always use
  584. <cite>datetime.datetime.utcnow()</cite>. If you are taking in user input that is in
  585. local time, immediately convert it to UTC. If that conversion would be
  586. ambiguous let the user know. Do not blindly guess. I know every time the
  587. DST switch comes up I am setting a second analog clock and not just my
  588. phone because my iPhone failed with that conversion twice now.</p>
  589. </div>
  590. <div class="section" id="do-not-use-offset-aware-datetimes">
  591. <h3>Do not use offset aware datetimes</h3>
  592. <p>It might sound like a good idea to always attach a tzinfo object, but it's
  593. actually a much better idea to not do that. If you assume that every
  594. datetime object without a tzinfo object is in UTC, that's the better
  595. solution. You can actually take advantage of the fact that you cannot
  596. compare these two, similar to how you cannot mix bytes and unicode in
  597. Python 3. Use that “API weakness” to your advantage.</p>
  598. <ol class="arabic simple">
  599. <li>internally always use offset naive datetime objects and consider them
  600. UTC.</li>
  601. <li>When interfacing with the user, convert to and from local time.</li>
  602. </ol>
  603. <p>Why would you not want to attach an UTC tzinfo object? First of all
  604. because the majority of libraries are written with the assumption of
  605. <cite>tzinfo</cite> == None in mind. Secondly because it was a horrible idea to have
  606. this tzinfo object in the first place as the API is broken. If you look
  607. into the pytz library it has to provide alternative functions for the
  608. conversion because the intended API for timezone conversions is not
  609. flexible enough to represent the majority of timezones. By not using
  610. tzinfo objects there is a chance that we can one time change to something
  611. better.</p>
  612. <p>Another reason for not using offset aware datetimes is that the tzinfo
  613. object is implementation defined. There is no standard way to transport
  614. that timezone information (with the exception of the UTC offset in that
  615. very moment) to other languages or over HTTP etc. Also datetime objects
  616. with timezone often cause much larger pickles or broken pickles altogether
  617. depending on the implementation of that timezone object.</p>
  618. </div>
  619. <div class="section" id="rebase-for-formatting">
  620. <h3>Rebase for Formatting</h3>
  621. <p>If you then want to show the time in the user's local timezone take that
  622. UTC datetime object, attach the <cite>UTC</cite> timezone information, look up the
  623. user's timezone, rebase to local time and format. Do not do the
  624. conversion of the timezone with the tzinfo method which is known to be
  625. broken, but use the pytz one. Then throw away that filthy offset aware
  626. datetime object you've created for formatting and go on with your life.</p></p>
  627. </article>
  628. </section>
  629. <nav id="jumpto">
  630. <p>
  631. <a href="/david/blog/">Accueil du blog</a> |
  632. <a href="http://lucumr.pocoo.org/2011/7/15/eppur-si-muove/">Source originale</a> |
  633. <a href="/david/stream/2019/">Accueil du flux</a>
  634. </p>
  635. </nav>
  636. <footer>
  637. <div>
  638. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  639. <p>
  640. Bonjour/Hi!
  641. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  642. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  643. </p>
  644. <p>
  645. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  646. </p>
  647. <p>
  648. Voici quelques articles choisis :
  649. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  650. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  651. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  652. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  653. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  654. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  655. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  656. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  657. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  658. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  659. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  660. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  661. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  662. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  663. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  664. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  665. </p>
  666. <p>
  667. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  668. </p>
  669. <p>
  670. Je ne traque pas ta navigation mais mon
  671. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  672. conserve des logs d’accès.
  673. </p>
  674. </div>
  675. </footer>
  676. <script type="text/javascript">
  677. ;(_ => {
  678. const jumper = document.getElementById('jumper')
  679. jumper.addEventListener('click', e => {
  680. e.preventDefault()
  681. const anchor = e.target.getAttribute('href')
  682. const targetEl = document.getElementById(anchor.substring(1))
  683. targetEl.scrollIntoView({behavior: 'smooth'})
  684. })
  685. })()
  686. </script>