19
19
7 . [ Use in synchronous code] ( ./SCHEDULE.md#7-use-in-synchronous-code ) If you really must.
20
20
7.1 [ Initialisation] ( ./SCHEDULE.md#71-initialisation ) __
21
21
8 . [ The simulate script] ( ./SCHEDULE.md#8-the-simulate-script ) Rapidly test sequences.
22
- 9 . [ Design note ] ( ./SCHEDULE.md#9-design-note ) Notes on use under an OS.
22
+ 9 . [ Daylight saving time ] ( ./SCHEDULE.md#9-daylight-saving-time ) Notes on timezone and DST when running under an OS.
23
23
24
- Release note:
24
+ Release note:
25
+ 7th Sep 2024 Document timezone and DST behaviour under Unix build.
25
26
11th Dec 2023 Document astronomy module, allowing scheduling based on Sun and
26
27
Moon rise and set times.
27
28
23rd Nov 2023 Add asynchronous iterator interface.
@@ -271,23 +272,24 @@ A `cron` call typically takes 270 to 520μs on a Pyboard, but the upper bound
271
272
depends on the complexity of the time specifiers.
272
273
273
274
On hardware platforms the MicroPython ` time ` module does not handle daylight
274
- saving time. Scheduled times are relative to system time. This does not apply
275
- to the Unix build where daylight saving needs to be considered.
275
+ saving time. Scheduled times are relative to system time. Under the Unix build,
276
+ where the locale uses daylight saving, its effects should be considered. See
277
+ [ Daylight saving time] ( ./SCHEDULE.md#9-daylight-saving-time ) .
276
278
277
279
## 4.4 The Unix build
278
280
279
281
Asynchronous use requires ` asyncio ` V3, so ensure this is installed on the
280
- Linux target.
281
-
282
- The synchronous and asynchronous demos run under the Unix build. The module is
283
- usable on Linux provided the daylight saving time (DST) constraints are met. A
284
- consequence of DST is that there are impossible times when clocks go forward
285
- and duplicates when they go back. Scheduling those times will fail. A solution
286
- is to avoid scheduling the times in your region where this occurs (01.00.00 to
287
- 02.00.00 in March and October here).
282
+ Linux target. This may be checked with:
283
+ ``` py
284
+ import asyncio
285
+ asyncio.__version__
286
+ ```
287
+ The module uses local time. When running under an OS, local time is affected by
288
+ geographical longitude (timezone - TZ) and daylight saving time (DST). The use
289
+ of local time avoids TZ issues but has consequences when the underlying time
290
+ source changes due to crossing a DST boundary.
288
291
289
- It is believed that in other respects DST is handled correctly by the OS: see
290
- [ Design note] ( ./SCHEDULE.md#9-design-note ) .
292
+ This is explained in detail in [ Daylight saving time] ( ./SCHEDULE.md#9-daylight-saving-time ) .
291
293
292
294
##### [ Top] ( ./SCHEDULE.md#0-contents )
293
295
@@ -551,36 +553,53 @@ sequence is delayed to ensure that the first trigger occurs at 01:00.
551
553
552
554
##### [ Top] ( ./SCHEDULE.md#0-contents )
553
555
554
- # 9. Design note
556
+ # 9. Daylight saving time
555
557
556
- This module is primarily intended for use on a microcontroller, where the time
557
- source is a hardware RTC. This is usually set to local time and does not change
558
- for daylight saving time (DST). Changing the system time while running ` asyncio `
559
- code is not recommended.
560
-
561
- A [ question was raised] ( https://github.com/peterhinch/micropython-async/pull/126 )
562
- regarding the behaviour of the module when running under the Unix build - in
563
- particular whether the module's use of ` time.localtime ` is correct, because
564
- ` .localtime ` changes when DST is invoked. To test whether a problem exists, an
565
- attempt was made to write a script whose behaviour under Unix differed from that
566
- on a microcontroller. The latter has no concept of DST or timezone (TZ) so can
567
- be assumed to be free of such bugs. Unless such a reproducer can be found, it
568
- seems that usage under the Unix build should be correct.
569
-
570
- The following test script outputs the time in seconds between two fixed times
571
- separated by two months, the period being chosen to cross a DST boundary here in
572
- the UK. It passed under the following conditions:
573
-
574
- * On a Pyboard.
575
- * On an ESP32.
576
- * On Unix MicroPython.
577
- * On CPython.
578
- * On the Unix build with my laptop's location set to California. Reported time
579
- changed by -7hrs.
580
- * On CPython in California.
581
-
582
- The conclusion is that the OS ensures that DST related errors do not occur.
558
+ Thanks are due to @rhermanklink for raising this issue.
583
559
560
+ This module is primarily intended for use on a microcontroller, where the time
561
+ source is a hardware RTC. This is usually set to local time, and must not change
562
+ for daylight saving time (DST); on a microcontroller neither this module nor
563
+ ` asyncio ` will work correctly if system time is changed at runtime. Under an OS,
564
+ some kind of thaumaturgy enables ` asyncio ` to tolerate this behaviour.
565
+
566
+ Internally the module uses local time (` time.time() ` and ` time.localtime() ` ) to
567
+ retrieve the current time. Under an OS, in a locale where DST is used, the time
568
+ returned by these methods does not increase monotonically but is subject to
569
+ sudden changes at a DST boundary.
570
+
571
+ A ` cron ` instance accepts "time now" measured in seconds from the epoch, and
572
+ returns the time to wait for the first scheduled event. This wait time is
573
+ calculated on the basis of a monotonic local time. Assume that the time is
574
+ 10:00:00 on 1st August, and the first scheduled event is at 10:00:00 on 1st
575
+ November. The ` cron ` instance will return the time to wait. The application task
576
+ waits for that period, but local clocks will have changed so that the time reads
577
+ 9:00:00.
578
+
579
+ The primary application for this module is on microcontrollers. Further, there
580
+ are alternatives such as [ Python schedule] ( https://github.com/dbader/schedule )
581
+ which are designed to run under an OS. Fixing this would require a timezone
582
+ solution; in many cases the application can correct for DST. Consequently this
583
+ behaviour has been deemed to be in the "document, don't fix" category.
584
+
585
+ The following notes are general observations which may be of interest.
586
+
587
+ ### The epoch
588
+
589
+ The Python ` time.time() ` method returns the number of seconds since the epoch.
590
+ This is computed relative to the system clock; consecutive calls around a DST
591
+ change will yield a sudden change (+3600 secs for a +one hour change).
592
+ This value may be converted to a time tuple with ` time.gmtime(secs) ` or with
593
+ ` time.localtime(secs) ` . If UTC and local time differ, for the same value of
594
+ ` secs ` these will produce UTC-relative and localtime-relative tuples.
595
+
596
+ Consider ` time.mktime() ` . This converts a time tuple to a number of seconds
597
+ since the epoch. The time difference between a specified time and the epoch is
598
+ independent of timezone and DST. The specified time and the epoch are assumed to
599
+ be defined in the same (unknown, unspecified) time system. Consequently, if a
600
+ delay is defined by the difference between two ` mktime() ` values, that delay
601
+ will be unaffected if a DST change occurs between those two values. This may be
602
+ verified with the following script:
584
603
``` py
585
604
from time import mktime, gmtime, localtime
586
605
from sys import implementation
@@ -602,3 +621,15 @@ if november - sept == 5270400:
602
621
else :
603
622
print (' FAIL' )
604
623
```
624
+ This test passes on the Unix build, under CPython, and on MicroPython on a
625
+ microcontroller. It also passes under an OS if the system's local time differs
626
+ substantially from UTC.
627
+
628
+ The ` cron ` module returns a time difference between a passed time value and one
629
+ produced by ` mktime() ` : accordingly ` cron ` takes no account of local time or
630
+ DST. If local time is changed while waiting for the period specified by ` cron ` ,
631
+ at the end of the delay, clocks measuring local time will indicate an incorrect
632
+ time.
633
+
634
+ This is only an issue when running under an OS: if it is considered an error, it
635
+ should be addressed in application code.
0 commit comments